Compare commits

...

22 Commits

Author SHA1 Message Date
34338e7107 Chore: update dependencies & fix typo 2019-06-29 16:48:48 +08:00
57fdd223f1 Feature: custom dns ipv4/ipv6 dual stack 2019-06-29 00:58:59 +08:00
bc3fc0c840 Feature: support DoH 2019-06-28 12:29:08 +08:00
662038e40e Fix: log correctly path 2019-06-27 22:56:24 +08:00
53528f8275 Fix: crash when authenticator is nil 2019-06-27 20:45:12 +08:00
1c792b46c9 Feature: local socks5/http(s) auth (#216) 2019-06-27 17:04:25 +08:00
2417cfda12 Chore: set log output to stdout 2019-06-26 20:30:57 +08:00
aa3516ca24 Chore: use 'dns' for ALPN in tcp-tls nameserver (#209) 2019-06-20 15:50:01 +08:00
bcf5b21208 Fix: check target is valid in rules (#210) 2019-06-20 11:03:50 +08:00
ba5eefb6dd Chore: clean up Dockfile 2019-06-19 22:12:25 +08:00
407de7388c Standardized: use recommend extension & forward compatibility before 1.0 2019-06-18 20:55:26 +08:00
6adafde9a0 Fix: strict ss obfs check 2019-06-18 20:37:53 +08:00
cba548114f Fix: use original sequence for url-test group (#201) 2019-06-13 20:18:07 +08:00
016e7bd0b4 Style: fix go vet warning 2019-05-26 13:55:13 +08:00
ad13ad8dba Fix: add mutex for fake ip pool 2019-05-23 23:27:29 +08:00
89168e6c96 Fix: DNS server not recreate correctly (#186) 2019-05-18 17:52:42 +08:00
a4b8e286db Fix: incorrect fake ip dns ttl (#187) 2019-05-18 17:44:12 +08:00
e837470a6a Fix: udp crash in tunnel 2019-05-16 18:40:20 +08:00
b589754485 Chore: fix typo (#182) 2019-05-16 14:19:37 +08:00
0eccbb023c Feature: make the selector proxies order as same as the order in the config file (#180)
* make the proxies order the same as the order in config file

* formatting & rename variable
2019-05-15 14:40:14 +08:00
71a08ad8e2 Chore: clean up code 2019-05-14 21:35:34 +08:00
0d4a999707 Chore: adjust fake-ip ttl 2019-05-12 10:48:07 +08:00
32 changed files with 817 additions and 373 deletions

View File

@ -18,6 +18,4 @@ RUN apk add --no-cache ca-certificates
COPY --from=builder /Country.mmdb /root/.config/clash/ COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash / COPY --from=builder /clash /
EXPOSE 7890 7891
ENTRYPOINT ["/clash"] ENTRYPOINT ["/clash"]

View File

@ -1,6 +1,6 @@
NAME=clash NAME=clash
BINDIR=bin BINDIR=bin
VERSION=$(shell git describe --tags || echo "unkown version") VERSION=$(shell git describe --tags || echo "unknown version")
BUILDTIME=$(shell date -u) BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \

View File

@ -64,7 +64,7 @@ If you have Docker installed, you can run clash directly using `docker-compose`.
The default configuration directory is `$HOME/.config/clash` The default configuration directory is `$HOME/.config/clash`
The name of the configuration file is `config.yml` The name of the configuration file is `config.yaml`
If you want to use another directory, you can use `-d` to control the configuration directory If you want to use another directory, you can use `-d` to control the configuration directory
@ -107,7 +107,12 @@ external-controller: 127.0.0.1:9090
# experimental feature # experimental feature
experimental: experimental:
ignore-resolve-fail: true # ignore dns reslove fail, default value is true ignore-resolve-fail: true # ignore dns resolve fail, default value is true
# authentication of local SOCKS5/HTTP(S) server
# authentication:
# - "user1:pass1"
# - "user2:pass2"
# dns: # dns:
# enable: true # set true to enable dns (default is false) # enable: true # set true to enable dns (default is false)
@ -118,6 +123,7 @@ experimental:
# nameserver: # nameserver:
# - 114.114.114.114 # - 114.114.114.114
# - tls://dns.rubyfish.cn:853 # dns over tls # - tls://dns.rubyfish.cn:853 # dns over tls
# - https://1.1.1.1/dns-query # dns over https
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN # fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
# - tcp://1.1.1.1 # - tcp://1.1.1.1

View File

@ -16,7 +16,7 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) {
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
} }
c, err := net.DialTimeout("tcp", address, tcpTimeout) c, err := dialTimeout("tcp", address, tcpTimeout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -30,7 +30,7 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error)
return nil, nil, err return nil, nil, err
} }
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) addr, err := resolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -36,7 +36,7 @@ type HttpOption struct {
} }
func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) { func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", h.addr, tcpTimeout) c, err := dialTimeout("tcp", h.addr, tcpTimeout)
if err == nil && h.tls { if err == nil && h.tls {
cc := tls.Client(c, h.tlsConfig) cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake() err = cc.Handshake()

View File

@ -4,15 +4,15 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"net" "net"
"sort"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Selector struct { type Selector struct {
*Base *Base
selected C.Proxy selected C.Proxy
proxies map[string]C.Proxy proxies map[string]C.Proxy
proxyList []string
} }
type SelectorOption struct { type SelectorOption struct {
@ -33,15 +33,10 @@ func (s *Selector) SupportUDP() bool {
} }
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string
for k := range s.proxies {
all = append(all, k)
}
sort.Strings(all)
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"type": s.Type().String(), "type": s.Type().String(),
"now": s.Now(), "now": s.Now(),
"all": all, "all": s.proxyList,
}) })
} }
@ -64,8 +59,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
} }
mapping := make(map[string]C.Proxy) mapping := make(map[string]C.Proxy)
for _, proxy := range proxies { proxyList := make([]string, len(proxies))
for idx, proxy := range proxies {
mapping[proxy.Name()] = proxy mapping[proxy.Name()] = proxy
proxyList[idx] = proxy.Name()
} }
s := &Selector{ s := &Selector{
@ -73,8 +70,9 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
name: name, name: name,
tp: C.Selector, tp: C.Selector,
}, },
proxies: mapping, proxies: mapping,
selected: proxies[0], selected: proxies[0],
proxyList: proxyList,
} }
return s, nil return s, nil
} }

