Compare commits

..

32 Commits

Author SHA1 Message Date
744728cb84 Chore: update README.md 2019-03-30 14:20:04 +08:00
2036f8cb7a Fix: IP-CIDR invalid payload crash 2019-03-30 14:11:59 +08:00
531f487629 Fix: incorrect mutex in speedTest (#153) 2019-03-29 10:27:26 +08:00
18f885a92a Feature: add interval url test for load-balance 2019-03-28 19:00:41 +08:00
d3b280a7e5 Fix: reuse Current.HomeDir until go 1.13 release 2019-03-28 18:20:19 +08:00
d1f6886558 Style: use atomic CompareAndSwap (#151) 2019-03-26 23:48:03 +08:00
791d72e05b Fix: crash when key value is nil 2019-03-25 20:42:20 +08:00
14600a8170 Fix: dns hot reload no effect 2019-03-23 19:41:41 +08:00
bb267e4a1f Feature: add version command (#148) 2019-03-23 19:24:26 +08:00
f99da37168 Fix: fallback & url-test lose efficacy 2019-03-23 16:29:27 +08:00
7a9d986ff3 Feature: add delay history and improve url-test behavior 2019-03-17 14:52:39 +08:00
63446da5fa Fix: expand UDPSize to avoid resolving error (#139) 2019-03-17 14:08:15 +08:00
acf55a7f64 Improve: Dial would reset proxy alive status 2019-03-16 00:43:16 +08:00
8c608f5d7a Feature: add custom headers support in v2ray-plugin (#137) 2019-03-15 09:43:46 +08:00
7f0c7d7802 Fix: should not return extra ip in msgToIP 2019-03-03 17:23:59 +08:00
7683271fe6 Style: rename Generator with Dial 2019-03-03 11:59:07 +08:00
23bb01a4df Fix: http request keepAlive with right http header 2019-03-03 11:51:15 +08:00
0011c7acfe Improve: support tcp dns server & return an error when parsing nameserver (#127) 2019-03-01 00:52:30 +08:00
d75f9ff783 Migration: go 1.12 2019-02-27 01:02:43 +08:00
815e80f720 Fix: dns use Extra records 2019-02-24 01:26:51 +08:00
ca5399a16e Fix: dns cache behavior 2019-02-23 20:31:59 +08:00
04927229ff Fix: disconnect normal proxy request 2019-02-21 16:16:49 +08:00
c0bd82d62b Chore: rename final 2019-02-18 21:53:57 +08:00
5c8bb24121 Fix: crash when directly request proxy server 2019-02-18 20:14:18 +08:00
575720e0cc Fix: windows 386 build 2019-02-15 23:43:28 +08:00
e7997a035b Chore: update README.md 2019-02-15 22:01:11 +08:00
287ad5bc53 Fix: vmess handshake block (#117) 2019-02-15 21:55:15 +08:00
c295c5e412 Feature: add load-balance group 2019-02-15 14:25:20 +08:00
8636a4f589 Fix: return 502 in http outbound (#116) 2019-02-14 11:37:47 +08:00
7a0717830c Fix: api invalid returning 2019-02-13 23:45:43 +08:00
5920b05752 Fix: crash when ip is nil 2019-02-12 12:29:33 +08:00
26a87f9d34 Fix: redir-host mode crash 2019-02-11 17:20:42 +08:00
40 changed files with 822 additions and 273 deletions

View File

@ -1,7 +1,7 @@
language: go language: go
sudo: false sudo: false
go: go:
- "1.11" - '1.12'
install: install:
- "go mod download" - "go mod download"
env: env:

View File

@ -1,6 +1,10 @@
NAME=clash NAME=clash
BINDIR=bin BINDIR=bin
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' VERSION=$(shell git describe --tags --long --dirty || echo "unkown version")
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s'
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64 \ darwin-amd64 \
@ -67,7 +71,7 @@ freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386: windows-386:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
@ -77,10 +81,10 @@ zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
$(gz_releases): %.gz : % $(gz_releases): %.gz : %
chmod +x $(BINDIR)/$(NAME)-$(basename $@) chmod +x $(BINDIR)/$(NAME)-$(basename $@)
gzip -f $(BINDIR)/$(NAME)-$(basename $@) tar -czf $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-$(basename $@)
$(zip_releases): %.zip : % $(zip_releases): %.zip : %
zip -m -j $(BINDIR)/$(NAME)-$(basename $@).zip $(BINDIR)/$(NAME)-$(basename $@).exe zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)

View File

@ -36,7 +36,13 @@ go get -u -v github.com/Dreamacro/clash
Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases) Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases)
Requires Go >= 1.11. Requires Go >= 1.12.
Checkout Clash version:
```sh
clash -v
```
## Daemon ## Daemon
@ -108,7 +114,7 @@ external-controller: 127.0.0.1:9090
# - 114.114.114.114 # - 114.114.114.114
# - tls://dns.rubyfish.cn:853 # dns over tls # - tls://dns.rubyfish.cn:853 # dns over tls
# 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
# - 8.8.8.8 # - tcp://1.1.1.1
Proxy: Proxy:
@ -143,6 +149,8 @@ Proxy:
# skip-cert-verify: true # skip-cert-verify: true
# host: bing.com # host: bing.com
# path: "/" # path: "/"
# headers:
# custom: value
# vmess # vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none # cipher support auto/aes-128-gcm/chacha20-poly1305/none
@ -181,21 +189,24 @@ Proxy Group:
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } - { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# load-balance: The request of the same eTLD will be dial on the same proxy.
- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# select is used for selecting proxy or proxy group # select is used for selecting proxy or proxy group
# you can use RESTful API to switch proxy, is recommended for use in GUI. # you can use RESTful API to switch proxy, is recommended for use in GUI.
- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] } - { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] }
Rule: Rule:
- DOMAIN-SUFFIX,google.com,Proxy - DOMAIN-SUFFIX,google.com,auto
- DOMAIN-KEYWORD,google,Proxy - DOMAIN-KEYWORD,google,auto
- DOMAIN,google.com,Proxy - DOMAIN,google.com,auto
- DOMAIN-SUFFIX,ad.com,REJECT - DOMAIN-SUFFIX,ad.com,REJECT
- IP-CIDR,127.0.0.0/8,DIRECT - IP-CIDR,127.0.0.0/8,DIRECT
- SOURCE-IP-CIDR,192.168.1.201/32,DIRECT - SOURCE-IP-CIDR,192.168.1.201/32,DIRECT
- GEOIP,CN,DIRECT - GEOIP,CN,DIRECT
# FINAL would remove after prerelease # FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
- MATCH,Proxy - MATCH,auto
``` ```
## Thanks ## Thanks

View File

@ -2,7 +2,11 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -19,8 +23,116 @@ func (b *Base) Type() C.AdapterType {
return b.tp return b.tp
} }
func (b *Base) Destroy() {}
func (b *Base) MarshalJSON() ([]byte, error) { func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{ return json.Marshal(map[string]string{
"type": b.Type().String(), "type": b.Type().String(),
}) })
} }
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
alive bool
}
func (p *Proxy) Alive() bool {
return p.alive
}
func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) {
conn, err := p.ProxyAdapter.Dial(metadata)
if err != nil {
p.alive = false
}
return conn, err
}
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
}
return histories
}
// LastDelay return last history record. if proxy is not alive, return the max value of int16.
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive {
return max
}
head := p.history.First()
if head == nil {
return max
}
history := head.(C.DelayHistory)
if history.Delay == 0 {
return max
}
return history.Delay
}
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]interface{}{}
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
func (p *Proxy) URLTest(url string) (t uint16, err error) {
defer func() {
p.alive = err == nil
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > 10 {
p.history.Pop()
}
}()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.Dial(&addr)
if err != nil {
return
}
defer instance.Close()
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Get(url)
if err != nil {
return
}
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), true}
}