View File

@ -58,7 +58,7 @@ type v2rayObfsOption struct {
} }
func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) c, err := dialTimeout("tcp", ss.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
} }
@ -87,7 +87,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr,
return nil, nil, err return nil, nil, err
} }
addr, err := net.ResolveUDPAddr("udp", ss.server) addr, err := resolveUDPAddr("udp", ss.server)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -137,6 +137,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if err := decoder.Decode(option.PluginOpts, &opts); err != nil { if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error()) return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
} }
if opts.Mode != "tls" && opts.Mode != "http" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
}
obfsMode = opts.Mode obfsMode = opts.Mode
obfsOption = &opts obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" { } else if option.Plugin == "v2ray-plugin" {
@ -144,7 +148,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
if err := decoder.Decode(option.PluginOpts, &opts); err != nil { if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error()) return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
} }
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
}
obfsMode = opts.Mode obfsMode = opts.Mode
var tlsConfig *tls.Config var tlsConfig *tls.Config
if opts.TLS { if opts.TLS {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{

View File

@ -32,7 +32,7 @@ type Socks5Option struct {
} }
func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) c, err := dialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls { if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig) cc := tls.Client(c, ss.tlsConfig)
@ -58,7 +58,7 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) {
} }
func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) c, err := dialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls { if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig) cc := tls.Client(c, ss.tlsConfig)

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"net" "net"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -56,7 +55,6 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
for _, proxy := range u.proxies { for _, proxy := range u.proxies {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
sort.Strings(all)
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"type": u.Type().String(), "type": u.Type().String(),
"now": u.Now(), "now": u.Now(),

View File

@ -12,6 +12,7 @@ import (
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
) )
const ( const (
@ -96,3 +97,30 @@ func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := fuc.Conn.Read(b) n, err := fuc.Conn.Read(b)
return n, fuc.RemoteAddr(), err return n, fuc.RemoteAddr(), err
} }
func dialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := dns.ResolveIP(host)
if err != nil {
return nil, err
}
return net.DialTimeout(network, net.JoinHostPort(ip.String(), port), timeout)
}
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)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}

View File

@ -32,7 +32,7 @@ type VmessOption struct {
} }
func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) {
c, err := net.DialTimeout("tcp", v.server, tcpTimeout) c, err := dialTimeout("tcp", v.server, tcpTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error", v.server) return nil, fmt.Errorf("%s connect error", v.server)
} }
@ -42,7 +42,7 @@ func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) {
} }
func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) {
c, err := net.DialTimeout("tcp", v.server, tcpTimeout) c, err := dialTimeout("tcp", v.server, tcpTimeout)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("%s connect error", v.server) return nil, nil, fmt.Errorf("%s connect error", v.server)
} }

View File

@ -4,6 +4,7 @@ package murmur3
import ( import (
"hash" "hash"
"math/bits"
"unsafe" "unsafe"
) )
@ -54,11 +55,11 @@ func (d *digest32) bmix(p []byte) (tail []byte) {
k1 := *(*uint32)(unsafe.Pointer(&p[i*4])) k1 := *(*uint32)(unsafe.Pointer(&p[i*4]))
k1 *= c1_32 k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 = bits.RotateLeft32(k1, 15)
k1 *= c2_32 k1 *= c2_32
h1 ^= k1 h1 ^= k1
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) h1 = bits.RotateLeft32(h1, 13)
h1 = h1*4 + h1 + 0xe6546b64 h1 = h1*4 + h1 + 0xe6546b64
} }
d.h1 = h1 d.h1 = h1
@ -80,7 +81,7 @@ func (d *digest32) Sum32() (h1 uint32) {
case 1: case 1:
k1 ^= uint32(d.tail[0]) k1 ^= uint32(d.tail[0])
k1 *= c1_32 k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 = bits.RotateLeft32(k1, 15)
k1 *= c2_32 k1 *= c2_32
h1 ^= k1 h1 ^= k1
} }
@ -102,20 +103,15 @@ func Sum32WithSeed(data []byte, seed uint32) uint32 {
h1 := seed h1 := seed
nblocks := len(data) / 4 nblocks := len(data) / 4
var p uintptr for i := 0; i < nblocks; i++ {
if len(data) > 0 { k1 := *(*uint32)(unsafe.Pointer(&data[i*4]))
p = uintptr(unsafe.Pointer(&data[0]))
}
p1 := p + uintptr(4*nblocks)
for ; p < p1; p += 4 {
k1 := *(*uint32)(unsafe.Pointer(p))
k1 *= c1_32 k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 = bits.RotateLeft32(k1, 15)
k1 *= c2_32 k1 *= c2_32
h1 ^= k1 h1 ^= k1
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) h1 = bits.RotateLeft32(h1, 13)
h1 = h1*4 + h1 + 0xe6546b64 h1 = h1*4 + h1 + 0xe6546b64
} }
@ -132,7 +128,7 @@ func Sum32WithSeed(data []byte, seed uint32) uint32 {
case 1: case 1:
k1 ^= uint32(tail[0]) k1 ^= uint32(tail[0])
k1 *= c1_32 k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 = bits.RotateLeft32(k1, 15)
k1 *= c2_32 k1 *= c2_32
h1 ^= k1 h1 ^= k1
} }

46
component/auth/auth.go Normal file
View File

@ -0,0 +1,46 @@
package auth
import (
"sync"
)
type Authenticator interface {
Verify(user string, pass string) bool
Users() []string
}
type AuthUser struct {
User string
Pass string
}
type inMemoryAuthenticator struct {
storage *sync.Map
usernames []string
}
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
realPass, ok := au.storage.Load(user)
return ok && realPass == pass
}
func (au *inMemoryAuthenticator) Users() []string { return au.usernames }
func NewAuthenticator(users []AuthUser) Authenticator {
if len(users) == 0 {
return nil
}
au := &inMemoryAuthenticator{storage: &sync.Map{}}
for _, user := range users {
au.storage.Store(user.User, user.Pass)
}
usernames := make([]string, 0, len(users))
au.storage.Range(func(key, value interface{}) bool {
usernames = append(usernames, key.(string))
return true
})
au.usernames = usernames
return au
}

View File

@ -3,6 +3,7 @@ package fakeip
import ( import (
"errors" "errors"
"net" "net"
"sync"
) )
// Pool is a implementation about fake ip generator without storage // Pool is a implementation about fake ip generator without storage
@ -10,10 +11,13 @@ type Pool struct {
max uint32 max uint32
min uint32 min uint32
offset uint32 offset uint32
mux *sync.Mutex
} }
// Get return a new fake ip // Get return a new fake ip
func (p *Pool) Get() net.IP { func (p *Pool) Get() net.IP {
p.mux.Lock()
defer p.mux.Unlock()
ip := uintToIP(p.min + p.offset) ip := uintToIP(p.min + p.offset)
p.offset = (p.offset + 1) % (p.max - p.min) p.offset = (p.offset + 1) % (p.max - p.min)
return ip return ip
@ -46,5 +50,6 @@ func New(ipnet *net.IPNet) (*Pool, error) {
return &Pool{ return &Pool{
min: min, min: min,
max: max, max: max,
mux: &sync.Mutex{},
}, nil }, nil
} }

View File

@ -6,6 +6,8 @@ import (
"io" "io"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/auth"
) )
// Error represents a SOCKS error // Error represents a SOCKS error
@ -35,6 +37,9 @@ const (
// MaxAddrLen is the maximum size of SOCKS address in bytes. // MaxAddrLen is the maximum size of SOCKS address in bytes.
const MaxAddrLen = 1 + 1 + 255 + 2 const MaxAddrLen = 1 + 1 + 255 + 2
// MaxAuthLen is the maximum size of user/password field in SOCKS5 Auth
const MaxAuthLen = 255
// Addr represents a SOCKS address as defined in RFC 1928 section 5. // Addr represents a SOCKS address as defined in RFC 1928 section 5.
type Addr = []byte type Addr = []byte
@ -50,13 +55,16 @@ const (
ErrAddressNotSupported = Error(8) ErrAddressNotSupported = Error(8)
) )
// Auth errors used to return a specific "Auth failed" error
var ErrAuth = errors.New("auth failed")
type User struct { type User struct {
Username string Username string
Password string Password string
} }
// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side. // ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side.
func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) { func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, err error) {
// Read RFC 1928 for request and reply structure and sizes. // Read RFC 1928 for request and reply structure and sizes.
buf := make([]byte, MaxAddrLen) buf := make([]byte, MaxAddrLen)
// read VER, NMETHODS, METHODS // read VER, NMETHODS, METHODS
@ -67,10 +75,64 @@ func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) {
if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil {
return return
} }
// write VER METHOD // write VER METHOD
if _, err = rw.Write([]byte{5, 0}); err != nil { if authenticator != nil {
return if _, err = rw.Write([]byte{5, 2}); err != nil {
return
}
// Get header
header := make([]byte, 2)
if _, err = io.ReadFull(rw, header); err != nil {
return
}
authBuf := make([]byte, MaxAuthLen)
// Get username
userLen := int(header[1])
if userLen <= 0 {
rw.Write([]byte{1, 1})
err = ErrAuth
return
}
if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil {
return
}
user := string(authBuf[:userLen])
// Get password
if _, err = rw.Read(header[:1]); err != nil {
return
}
passLen := int(header[0])
if passLen <= 0 {
rw.Write([]byte{1, 1})
err = ErrAuth
return
}
if _, err = io.ReadFull(rw, authBuf[:passLen]); err != nil {
return
}
pass := string(authBuf[:passLen])
// Verify
if ok := authenticator.Verify(string(user), string(pass)); !ok {
rw.Write([]byte{1, 1})
err = ErrAuth
return
}
// Response auth state
if _, err = rw.Write([]byte{1, 0}); err != nil {
return
}
} else {
if _, err = rw.Write([]byte{5, 0}); err != nil {
return
}
} }
// read VER CMD RSV ATYP DST.ADDR DST.PORT // read VER CMD RSV ATYP DST.ADDR DST.PORT
if _, err = io.ReadFull(rw, buf[:3]); err != nil { if _, err = io.ReadFull(rw, buf[:3]); err != nil {
return return

View File

@ -11,6 +11,7 @@ import (
adapters "github.com/Dreamacro/clash/adapters/outbound" adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
@ -26,6 +27,7 @@ type General struct {
Port int `json:"port"` Port int `json:"port"`
SocksPort int `json:"socks-port"` SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
Mode T.Mode `json:"mode"` Mode T.Mode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"` LogLevel log.LogLevel `json:"log-level"`
@ -56,6 +58,7 @@ type Config struct {
DNS *DNS DNS *DNS
Experimental *Experimental Experimental *Experimental
Rules []C.Rule Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy Proxies map[string]C.Proxy
} }
@ -73,6 +76,7 @@ type rawConfig struct {
Port int `yaml:"port"` Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"` SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"` RedirPort int `yaml:"redir-port"`
Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
Mode T.Mode `yaml:"mode"` Mode T.Mode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"` LogLevel log.LogLevel `yaml:"log-level"`
@ -87,27 +91,43 @@ type rawConfig struct {
Rule []string `yaml:"Rule"` Rule []string `yaml:"Rule"`
} }
// forward compatibility before 1.0
func readRawConfig(path string) ([]byte, error) {
data, err := ioutil.ReadFile(path)
if err == nil && len(data) != 0 {
return data, nil
}
if filepath.Ext(path) != ".yaml" {
return nil, err
}
path = path[:len(path)-5] + ".yml"
return ioutil.ReadFile(path)
}
func readConfig(path string) (*rawConfig, error) { func readConfig(path string) (*rawConfig, error) {
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err return nil, err
} }
data, err := ioutil.ReadFile(path) data, err := readRawConfig(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(data) == 0 { if len(data) == 0 {
return nil, fmt.Errorf("Configuration file %s is empty", C.Path.Config()) return nil, fmt.Errorf("Configuration file %s is empty", path)
} }
// config with some default value // config with some default value
rawConfig := &rawConfig{ rawConfig := &rawConfig{
AllowLan: false, AllowLan: false,
Mode: T.Rule, Mode: T.Rule,
LogLevel: log.INFO, Authentication: []string{},
Rule: []string{}, LogLevel: log.INFO,
Proxy: []map[string]interface{}{}, Rule: []string{},
ProxyGroup: []map[string]interface{}{}, Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{},
Experimental: Experimental{ Experimental: Experimental{
IgnoreResolveFail: true, IgnoreResolveFail: true,
}, },
@ -142,7 +162,7 @@ func Parse(path string) (*Config, error) {
} }
config.Proxies = proxies config.Proxies = proxies
rules, err := parseRules(rawCfg) rules, err := parseRules(rawCfg, proxies)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,6 +174,8 @@ func Parse(path string) (*Config, error) {
} }
config.DNS = dnsCfg config.DNS = dnsCfg
config.Users = parseAuthentication(rawCfg.Authentication)
return config, nil return config, nil
} }
@ -194,6 +216,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
proxies := make(map[string]C.Proxy) proxies := make(map[string]C.Proxy)
proxyList := []string{}
proxiesConfig := cfg.Proxy proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup groupsConfig := cfg.ProxyGroup
@ -201,6 +224,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect()) proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
proxies["REJECT"] = adapters.NewProxy(adapters.NewReject()) proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
proxyList = append(proxyList, "DIRECT", "REJECT")
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
@ -210,7 +234,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
} }
var proxy C.ProxyAdapter var proxy C.ProxyAdapter
err := fmt.Errorf("can't parse") err := fmt.Errorf("cannot parse")
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &adapters.ShadowSocksOption{} ssOption := &adapters.ShadowSocksOption{}
@ -252,6 +276,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
} }
proxies[proxy.Name()] = adapters.NewProxy(proxy) proxies[proxy.Name()] = adapters.NewProxy(proxy)
proxyList = append(proxyList, proxy.Name())
} }
// parse proxy group // parse proxy group
@ -268,7 +293,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
var group C.ProxyAdapter var group C.ProxyAdapter
ps := []C.Proxy{} ps := []C.Proxy{}
err := fmt.Errorf("can't parse") err := fmt.Errorf("cannot parse")
switch groupType { switch groupType {
case "url-test": case "url-test":
urlTestOption := &adapters.URLTestOption{} urlTestOption := &adapters.URLTestOption{}
@ -323,11 +348,12 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
} }
proxies[groupName] = adapters.NewProxy(group) proxies[groupName] = adapters.NewProxy(group)
proxyList = append(proxyList, groupName)
} }
ps := []C.Proxy{} ps := []C.Proxy{}
for _, v := range proxies { for _, v := range proxyList {
ps = append(ps, v) ps = append(ps, proxies[v])
} }
global, _ := adapters.NewSelector("GLOBAL", ps) global, _ := adapters.NewSelector("GLOBAL", ps)
@ -335,7 +361,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
return proxies, nil return proxies, nil
} }
func parseRules(cfg *rawConfig) ([]C.Rule, error) { func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
rules := []C.Rule{} rules := []C.Rule{}
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
@ -357,6 +383,10 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line) return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
} }
if _, ok := proxies[target]; !ok {
return nil, fmt.Errorf("Rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
}
rule = trimArr(rule) rule = trimArr(rule)
var parsed C.Rule var parsed C.Rule
switch rule[0] { switch rule[0] {
@ -445,9 +475,14 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
case "tls": case "tls":
host, err = hostWithDefaultPort(u.Host, "853") host, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS dnsNetType = "tcp-tls" // DNS over TLS
case "https":
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
host = clearURL.String()
dnsNetType = "https" // DNS over HTTPS
default: default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
@ -471,6 +506,7 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
dnsCfg := &DNS{ dnsCfg := &DNS{
Enable: cfg.Enable, Enable: cfg.Enable,
Listen: cfg.Listen, Listen: cfg.Listen,
IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode, EnhancedMode: cfg.EnhancedMode,
} }
var err error var err error
@ -497,3 +533,14 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
return dnsCfg, nil return dnsCfg, nil
} }
func parseAuthentication(rawRecords []string) []auth.AuthUser {
users := make([]auth.AuthUser, 0)
for _, line := range rawRecords {
userData := strings.SplitN(line, ":", 2)
if len(userData) == 2 {
users = append(users, auth.AuthUser{User: userData[0], Pass: userData[1]})
}
}
return users
}

View File

@ -63,7 +63,7 @@ func Init(dir string) error {
} }
} }
// initial config.yml // initial config.yaml
if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) {
log.Info("Can't find config, create an empty file") log.Info("Can't find config, create an empty file")
os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)