View File

@ -10,7 +10,7 @@ type Direct struct {
*Base *Base
} }
func (d *Direct) Generator(metadata *C.Metadata) (net.Conn, error) { func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) {
address := net.JoinHostPort(metadata.Host, metadata.Port) address := net.JoinHostPort(metadata.Host, metadata.Port)
if metadata.IP != nil { if metadata.IP != nil {
address = net.JoinHostPort(metadata.IP.String(), metadata.Port) address = net.JoinHostPort(metadata.IP.String(), metadata.Port)

View File

@ -10,14 +10,9 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type proxy struct {
RawProxy C.Proxy
Valid bool
}
type Fallback struct { type Fallback struct {
*Base *Base
proxies []*proxy proxies []C.Proxy
rawURL string rawURL string
interval time.Duration interval time.Duration
done chan struct{} done chan struct{}
@ -31,36 +26,19 @@ type FallbackOption struct {
} }
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
_, proxy := f.findNextValidProxy(0) proxy := f.findAliveProxy()
if proxy != nil { return proxy.Name()
return proxy.RawProxy.Name()
}
return f.proxies[0].RawProxy.Name()
} }
func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) { func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) {
idx := 0 proxy := f.findAliveProxy()
var proxy *proxy return proxy.Dial(metadata)
for {
idx, proxy = f.findNextValidProxy(idx)
if proxy == nil {
break
}
adapter, err := proxy.RawProxy.Generator(metadata)
if err != nil {
proxy.Valid = false
idx++
continue
}
return adapter, err
}
return f.proxies[0].RawProxy.Generator(metadata)
} }
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string var all []string
for _, proxy := range f.proxies { for _, proxy := range f.proxies {
all = append(all, proxy.RawProxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"type": f.Type().String(), "type": f.Type().String(),
@ -69,7 +47,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
}) })
} }
func (f *Fallback) Close() { func (f *Fallback) Destroy() {
f.done <- struct{}{} f.done <- struct{}{}
} }
@ -87,13 +65,13 @@ Loop:
} }
} }
func (f *Fallback) findNextValidProxy(start int) (int, *proxy) { func (f *Fallback) findAliveProxy() C.Proxy {
for i := start; i < len(f.proxies); i++ { for _, proxy := range f.proxies {
if f.proxies[i].Valid { if proxy.Alive() {
return i, f.proxies[i] return proxy
} }
} }
return -1, nil return f.proxies[0]
} }
func (f *Fallback) validTest() { func (f *Fallback) validTest() {
@ -101,9 +79,8 @@ func (f *Fallback) validTest() {
wg.Add(len(f.proxies)) wg.Add(len(f.proxies))
for _, p := range f.proxies { for _, p := range f.proxies {
go func(p *proxy) { go func(p C.Proxy) {
_, err := DelayTest(p.RawProxy, f.rawURL) p.URLTest(f.rawURL)
p.Valid = err == nil
wg.Done() wg.Done()
}(p) }(p)
} }
@ -122,20 +99,13 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
} }
interval := time.Duration(option.Interval) * time.Second interval := time.Duration(option.Interval) * time.Second
warpperProxies := make([]*proxy, len(proxies))
for idx := range proxies {
warpperProxies[idx] = &proxy{
RawProxy: proxies[idx],
Valid: true,
}
}
Fallback := &Fallback{ Fallback := &Fallback{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Fallback, tp: C.Fallback,
}, },
proxies: warpperProxies, proxies: proxies,
rawURL: option.URL, rawURL: option.URL,
interval: interval, interval: interval,
done: make(chan struct{}), done: make(chan struct{}),

View File

@ -35,7 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (h *Http) Generator(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 := net.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)
@ -58,9 +58,9 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
var buf bytes.Buffer var buf bytes.Buffer
var err error var err error
addr := net.JoinHostPort(metadata.Host, metadata.Port) addr := net.JoinHostPort(metadata.String(), metadata.Port)
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
buf.WriteString("Host: " + metadata.Host + "\r\n") buf.WriteString("Host: " + metadata.String() + "\r\n")
buf.WriteString("Proxy-Connection: Keep-Alive\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
if h.user != "" && h.pass != "" { if h.user != "" && h.pass != "" {
@ -101,8 +101,6 @@ func NewHttp(option HttpOption) *Http {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(), ClientSessionCache: getClientSessionCache(),
MinVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS12,
ServerName: option.Server, ServerName: option.Server,
} }
} }

View File

@ -0,0 +1,140 @@
package adapters
import (
"encoding/json"
"errors"
"net"
"sync"
"time"
"github.com/Dreamacro/clash/common/murmur3"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/publicsuffix"
)
type LoadBalance struct {
*Base
proxies []C.Proxy
maxRetry int
rawURL string
interval time.Duration
done chan struct{}
}
func getKey(metadata *C.Metadata) string {
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if metadata.IP == nil {
return ""
}
return metadata.IP.String()
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) {
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := lb.proxies[idx]
if proxy.Alive() {
return proxy.Dial(metadata)
}
}
return lb.proxies[0].Dial(metadata)
}
func (lb *LoadBalance) Destroy() {
lb.done <- struct{}{}
}
func (lb *LoadBalance) validTest() {
wg := sync.WaitGroup{}
wg.Add(len(lb.proxies))
for _, p := range lb.proxies {
go func(p C.Proxy) {
p.URLTest(lb.rawURL)
wg.Done()
}(p)
}
wg.Wait()
}
func (lb *LoadBalance) loop() {
tick := time.NewTicker(lb.interval)
go lb.validTest()
Loop:
for {
select {
case <-tick.C:
go lb.validTest()
case <-lb.done:
break Loop
}
}
}
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.proxies {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": lb.Type().String(),
"all": all,
})
}
type LoadBalanceOption struct {
Name string `proxy:"name"`
Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
}
func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) {
if len(proxies) == 0 {
return nil, errors.New("Provide at least one proxy")
}
interval := time.Duration(option.Interval) * time.Second
lb := &LoadBalance{
Base: &Base{
name: option.Name,
tp: C.LoadBalance,
},
proxies: proxies,
maxRetry: 3,
rawURL: option.URL,
interval: interval,
done: make(chan struct{}),
}
go lb.loop()
return lb, nil
}

View File

@ -12,7 +12,7 @@ type Reject struct {
*Base *Base
} }
func (r *Reject) Generator(metadata *C.Metadata) (net.Conn, error) { func (r *Reject) Dial(metadata *C.Metadata) (net.Conn, error) {
return &NopConn{}, nil return &NopConn{}, nil
} }

View File

@ -20,8 +20,8 @@ type SelectorOption struct {
Proxies []string `proxy:"proxies"` Proxies []string `proxy:"proxies"`
} }
func (s *Selector) Generator(metadata *C.Metadata) (net.Conn, error) { func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) {
return s.selected.Generator(metadata) return s.selected.Dial(metadata)
} }
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {

View File

@ -48,14 +48,15 @@ type simpleObfsOption struct {
} }
type v2rayObfsOption struct { type v2rayObfsOption struct {
Mode string `obfs:"mode"` Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"` Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"` TLS bool `obfs:"tls,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
} }
func (ss *ShadowSocks) Generator(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 := net.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())
@ -131,10 +132,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
ClientSessionCache: getClientSessionCache(), ClientSessionCache: getClientSessionCache(),
} }
} }
wsOption = &v2rayObfs.WebsocketOption{ wsOption = &v2rayObfs.WebsocketOption{
Host: opts.Host, Host: opts.Host,
Path: opts.Path, Path: opts.Path,
Headers: opts.Headers,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
} }
} }

View File

@ -34,7 +34,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Socks5) Generator(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 := net.DialTimeout("tcp", ss.addr, tcpTimeout)
if err == nil && ss.tls { if err == nil && ss.tls {
@ -118,8 +118,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(), ClientSessionCache: getClientSessionCache(),
MinVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS12,
ServerName: option.Server, ServerName: option.Server,
} }
} }

View File