View File

@ -42,7 +42,7 @@ func (p *path) HomeDir() string {
} }
func (p *path) Config() string { func (p *path) Config() string {
return P.Join(p.homedir, "config.yml") return P.Join(p.homedir, "config.yaml")
} }
func (p *path) MMDB() string { func (p *path) MMDB() string {

View File

@ -2,269 +2,20 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"errors"
"net"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker"
"github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns" D "github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
) )
var ( type client struct {
globalSessionCache = tls.NewLRUClientSessionCache(64) *D.Client
mmdb *geoip2.Reader
once sync.Once
resolver *Resolver
)
type Resolver struct {
ipv6 bool
mapping bool
fakeip bool
pool *fakeip.Pool
fallback []*nameserver
main []*nameserver
cache *cache.Cache
}
type result struct {
Msg *D.Msg
Error error
}
func isIPRequest(q D.Question) bool {
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
return true
}
return false
}
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 {
return nil, errors.New("should have one question at least")
}
q := m.Question[0]
cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil {
msg = cache.(*D.Msg).Copy()
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
return
}
defer func() {
if msg == nil {
return
}
putMsgToCache(r.cache, q.String(), msg)
if r.mapping {
ips := r.msgToIP(msg)
for _, ip := range ips {
putMsgToCache(r.cache, ip.String(), msg)
}
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
msg, err = r.resolveIP(m)
return
}
msg, err = r.exchange(r.main, m)
return
}
func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err error) {
in := make(chan interface{})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
fast := picker.SelectFast(ctx, in)
wg := sync.WaitGroup{}
wg.Add(len(servers))
for _, server := range servers {
go func(s *nameserver) {
defer wg.Done()
msg, _, err := s.Client.Exchange(m, s.Address)
if err != nil || msg.Rcode != D.RcodeSuccess {
return
}
in <- &result{Msg: msg, Error: err}
}(server)
}
// release in channel
go func() {
wg.Wait()
close(in)
}()
elm, exist := <-fast
if !exist {
return nil, errors.New("All DNS requests failed")
}
resp := elm.(*result)
msg, err = resp.Msg, resp.Error
return
}
func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
msgCh := r.resolve(r.main, m)
if r.fallback == nil {
res := <-msgCh
msg, err = res.Msg, res.Error
return
}
fallbackMsg := r.resolve(r.fallback, m)
res := <-msgCh
if res.Error == nil {
if mmdb == nil {
return nil, errors.New("GeoIP can't use")
}
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
// release channel
go func() { <-fallbackMsg }()
msg = res.Msg
return msg, err
}
}
}
res = <-fallbackMsg
msg, err = res.Msg, res.Error
return
}
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ip = net.ParseIP(host)
if ip != nil {
return ip, nil
}
query := &D.Msg{}
dnsType := D.TypeA
if r.ipv6 {
dnsType = D.TypeAAAA
}
query.SetQuestion(D.Fqdn(host), dnsType)
msg, err := r.Exchange(query)
if err != nil {
return nil, err
}
ips := r.msgToIP(msg)
if len(ips) == 0 {
return nil, errors.New("can't found ip")
}
ip = ips[0]
return
}
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
ips := []net.IP{}
for _, answer := range msg.Answer {
switch ans := answer.(type) {
case *D.AAAA:
ips = append(ips, ans.AAAA)
case *D.A:
ips = append(ips, ans.A)
}
}
return ips
}
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
cache := r.cache.Get(ip.String())
if cache == nil {
return "", false
}
fqdn := cache.(*D.Msg).Question[0].Name
return strings.TrimRight(fqdn, "."), true
}
func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result {
ch := make(chan *result)
go func() {
res, err := r.exchange(client, msg)
ch <- &result{Msg: res, Error: err}
}()
return ch
}
func (r *Resolver) IsMapping() bool {
return r.mapping
}
func (r *Resolver) IsFakeIP() bool {
return r.fakeip
}
type NameServer struct {
Net string
Addr string
}
type nameserver struct {
Client *D.Client
Address string Address string
} }
type Config struct { func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
Main, Fallback []NameServer return c.ExchangeContext(context.Background(), m)
IPv6 bool
EnhancedMode EnhancedMode
Pool *fakeip.Pool
} }
func transform(servers []NameServer) []*nameserver { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
var ret []*nameserver msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address)
for _, s := range servers { return
ret = append(ret, &nameserver{
Client: &D.Client{
Net: s.Net,
TLSConfig: &tls.Config{
ClientSessionCache: globalSessionCache,
},
UDPSize: 4096,
},
Address: s.Addr,
})
}
return ret
}
func New(config Config) *Resolver {
once.Do(func() {
mmdb, _ = geoip2.Open(C.Path.MMDB())
})
r := &Resolver{
main: transform(config.Main),
ipv6: config.IPv6,
cache: cache.New(time.Second * 60),
mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP,
pool: config.Pool,
}
if config.Fallback != nil {
r.fallback = transform(config.Fallback)
}
return r
} }