@ -1,6 +1,7 @@
package adapters package adapters
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"net" "net"
@ -9,6 +10,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/Dreamacro/clash/common/picker"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -33,10 +35,10 @@ func (u *URLTest) Now() string {
return u.fast.Name() return u.fast.Name()
} }
func (u *URLTest) Generator(metadata *C.Metadata) (net.Conn, error) { func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
a, err := u.fast.Generator(metadata) a, err := u.fast.Dial(metadata)
if err != nil { if err != nil {
go u.speedTest() u.fallback()
} }
return a, err return a, err
} }
@ -54,7 +56,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
}) })
} }
func (u *URLTest) Close() { func (u *URLTest) Destroy() {
u.done <- struct{}{} u.done <- struct{}{}
} }
@ -72,8 +74,25 @@ Loop:
} }
} }
func (u *URLTest) fallback() {
fast := u.proxies[0]
min := fast.LastDelay()
for _, proxy := range u.proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
u.fast = fast
}
func (u *URLTest) speedTest() { func (u *URLTest) speedTest() {
if atomic.AddInt32(&u.once, 1) != 1 { if !atomic.CompareAndSwapInt32(&u.once, 0, 1) {
return return
} }
defer atomic.StoreInt32(&u.once, 0) defer atomic.StoreInt32(&u.once, 0)
@ -81,12 +100,12 @@ func (u *URLTest) speedTest() {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(u.proxies)) wg.Add(len(u.proxies))
c := make(chan interface{}) c := make(chan interface{})
fast := selectFast(c) fast := picker.SelectFast(context.Background(), c)
timer := time.NewTimer(u.interval) timer := time.NewTimer(u.interval)
for _, p := range u.proxies { for _, p := range u.proxies {
go func(p C.Proxy) { go func(p C.Proxy) {
_, err := DelayTest(p, u.rawURL) _, err := p.URLTest(u.rawURL)
if err == nil { if err == nil {
c <- p c <- p
} }

View File

@ -4,7 +4,6 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http"
"net/url" "net/url"
"sync" "sync"
"time" "time"
@ -21,39 +20,6 @@ var (
once sync.Once once sync.Once
) )
// DelayTest get the delay for the specified URL
func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := proxy.Generator(&addr)
if err != nil {
return
}
defer instance.Close()
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Get(url)
if err != nil {
return
}
resp.Body.Close()
t = int16(time.Since(start) / time.Millisecond)
return
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) { func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL) u, err := url.Parse(rawURL)
if err != nil { if err != nil {
@ -81,21 +47,6 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
return return
} }
func selectFast(in chan interface{}) chan interface{} {
out := make(chan interface{})
go func() {
p, open := <-in
if open {
out <- p
}
close(out)
for range in {
}
}()
return out
}
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) tcp.SetKeepAlive(true)

View File

@ -30,7 +30,7 @@ type VmessOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (v *Vmess) Generator(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 := net.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)

50
common/murmur3/murmur.go Normal file
View File

@ -0,0 +1,50 @@
package murmur3
type bmixer interface {
bmix(p []byte) (tail []byte)
Size() (n int)
reset()
}
type digest struct {
clen int // Digested input cumulative length.
tail []byte // 0 to Size()-1 bytes view of `buf'.
buf [16]byte // Expected (but not required) to be Size() large.
seed uint32 // Seed for initializing the hash.
bmixer
}
func (d *digest) BlockSize() int { return 1 }
func (d *digest) Write(p []byte) (n int, err error) {
n = len(p)
d.clen += n
if len(d.tail) > 0 {
// Stick back pending bytes.
nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1].
if nfree < len(p) {
// One full block can be formed.
block := append(d.tail, p[:nfree]...)
p = p[nfree:]
_ = d.bmix(block) // No tail.
} else {
// Tail's buf is large enough to prevent reallocs.
p = append(d.tail, p...)
}
}
d.tail = d.bmix(p)
// Keep own copy of the 0 to Size()-1 pending bytes.
nn := copy(d.buf[:], d.tail)
d.tail = d.buf[:nn]
return n, nil
}
func (d *digest) Reset() {
d.clen = 0
d.tail = nil
d.bmixer.reset()
}

149
common/murmur3/murmur32.go Normal file
View File

@ -0,0 +1,149 @@
package murmur3
// https://github.com/spaolacci/murmur3/blob/master/murmur32.go
import (
"hash"
"unsafe"
)
// Make sure interfaces are correctly implemented.
var (
_ hash.Hash32 = new(digest32)
_ bmixer = new(digest32)
)
const (
c1_32 uint32 = 0xcc9e2d51
c2_32 uint32 = 0x1b873593
)
// digest32 represents a partial evaluation of a 32 bites hash.
type digest32 struct {
digest
h1 uint32 // Unfinalized running hash.
}
// New32 returns new 32-bit hasher
func New32() hash.Hash32 { return New32WithSeed(0) }
// New32WithSeed returns new 32-bit hasher set with explicit seed value
func New32WithSeed(seed uint32) hash.Hash32 {
d := new(digest32)
d.seed = seed
d.bmixer = d
d.Reset()
return d
}
func (d *digest32) Size() int { return 4 }
func (d *digest32) reset() { d.h1 = d.seed }
func (d *digest32) Sum(b []byte) []byte {
h := d.Sum32()
return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h))
}
// Digest as many blocks as possible.
func (d *digest32) bmix(p []byte) (tail []byte) {
h1 := d.h1
nblocks := len(p) / 4
for i := 0; i < nblocks; i++ {
k1 := *(*uint32)(unsafe.Pointer(&p[i*4]))
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
h1 = h1*4 + h1 + 0xe6546b64
}
d.h1 = h1
return p[nblocks*d.Size():]
}
func (d *digest32) Sum32() (h1 uint32) {
h1 = d.h1
var k1 uint32
switch len(d.tail) & 3 {
case 3:
k1 ^= uint32(d.tail[2]) << 16
fallthrough
case 2:
k1 ^= uint32(d.tail[1]) << 8
fallthrough
case 1:
k1 ^= uint32(d.tail[0])
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
}
h1 ^= uint32(d.clen)
h1 ^= h1 >> 16
h1 *= 0x85ebca6b
h1 ^= h1 >> 13
h1 *= 0xc2b2ae35
h1 ^= h1 >> 16
return h1
}
func Sum32(data []byte) uint32 { return Sum32WithSeed(data, 0) }
func Sum32WithSeed(data []byte, seed uint32) uint32 {
h1 := seed
nblocks := len(data) / 4
var p uintptr
if len(data) > 0 {
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 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
h1 = h1*4 + h1 + 0xe6546b64
}
tail := data[nblocks*4:]
var k1 uint32
switch len(tail) & 3 {
case 3:
k1 ^= uint32(tail[2]) << 16
fallthrough
case 2:
k1 ^= uint32(tail[1]) << 8
fallthrough
case 1:
k1 ^= uint32(tail[0])
k1 *= c1_32
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
k1 *= c2_32
h1 ^= k1
}
h1 ^= uint32(len(data))
h1 ^= h1 >> 16
h1 *= 0x85ebca6b
h1 ^= h1 >> 13
h1 *= 0xc2b2ae35
h1 ^= h1 >> 16
return h1
}

71
common/queue/queue.go Normal file
View File

@ -0,0 +1,71 @@
package queue
import (
"sync"
)
// Queue is a simple concurrent safe queue
type Queue struct {
items []interface{}
lock sync.RWMutex
}
// Put add the item to the queue.
func (q *Queue) Put(items ...interface{}) {
if len(items) == 0 {
return
}
q.lock.Lock()
q.items = append(q.items, items...)
q.lock.Unlock()
}
// Pop returns the head of items.
func (q *Queue) Pop() interface{} {
if len(q.items) == 0 {
return nil
}
q.lock.Lock()
head := q.items[0]
q.items = q.items[1:]
q.lock.Unlock()
return head
}
// First returns the head of items without deleting.
func (q *Queue) First() interface{} {
if len(q.items) == 0 {
return nil
}
q.lock.RLock()
head := q.items[0]
q.lock.RUnlock()
return head
}
// Copy get the copy of queue.
func (q *Queue) Copy() []interface{} {
items := []interface{}{}
q.lock.RLock()
items = append(items, q.items...)
q.lock.RUnlock()
return items
}
// Len returns the number of items in this queue.
func (q *Queue) Len() int64 {
q.lock.Lock()
defer q.lock.Unlock()
return int64(len(q.items))
}
// New is a constructor for a new concurrent safe queue.
func New(hint int64) *Queue {
return &Queue{
items: make([]interface{}, 0, hint),
}
}

View File

@ -47,11 +47,11 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error {
} }
value, ok := src[key] value, ok := src[key]
if !ok { if !ok || value == nil {
if omitempty { if omitempty {
continue continue
} }
return fmt.Errorf("key %s missing", key) return fmt.Errorf("key '%s' missing", key)
} }
err := d.decode(key, value, v.Field(idx)) err := d.decode(key, value, v.Field(idx))
@ -114,7 +114,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
val.SetString(strconv.FormatInt(dataVal.Int(), 10)) val.SetString(strconv.FormatInt(dataVal.Int(), 10))
default: default:
err = fmt.Errorf( err = fmt.Errorf(
"'%s' expected type'%s', got unconvertible type '%s'", "'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type(), name, val.Type(), dataVal.Type(),
) )
} }
@ -131,7 +131,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (
val.SetBool(dataVal.Int() != 0) val.SetBool(dataVal.Int() != 0)
default: default:
err = fmt.Errorf( err = fmt.Errorf(
"'%s' expected type'%s', got unconvertible type '%s'", "'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type(), name, val.Type(), dataVal.Type(),
) )
} }

View File

@ -11,6 +11,7 @@ import (
type WebsocketOption struct { type WebsocketOption struct {
Host string Host string
Path string Path string
Headers map[string]string
TLSConfig *tls.Config TLSConfig *tls.Config
} }
@ -20,6 +21,7 @@ func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error)
Host: option.Host, Host: option.Host,
Path: option.Path, Path: option.Path,
TLS: option.TLSConfig != nil, TLS: option.TLSConfig != nil,
Headers: option.Headers,
TLSConfig: option.TLSConfig, TLSConfig: option.TLSConfig,
} }

View File

@ -35,20 +35,10 @@ type Conn struct {
respV byte respV byte
security byte security byte
sent bool
received bool received bool
} }
func (vc *Conn) Write(b []byte) (int, error) { func (vc *Conn) Write(b []byte) (int, error) {
if vc.sent {
return vc.writer.Write(b)
}
if err := vc.sendRequest(); err != nil {
return 0, err
}
vc.sent = true
return vc.writer.Write(b) return vc.writer.Write(b)
} }
@ -153,7 +143,7 @@ func hashTimestamp(t time.Time) []byte {
} }
// newConn return a Conn instance // newConn return a Conn instance
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn { func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, error) {
randBytes := make([]byte, 33) randBytes := make([]byte, 33)
rand.Read(randBytes) rand.Read(randBytes)
reqBodyIV := make([]byte, 16) reqBodyIV := make([]byte, 16)
@ -196,7 +186,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
reader = newAEADReader(conn, aead, respBodyIV[:]) reader = newAEADReader(conn, aead, respBodyIV[:])
} }
return &Conn{ c := &Conn{
Conn: conn, Conn: conn,
id: id, id: id,
dst: dst, dst: dst,
@ -209,4 +199,8 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
writer: writer, writer: writer,
security: security, security: security,
} }
if err := c.sendRequest(); err != nil {
return nil, err
}
return c, nil
} }

View File

@ -100,7 +100,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
} else if c.tls { } else if c.tls {
conn = tls.Client(conn, c.tlsConfig) conn = tls.Client(conn, c.tlsConfig)
} }
return newConn(conn, c.user[r], dst, c.security), nil return newConn(conn, c.user[r], dst, c.security)
} }
// NewClient return Client instance // NewClient return Client instance

View File