75
dns/doh.go Normal file
View File

@ -0,0 +1,75 @@
package dns
import (
"bytes"
"context"
"crypto/tls"
"io/ioutil"
"net/http"
D "github.com/miekg/dns"
)
const (
// dotMimeType is the DoH mimetype that should be used.
dotMimeType = "application/dns-message"
// dotPath is the URL path that should be used.
dotPath = "/dns-query"
)
var dohTransport = &http.Transport{
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
}
type dohClient struct {
url string
}
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return dc.ExchangeContext(context.Background(), m)
}
func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
req, err := dc.newRequest(m)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
return dc.doRequest(req)
}
// newRequest returns a new DoH request given a dns.Msg.
func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
buf, err := m.Pack()
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, dc.url+"?bla=foo:443", bytes.NewReader(buf))
if err != nil {
return req, err
}
req.Header.Set("content-type", dotMimeType)
req.Header.Set("accept", dotMimeType)
return req, nil
}
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
client := &http.Client{Transport: dohTransport}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
msg = &D.Msg{}
err = msg.Unpack(buf)
return msg, err
}

32
dns/iputil.go Normal file
View File

@ -0,0 +1,32 @@
package dns
import (
"errors"
"net"
)
var (
errIPNotFound = errors.New("cannot found ip")
)
// ResolveIP with a host, return ip
func ResolveIP(host string) (net.IP, error) {
if DefaultResolver != nil {
if DefaultResolver.ipv6 {
return DefaultResolver.ResolveIP(host)
}
return DefaultResolver.ResolveIPv4(host)
}
ip := net.ParseIP(host)
if ip != nil {
return ip, nil
}
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
return ipAddr.IP, nil
}