@ -184,8 +184,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
proxies["DIRECT"] = adapters.NewDirect() proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
proxies["REJECT"] = adapters.NewReject() proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
@ -194,8 +194,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
return nil, fmt.Errorf("Proxy %d missing type", idx) return nil, fmt.Errorf("Proxy %d missing type", idx)
} }
var proxy C.Proxy var proxy C.ProxyAdapter
var err error err := fmt.Errorf("can't parse")
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &adapters.ShadowSocksOption{} ssOption := &adapters.ShadowSocksOption{}
@ -236,7 +236,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
if _, exist := proxies[proxy.Name()]; exist { if _, exist := proxies[proxy.Name()]; exist {
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()] = proxy proxies[proxy.Name()] = adapters.NewProxy(proxy)
} }
// parse proxy group // parse proxy group
@ -250,9 +250,10 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
if _, exist := proxies[groupName]; exist { if _, exist := proxies[groupName]; exist {
return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
} }
var group C.Proxy var group C.ProxyAdapter
var ps []C.Proxy ps := []C.Proxy{}
var err error
err := fmt.Errorf("can't parse")
switch groupType { switch groupType {
case "url-test": case "url-test":
urlTestOption := &adapters.URLTestOption{} urlTestOption := &adapters.URLTestOption{}
@ -290,19 +291,32 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewFallback(*fallbackOption, ps) group, err = adapters.NewFallback(*fallbackOption, ps)
case "load-balance":
loadBalanceOption := &adapters.LoadBalanceOption{}
err = decoder.Decode(mapping, loadBalanceOption)
if err != nil {
break
}
ps, err = getProxies(proxies, loadBalanceOption.Proxies)
if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
}
group, err = adapters.NewLoadBalance(*loadBalanceOption, ps)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
} }
proxies[groupName] = group proxies[groupName] = adapters.NewProxy(group)
} }
var ps []C.Proxy ps := []C.Proxy{}
for _, v := range proxies { for _, v := range proxies {
ps = append(ps, v) ps = append(ps, v)
} }
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) global, _ := adapters.NewSelector("GLOBAL", ps)
proxies["GLOBAL"] = adapters.NewProxy(global)
return proxies, nil return proxies, nil
} }
@ -325,28 +339,39 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
payload = rule[1] payload = rule[1]
target = rule[2] target = rule[2]
default: default:
return nil, fmt.Errorf("Rules[%d] [- %s] error: format invalid", idx, line) return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
} }
rule = trimArr(rule) rule = trimArr(rule)
var parsed C.Rule
switch rule[0] { switch rule[0] {
case "DOMAIN": case "DOMAIN":
rules = append(rules, R.NewDomain(payload, target)) parsed = R.NewDomain(payload, target)
case "DOMAIN-SUFFIX": case "DOMAIN-SUFFIX":
rules = append(rules, R.NewDomainSuffix(payload, target)) parsed = R.NewDomainSuffix(payload, target)
case "DOMAIN-KEYWORD": case "DOMAIN-KEYWORD":
rules = append(rules, R.NewDomainKeyword(payload, target)) parsed = R.NewDomainKeyword(payload, target)
case "GEOIP": case "GEOIP":
rules = append(rules, R.NewGEOIP(payload, target)) parsed = R.NewGEOIP(payload, target)
case "IP-CIDR", "IP-CIDR6": case "IP-CIDR", "IP-CIDR6":
rules = append(rules, R.NewIPCIDR(payload, target, false)) if rule := R.NewIPCIDR(payload, target, false); rule != nil {
parsed = rule
}
case "SOURCE-IP-CIDR": case "SOURCE-IP-CIDR":
rules = append(rules, R.NewIPCIDR(payload, target, true)) if rule := R.NewIPCIDR(payload, target, true); rule != nil {
parsed = rule
}
case "MATCH": case "MATCH":
fallthrough fallthrough
case "FINAL": case "FINAL":
rules = append(rules, R.NewFinal(target)) parsed = R.NewMatch(target)
} }
if parsed == nil {
return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line)
}
rules = append(rules, parsed)
} }
return rules, nil return rules, nil
@ -374,33 +399,40 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
for idx, server := range servers { for idx, server := range servers {
// parse without scheme .e.g 8.8.8.8:53 // parse without scheme .e.g 8.8.8.8:53
if host, err := hostWithDefaultPort(server, "53"); err == nil { if !strings.Contains(server, "://") {
nameservers = append( server = "udp://" + server
nameservers,
dns.NameServer{Addr: host},
)
continue
} }
u, err := url.Parse(server) u, err := url.Parse(server)
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())
} }
if u.Scheme != "tls" { var host, dnsNetType string
switch u.Scheme {
case "udp":
host, err = hostWithDefaultPort(u.Host, "53")
dnsNetType = "" // UDP
case "tcp":
host, err = hostWithDefaultPort(u.Host, "53")
dnsNetType = "tcp" // TCP
case "tls":
host, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS
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 {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
}
host, err := hostWithDefaultPort(u.Host, "853")
nameservers = append( nameservers = append(
nameservers, nameservers,
dns.NameServer{ dns.NameServer{
Net: "tcp-tls", Net: dnsNetType,
Addr: host, Addr: host,
}, },
) )
} }
return nameservers, nil return nameservers, nil
} }
@ -414,13 +446,13 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
Listen: cfg.Listen, Listen: cfg.Listen,
EnhancedMode: cfg.EnhancedMode, EnhancedMode: cfg.EnhancedMode,
} }
var err error
if nameserver, err := parseNameServer(cfg.NameServer); err == nil { if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
dnsCfg.NameServer = nameserver return nil, err
} }
if fallback, err := parseNameServer(cfg.Fallback); err == nil { if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback); err != nil {
dnsCfg.Fallback = fallback return nil, err
} }
return dnsCfg, nil return dnsCfg, nil

View File

@ -2,6 +2,7 @@ package constant
import ( import (
"net" "net"
"time"
) )
// Adapter Type // Adapter Type
@ -15,6 +16,7 @@ const (
Http Http
URLTest URLTest
Vmess Vmess
LoadBalance
) )
type ServerAdapter interface { type ServerAdapter interface {
@ -22,13 +24,27 @@ type ServerAdapter interface {
Close() Close()
} }
type Proxy interface { type ProxyAdapter interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
Generator(metadata *Metadata) (net.Conn, error) Dial(metadata *Metadata) (net.Conn, error)
Destroy()
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
} }
type DelayHistory struct {
Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
}
type Proxy interface {
ProxyAdapter
Alive() bool
DelayHistory() []DelayHistory
LastDelay() uint16
URLTest(url string) (uint16, error)
}
// AdapterType is enum of adapter type // AdapterType is enum of adapter type
type AdapterType int type AdapterType int
@ -52,6 +68,8 @@ func (at AdapterType) String() string {
return "URLTest" return "URLTest"
case Vmess: case Vmess:
return "Vmess" return "Vmess"
case LoadBalance:
return "LoadBalance"
default: default:
return "Unknow" return "Unknow"
} }

View File

@ -27,6 +27,7 @@ func init() {
} else { } else {
homedir = currentUser.HomeDir homedir = currentUser.HomeDir
} }
homedir = P.Join(homedir, ".config", Name) homedir = P.Join(homedir, ".config", Name)
Path = &path{homedir: homedir} Path = &path{homedir: homedir}
} }

View File

@ -8,7 +8,7 @@ const (
GEOIP GEOIP
IPCIDR IPCIDR
SourceIPCIDR SourceIPCIDR
FINAL MATCH
) )
type RuleType int type RuleType int
@ -27,8 +27,8 @@ func (rt RuleType) String() string {
return "IPCIDR" return "IPCIDR"
case SourceIPCIDR: case SourceIPCIDR:
return "SourceIPCIDR" return "SourceIPCIDR"
case FINAL: case MATCH:
return "FINAL" return "MATCH"
default: default:
return "Unknow" return "Unknow"
} }

6
constant/version.go Normal file
View File

@ -0,0 +1,6 @@
package constant
var (
Version = "unknown version"
BuildTime = "unknown time"
)

View File