299
dns/resolver.go Normal file
View File

@ -0,0 +1,299 @@
package dns
import (
"context"
"crypto/tls"
"errors"
"net"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker"
"github.com/Dreamacro/clash/component/fakeip"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang"
)
var (
// DefaultResolver aim to resolve ip with host
DefaultResolver *Resolver
)
var (
globalSessionCache = tls.NewLRUClientSessionCache(64)
mmdb *geoip2.Reader
once sync.Once
)
type resolver interface {
Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
}
type result struct {
Msg *D.Msg
Error error
}
type Resolver struct {
ipv6 bool
mapping bool
fakeip bool
pool *fakeip.Pool
fallback []resolver
main []resolver
cache *cache.Cache
}
// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ip = net.ParseIP(host)
if ip != nil {
return ip, nil
}
ch := make(chan net.IP)
go func() {
defer close(ch)
ip, err := r.resolveIP(host, D.TypeA)
if err != nil {
return
}
ch <- ip
}()
ip, err = r.resolveIP(host, D.TypeAAAA)
if err == nil {
go func() {
<-ch
}()
return
}
ip, open := <-ch
if !open {
return nil, errIPNotFound
}
return ip, nil
}
// ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) {
ip = net.ParseIP(host)
if ip != nil {
return ip, nil
}
query := &D.Msg{}
query.SetQuestion(D.Fqdn(host), D.TypeA)
msg, err := r.Exchange(query)
if err != nil {
return nil, err
}
ips := r.msgToIP(msg)
if len(ips) == 0 {
return nil, errIPNotFound
}
ip = ips[0]
return
}
// Exchange a batch of dns request, and it use cache
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 {
return nil, errors.New("should have one question at least")
}
q := m.Question[0]
cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil {
msg = cache.(*D.Msg).Copy()
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
return
}
defer func() {
if msg == nil {
return
}
putMsgToCache(r.cache, q.String(), msg)
if r.mapping {
ips := r.msgToIP(msg)
for _, ip := range ips {
putMsgToCache(r.cache, ip.String(), msg)
}
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
msg, err = r.fallbackExchange(m)
return
}
msg, err = r.batchExchange(r.main, m)
return
}
// IPToHost return fake-ip or redir-host mapping host
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
cache := r.cache.Get(ip.String())
if cache == nil {
return "", false
}
fqdn := cache.(*D.Msg).Question[0].Name
return strings.TrimRight(fqdn, "."), true
}
func (r *Resolver) IsMapping() bool {
return r.mapping
}
func (r *Resolver) IsFakeIP() bool {
return r.fakeip
}
func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) {
in := make(chan interface{})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
fast := picker.SelectFast(ctx, in)
wg := sync.WaitGroup{}
wg.Add(len(clients))
for _, r := range clients {
go func(r resolver) {
defer wg.Done()
msg, err := r.ExchangeContext(ctx, m)
if err != nil || msg.Rcode != D.RcodeSuccess {
return
}
in <- msg
}(r)
}
// release in channel
go func() {
wg.Wait()
close(in)
}()
elm, exist := <-fast
if !exist {
return nil, errors.New("All DNS requests failed")
}
msg = elm.(*D.Msg)
return
}
func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
msgCh := r.asyncExchange(r.main, m)
if r.fallback == nil {
res := <-msgCh
msg, err = res.Msg, res.Error
return
}
fallbackMsg := r.asyncExchange(r.fallback, m)
res := <-msgCh
if res.Error == nil {
if mmdb == nil {
return nil, errors.New("GeoIP cannot use")
}
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
// release channel
go func() { <-fallbackMsg }()
msg = res.Msg
return msg, err
}
}
}
res = <-fallbackMsg
msg, err = res.Msg, res.Error
return
}
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
query := &D.Msg{}
query.SetQuestion(D.Fqdn(host), dnsType)
msg, err := r.Exchange(query)
if err != nil {
return nil, err
}
ips := r.msgToIP(msg)
if len(ips) == 0 {
return nil, errIPNotFound
}
ip = ips[0]
return
}
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
ips := []net.IP{}
for _, answer := range msg.Answer {
switch ans := answer.(type) {
case *D.AAAA:
ips = append(ips, ans.AAAA)
case *D.A:
ips = append(ips, ans.A)
}
}
return ips
}
func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
ch := make(chan *result)
go func() {
res, err := r.batchExchange(client, msg)
ch <- &result{Msg: res, Error: err}
}()
return ch
}
type NameServer struct {
Net string
Addr string
}
type Config struct {
Main, Fallback []NameServer
IPv6 bool
EnhancedMode EnhancedMode
Pool *fakeip.Pool
}
func New(config Config) *Resolver {
once.Do(func() {
mmdb, _ = geoip2.Open(C.Path.MMDB())
})
r := &Resolver{
ipv6: config.IPv6,
main: transform(config.Main),
cache: cache.New(time.Second * 60),
mapping: config.EnhancedMode == MAPPING,
fakeip: config.EnhancedMode == FAKEIP,
pool: config.Pool,
}
if len(config.Fallback) != 0 {
r.fallback = transform(config.Fallback)
}
return r
}