@ -12,7 +12,6 @@ import (
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker" "github.com/Dreamacro/clash/common/picker"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
geoip2 "github.com/oschwald/geoip2-golang" geoip2 "github.com/oschwald/geoip2-golang"
@ -55,26 +54,19 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
cache, expireTime := r.cache.GetWithExpire(q.String()) cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil { if cache != nil {
msg = cache.(*D.Msg).Copy() msg = cache.(*D.Msg).Copy()
if len(msg.Answer) > 0 { setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
ttl := uint32(expireTime.Sub(time.Now()).Seconds())
for _, answer := range msg.Answer {
answer.Header().Ttl = ttl
}
}
return return
} }
defer func() { defer func() {
if msg != nil { if msg == nil {
putMsgToCache(r.cache, q.String(), msg) return
if r.mapping { }
ips, err := r.msgToIP(msg)
if err != nil { putMsgToCache(r.cache, q.String(), msg)
log.Debugln("[DNS] msg to ip error: %s", err.Error()) if r.mapping {
return ips := r.msgToIP(msg)
} for _, ip := range ips {
for _, ip := range ips { putMsgToCache(r.cache, ip.String(), msg)
putMsgToCache(r.cache, ip.String(), msg)
}
} }
} }
}() }()
@ -138,8 +130,7 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
return nil, errors.New("GeoIP can't use") return nil, errors.New("GeoIP can't use")
} }
ips, err := r.msgToIP(res.Msg) if ips := r.msgToIP(res.Msg); len(ips) != 0 {
if err == nil {
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
// release channel // release channel
go func() { <-fallbackMsg }() go func() { <-fallbackMsg }()
@ -172,18 +163,17 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
return nil, err return nil, err
} }
var ips []net.IP ips := r.msgToIP(msg)
ips, err = r.msgToIP(msg) if len(ips) == 0 {
if err != nil { return nil, errors.New("can't found ip")
return nil, err
} }
ip = ips[0] ip = ips[0]
return return
} }
func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) { func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
var ips []net.IP ips := []net.IP{}
for _, answer := range msg.Answer { for _, answer := range msg.Answer {
switch ans := answer.(type) { switch ans := answer.(type) {
@ -194,11 +184,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) {
} }
} }
if len(ips) == 0 { return ips
return nil, errors.New("Can't parse msg")
}
return ips, nil
} }
func (r *Resolver) IPToHost(ip net.IP) (string, bool) { func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
@ -248,6 +234,7 @@ func transform(servers []NameServer) []*nameserver {
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
ClientSessionCache: globalSessionCache, ClientSessionCache: globalSessionCache,
}, },
UDPSize: 4096,
}, },
Address: s.Addr, Address: s.Addr,
}) })

View File

@ -1,8 +1,10 @@
package dns package dns
import ( import (
"fmt"
"net" "net"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -20,6 +22,11 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
msg, err := s.r.Exchange(r) msg, err := s.r.Exchange(r)
if err != nil { if err != nil {
if len(r.Question) > 0 {
q := r.Question[0]
qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String())
log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err)
}
D.HandleFailed(w, r) D.HandleFailed(w, r)
return return
} }
@ -27,13 +34,18 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
w.WriteMsg(msg) w.WriteMsg(msg)
} }
func (s *Server) setReslover(r *Resolver) {
s.r = r
}
func ReCreateServer(addr string, resolver *Resolver) error { func ReCreateServer(addr string, resolver *Resolver) error {
if server.Server != nil { if addr == address {
server.Shutdown() server.setReslover(resolver)
return nil
} }
if addr == address { if server.Server != nil {
return nil server.Shutdown()
} }
_, port, err := net.SplitHostPort(addr) _, port, err := net.SplitHostPort(addr)

View File

@ -79,11 +79,31 @@ func (e EnhancedMode) String() string {
} }
func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) {
if len(msg.Answer) == 0 { var ttl time.Duration
log.Debugln("[DNS] answer length is zero: %#v", msg) if len(msg.Answer) != 0 {
ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second
} else if len(msg.Ns) != 0 {
ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second
} else if len(msg.Extra) != 0 {
ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second
} else {
log.Debugln("[DNS] response msg error: %#v", msg)
return return
} }
ttl := time.Duration(msg.Answer[0].Header().Ttl) * time.Second c.Put(key, msg.Copy(), ttl)
c.Put(key, msg, ttl) }
func setMsgTTL(msg *D.Msg, ttl uint32) {
for _, answer := range msg.Answer {
answer.Header().Ttl = ttl
}
for _, ns := range msg.Ns {
ns.Header().Ttl = ttl
}
for _, extra := range msg.Extra {
extra.Header().Ttl = ttl
}
} }

2
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/sirupsen/logrus v1.3.0 github.com/sirupsen/logrus v1.3.0
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect golang.org/x/net v0.0.0-20181108082009-03003ca0c849
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // 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

View File

@ -1,7 +1,6 @@
package executor package executor
import ( import (
adapters "github.com/Dreamacro/clash/adapters/outbound"
"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"
@ -66,14 +65,9 @@ func updateProxies(proxies map[string]C.Proxy) {
tunnel := T.Instance() tunnel := T.Instance()
oldProxies := tunnel.Proxies() oldProxies := tunnel.Proxies()
// close old goroutine // close proxy group goroutine
for _, proxy := range oldProxies { for _, proxy := range oldProxies {
switch raw := proxy.(type) { proxy.Destroy()
case *adapters.URLTest:
raw.Close()
case *adapters.Fallback:
raw.Close()
}
} }
tunnel.UpdateProxies(proxies) tunnel.UpdateProxies(proxies)

View File

@ -59,7 +59,7 @@ func findProxyByName(next http.Handler) http.Handler {
func getProxies(w http.ResponseWriter, r *http.Request) { func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := T.Instance().Proxies() proxies := T.Instance().Proxies()
render.JSON(w, r, map[string]map[string]C.Proxy{ render.JSON(w, r, render.M{
"proxies": proxies, "proxies": proxies,
}) })
} }
@ -81,12 +81,11 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return return
} }
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(*A.Proxy)
selector, ok := proxy.ProxyAdapter.(*A.Selector)
selector, ok := proxy.(*A.Selector)
if !ok { if !ok {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, newError("Must be a Selector"))
return return
} }
@ -111,9 +110,9 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
sigCh := make(chan int16) sigCh := make(chan uint16)
go func() { go func() {
t, err := A.DelayTest(proxy, url) t, err := proxy.URLTest(url)
if err != nil { if err != nil {
sigCh <- 0 sigCh <- 0
} }
@ -129,7 +128,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusServiceUnavailable) render.Status(r, http.StatusServiceUnavailable)
render.JSON(w, r, newError("An error occurred in the delay test")) render.JSON(w, r, newError("An error occurred in the delay test"))
} else { } else {
render.JSON(w, r, map[string]int16{ render.JSON(w, r, render.M{
"delay": t, "delay": t,
}) })
} }