View File

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"time"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -58,10 +57,10 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
q := r.Question[0] q := r.Question[0]
cache, expireTime := s.r.cache.GetWithExpire("fakeip:" + q.String()) cache := s.r.cache.Get("fakeip:" + q.String())
if cache != nil { if cache != nil {
msg = cache.(*D.Msg).Copy() msg = cache.(*D.Msg).Copy()
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) setMsgTTL(msg, 1)
return return
} }
@ -73,6 +72,8 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) {
putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg) putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg)
putMsgToCache(s.r.cache, ip.String(), msg) putMsgToCache(s.r.cache, ip.String(), msg)
setMsgTTL(msg, 1)
}() }()
rr := &D.A{} rr := &D.A{}
@ -96,6 +97,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
if server.Server != nil { if server.Server != nil {
server.Shutdown() server.Shutdown()
address = ""
} }
_, port, err := net.SplitHostPort(addr) _, port, err := net.SplitHostPort(addr)

View File

@ -1,6 +1,7 @@
package dns package dns
import ( import (
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"time" "time"
@ -107,3 +108,34 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
extra.Header().Ttl = ttl extra.Header().Ttl = ttl
} }
} }
func isIPRequest(q D.Question) bool {
if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) {
return true
}
return false
}
func transform(servers []NameServer) []resolver {
ret := []resolver{}
for _, s := range servers {
if s.Net == "https" {
ret = append(ret, &dohClient{url: s.Addr})
continue
}
ret = append(ret, &client{
Client: &D.Client{
Net: s.Net,
TLSConfig: &tls.Config{
ClientSessionCache: globalSessionCache,
// alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6
NextProtos: []string{"dns"},
},
UDPSize: 4096,
},
Address: s.Addr,
})
}
return ret
}

10
go.mod
View File

@ -8,13 +8,13 @@ require (
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.2.0+incompatible github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.0 github.com/gorilla/websocket v1.4.0
github.com/miekg/dns v1.1.9 github.com/miekg/dns v1.1.14
github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/geoip2-golang v1.3.0
github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/oschwald/maxminddb-golang v1.3.1 // indirect
github.com/sirupsen/logrus v1.4.1 github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/eapache/channels.v1 v1.1.0
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2
) )

23
go.sum
View File

@ -18,16 +18,16 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.9 h1:OIdC9wT96RzuZMf2PfKRhFgsStHUUBZLM/lo1LqiM9E= github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA=
github.com/miekg/dns v1.1.9/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8=
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.1 h1:kPc5+ieL5CC/Zn0IaXJPxDFlUxKTQEU8QBTtmfQDAIo=
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= github.com/oschwald/maxminddb-golang v1.3.1/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -36,12 +36,13 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/Le
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -1,11 +1,13 @@
package executor package executor
import ( import (
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
P "github.com/Dreamacro/clash/proxy" P "github.com/Dreamacro/clash/proxy"
authStore "github.com/Dreamacro/clash/proxy/auth"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
) )
@ -21,6 +23,7 @@ func ParseWithPath(path string) (*config.Config, error) {
// ApplyConfig dispatch configure to all parts // ApplyConfig dispatch configure to all parts
func ApplyConfig(cfg *config.Config, force bool) { func ApplyConfig(cfg *config.Config, force bool) {
updateUsers(cfg.Users)
if force { if force {
updateGeneral(cfg.General) updateGeneral(cfg.General)
} }
@ -32,14 +35,22 @@ func ApplyConfig(cfg *config.Config, force bool) {
func GetGeneral() *config.General { func GetGeneral() *config.General {
ports := P.GetPorts() ports := P.GetPorts()
return &config.General{ authenticator := []string{}
Port: ports.Port, if auth := authStore.Authenticator(); auth != nil {
SocksPort: ports.SocksPort, authenticator = auth.Users()
RedirPort: ports.RedirPort,
AllowLan: P.AllowLan(),
Mode: T.Instance().Mode(),
LogLevel: log.Level(),
} }
general := &config.General{
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
Authentication: authenticator,
AllowLan: P.AllowLan(),
Mode: T.Instance().Mode(),
LogLevel: log.Level(),
}
return general
} }
func updateExperimental(c *config.Experimental) { func updateExperimental(c *config.Experimental) {
@ -48,7 +59,7 @@ func updateExperimental(c *config.Experimental) {
func updateDNS(c *config.DNS) { func updateDNS(c *config.DNS) {
if c.Enable == false { if c.Enable == false {
T.Instance().SetResolver(nil) dns.DefaultResolver = nil
dns.ReCreateServer("", nil) dns.ReCreateServer("", nil)
return return
} }
@ -59,12 +70,15 @@ func updateDNS(c *config.DNS) {
EnhancedMode: c.EnhancedMode, EnhancedMode: c.EnhancedMode,
Pool: c.FakeIPRange, Pool: c.FakeIPRange,
}) })
T.Instance().SetResolver(r) dns.DefaultResolver = r
if err := dns.ReCreateServer(c.Listen, r); err != nil { if err := dns.ReCreateServer(c.Listen, r); err != nil {
log.Errorln("Start DNS server error: %s", err.Error()) log.Errorln("Start DNS server error: %s", err.Error())
return return
} }
log.Infoln("DNS server listening at: %s", c.Listen)
if c.Listen != "" {
log.Infoln("DNS server listening at: %s", c.Listen)
}
} }
func updateProxies(proxies map[string]C.Proxy) { func updateProxies(proxies map[string]C.Proxy) {
@ -90,6 +104,7 @@ func updateGeneral(general *config.General) {
allowLan := general.AllowLan allowLan := general.AllowLan
P.SetAllowLan(allowLan) P.SetAllowLan(allowLan)
if err := P.ReCreateHTTP(general.Port); err != nil { if err := P.ReCreateHTTP(general.Port); err != nil {
log.Errorln("Start HTTP server error: %s", err.Error()) log.Errorln("Start HTTP server error: %s", err.Error())
} }
@ -102,3 +117,11 @@ func updateGeneral(general *config.General) {
log.Errorln("Start Redir server error: %s", err.Error()) log.Errorln("Start Redir server error: %s", err.Error())
} }
} }
func updateUsers(users []auth.AuthUser) {
authenticator := auth.NewAuthenticator(users)
authStore.SetAuthenticator(authenticator)
if authenticator != nil {
log.Infoln("Authentication of local server updated")
}
}

View File

@ -2,6 +2,7 @@ package log
import ( import (
"fmt" "fmt"
"os"
"github.com/Dreamacro/clash/common/observable" "github.com/Dreamacro/clash/common/observable"
@ -15,6 +16,7 @@ var (
) )
func init() { func init() {
log.SetOutput(os.Stdout)
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} }

17
proxy/auth/auth.go Normal file
View File

@ -0,0 +1,17 @@
package auth
import (
"github.com/Dreamacro/clash/component/auth"
)
var (
authenticator auth.Authenticator
)
func Authenticator() auth.Authenticator {
return authenticator
}
func SetAuthenticator(au auth.Authenticator) {
authenticator = au
}

View File

@ -2,11 +2,17 @@ package http
import ( import (
"bufio" "bufio"
"encoding/base64"
"net" "net"
"net/http" "net/http"
"strings"
"time"
adapters "github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
authStore "github.com/Dreamacro/clash/proxy/auth"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
@ -18,6 +24,7 @@ type HttpListener struct {
net.Listener net.Listener
address string address string
closed bool closed bool
cache *cache.Cache
} }
func NewHttpProxy(addr string) (*HttpListener, error) { func NewHttpProxy(addr string) (*HttpListener, error) {
@ -25,10 +32,11 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
hl := &HttpListener{l, addr, false} hl := &HttpListener{l, addr, false, cache.New(30 * time.Second)}
go func() { go func() {
log.Infoln("HTTP proxy listening at: %s", addr) log.Infoln("HTTP proxy listening at: %s", addr)
for { for {
c, err := hl.Accept() c, err := hl.Accept()
if err != nil { if err != nil {
@ -37,7 +45,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) {
} }
continue continue
} }
go handleConn(c) go handleConn(c, hl.cache)
} }
}() }()
@ -53,7 +61,19 @@ func (l *HttpListener) Address() string {
return l.address return l.address
} }
func handleConn(conn net.Conn) { func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
if result := cache.Get(loginStr); result != nil {
ret = result.(bool)
}
loginData, err := base64.StdEncoding.DecodeString(loginStr)
login := strings.Split(string(loginData), ":")
ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1])
cache.Put(loginStr, ret, time.Minute)
return
}
func handleConn(conn net.Conn, cache *cache.Cache) {
br := bufio.NewReader(conn) br := bufio.NewReader(conn)
request, err := http.ReadRequest(br) request, err := http.ReadRequest(br)
if err != nil || request.URL.Host == "" { if err != nil || request.URL.Host == "" {
@ -61,6 +81,20 @@ func handleConn(conn net.Conn) {
return return
} }
authenticator := authStore.Authenticator()
if authenticator != nil {
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
_, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
conn.Close()
return
} else if !canActivate(authStrings[1], authenticator, cache) {
conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n"))
log.Infoln("Auth failed from %s", conn.RemoteAddr().String())
conn.Close()
return
}
}
if request.Method == http.MethodConnect { if request.Method == http.MethodConnect {
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil { if err != nil {

View File

@ -7,6 +7,7 @@ import (
"github.com/Dreamacro/clash/component/socks5" "github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
authStore "github.com/Dreamacro/clash/proxy/auth"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
@ -54,7 +55,7 @@ func (l *SockListener) Address() string {
} }
func handleSocks(conn net.Conn) { func handleSocks(conn net.Conn) {
target, command, err := socks5.ServerHandshake(conn) target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator())
if err != nil { if err != nil {
conn.Close() conn.Close()
return return

View File

@ -26,7 +26,6 @@ type Tunnel struct {
proxies map[string]C.Proxy proxies map[string]C.Proxy
configMux *sync.RWMutex configMux *sync.RWMutex
traffic *C.Traffic traffic *C.Traffic
resolver *dns.Resolver
// experimental features // experimental features
ignoreResolveFail bool ignoreResolveFail bool
@ -86,15 +85,6 @@ func (t *Tunnel) SetMode(mode Mode) {
t.mode = mode t.mode = mode
} }
// SetResolver change the resolver of tunnel
func (t *Tunnel) SetResolver(resolver *dns.Resolver) {
t.resolver = resolver
}
func (t *Tunnel) hasResolver() bool {
return t.resolver != nil
}
func (t *Tunnel) process() { func (t *Tunnel) process() {
queue := t.queue.Out() queue := t.queue.Out()
for { for {
@ -105,20 +95,11 @@ func (t *Tunnel) process() {
} }
func (t *Tunnel) resolveIP(host string) (net.IP, error) { func (t *Tunnel) resolveIP(host string) (net.IP, error) {
if t.resolver == nil { return dns.ResolveIP(host)
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
return ipAddr.IP, nil
}
return t.resolver.ResolveIP(host)
} }
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil
} }
func (t *Tunnel) handleConn(localConn C.ServerAdapter) { func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
@ -132,11 +113,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
// preprocess enhanced-mode metadata // preprocess enhanced-mode metadata
if t.needLookupIP(metadata) { if t.needLookupIP(metadata) {
host, exist := t.resolver.IPToHost(*metadata.DstIP) host, exist := dns.DefaultResolver.IPToHost(*metadata.DstIP)
if exist { if exist {
metadata.Host = host metadata.Host = host
metadata.AddrType = C.AtypDomainName metadata.AddrType = C.AtypDomainName
if t.resolver.IsFakeIP() { if dns.DefaultResolver.IsFakeIP() {
metadata.DstIP = nil metadata.DstIP = nil
} }
} }
@ -159,10 +140,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
pc, addr, err := proxy.DialUDP(metadata) pc, addr, err := proxy.DialUDP(metadata)
defer pc.Close()
if err != nil { if err != nil {
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error())
return
} }
defer pc.Close()
t.handleUDPOverTCP(localConn, pc, addr) t.handleUDPOverTCP(localConn, pc, addr)
return return