View File

@ -24,7 +24,7 @@ type Rule struct {
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := T.Instance().Rules() rawRules := T.Instance().Rules()
var rules []Rule rules := []Rule{}
for _, rule := range rawRules { for _, rule := range rawRules {
rules = append(rules, Rule{ rules = append(rules, Rule{
Type: rule.RuleType().String(), Type: rule.RuleType().String(),
@ -33,7 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
}) })
} }
render.JSON(w, r, map[string][]Rule{ render.JSON(w, r, render.M{
"rules": rules, "rules": rules,
}) })
} }

12
main.go
View File

@ -2,9 +2,11 @@ package main
import ( import (
"flag" "flag"
"fmt"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime"
"syscall" "syscall"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
@ -15,15 +17,25 @@ import (
) )
var ( var (
version bool
homedir string homedir string
) )
func init() { func init() {
flag.StringVar(&homedir, "d", "", "set configuration directory") flag.StringVar(&homedir, "d", "", "set configuration directory")
flag.BoolVar(&version, "v", false, "show current version of clash")
flag.Parse() flag.Parse()
} }
func main() { func main() {
if version {
fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime)
return
}
// enable tls 1.3 and remove when go 1.13
os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
if homedir != "" { if homedir != "" {
if !filepath.IsAbs(homedir) { if !filepath.IsAbs(homedir) {
currentDir, _ := os.Getwd() currentDir, _ := os.Getwd()

View File

@ -5,7 +5,7 @@ import (
"net" "net"
"net/http" "net/http"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
) )
@ -56,7 +56,7 @@ func (l *HttpListener) Address() string {
func handleConn(conn net.Conn) { func handleConn(conn net.Conn) {
br := bufio.NewReader(conn) br := bufio.NewReader(conn)
request, err := http.ReadRequest(br) request, err := http.ReadRequest(br)
if err != nil { if err != nil || request.URL.Host == "" {
conn.Close() conn.Close()
return return
} }

View File

@ -4,28 +4,28 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Final struct { type Match struct {
adapter string adapter string
} }
func (f *Final) RuleType() C.RuleType { func (f *Match) RuleType() C.RuleType {
return C.FINAL return C.MATCH
} }
func (f *Final) IsMatch(metadata *C.Metadata) bool { func (f *Match) IsMatch(metadata *C.Metadata) bool {
return true return true
} }
func (f *Final) Adapter() string { func (f *Match) Adapter() string {
return f.adapter return f.adapter
} }
func (f *Final) Payload() string { func (f *Match) Payload() string {
return "" return ""
} }
func NewFinal(adapter string) *Final { func NewMatch(adapter string) *Match {
return &Final{ return &Match{
adapter: adapter, adapter: adapter,
} }
} }

View File

@ -24,7 +24,7 @@ func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool {
if i.isSourceIP { if i.isSourceIP {
ip = metadata.SourceIP ip = metadata.SourceIP
} }
return i.ipnet.Contains(*ip) return ip != nil && i.ipnet.Contains(*ip)
} }
func (i *IPCIDR) Adapter() string { func (i *IPCIDR) Adapter() string {
@ -38,6 +38,7 @@ func (i *IPCIDR) Payload() string {
func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR { func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR {
_, ipnet, err := net.ParseCIDR(s) _, ipnet, err := net.ParseCIDR(s)
if err != nil { if err != nil {
return nil
} }
return &IPCIDR{ return &IPCIDR{
ipnet: ipnet, ipnet: ipnet,

View File

@ -25,12 +25,9 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
conn := newTrafficTrack(outbound, t.traffic) conn := newTrafficTrack(outbound, t.traffic)
req := request.R req := request.R
host := req.Host host := req.Host
keepalive := true
for { for {
if strings.ToLower(req.Header.Get("Connection")) == "close" { keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive"
keepalive = false
}
req.Header.Set("Connection", "close") req.Header.Set("Connection", "close")
req.RequestURI = "" req.RequestURI = ""
@ -58,7 +55,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
break break
} }
if !keepalive { if !keepAlive {
break break
} }

View File

@ -107,15 +107,20 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) {
return t.resolver.ResolveIP(host) return t.resolver.ResolveIP(host)
} }
func (t *Tunnel) needLookupIP() bool { func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
return t.hasResolver() && t.resolver.IsMapping() return t.hasResolver() && t.resolver.IsMapping() && metadata.Host == "" && metadata.IP != nil
} }
func (t *Tunnel) handleConn(localConn C.ServerAdapter) { func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
defer localConn.Close() defer localConn.Close()
metadata := localConn.Metadata() metadata := localConn.Metadata()
if t.needLookupIP() { if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
if t.needLookupIP(metadata) {
host, exist := t.resolver.IPToHost(*metadata.IP) host, exist := t.resolver.IPToHost(*metadata.IP)
if exist { if exist {
metadata.Host = host metadata.Host = host
@ -138,12 +143,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
} }
} }
if !metadata.Valid() { remoConn, err := proxy.Dial(metadata)
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
remoConn, err := proxy.Generator(metadata)
if err != nil { if err != nil {
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error())
return return