Compare commits

...

25 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
34 changed files with 498 additions and 255 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
@ -182,7 +190,7 @@ Proxy 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. # 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"] } - { 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.

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)
@ -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

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"net" "net"
"sync"
"time"
"github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/murmur3"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -15,6 +17,9 @@ type LoadBalance struct {
*Base *Base
proxies []C.Proxy proxies []C.Proxy
maxRetry int maxRetry int
rawURL string
interval time.Duration
done chan struct{}
} }
func getKey(metadata *C.Metadata) string { func getKey(metadata *C.Metadata) string {
@ -48,18 +53,50 @@ func jumpHash(key uint64, buckets int32) int32 {
return int32(b) return int32(b)
} }
func (lb *LoadBalance) Generator(metadata *C.Metadata) (net.Conn, error) { func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) {
key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(lb.proxies)) buckets := int32(len(lb.proxies))
for i := 0; i < lb.maxRetry; i++ { for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets) idx := jumpHash(key, buckets)
if proxy, err := lb.proxies[idx].Generator(metadata); err == nil { proxy := lb.proxies[idx]
return proxy, nil if proxy.Alive() {
return proxy.Dial(metadata)
} }
key++
} }
return lb.proxies[0].Generator(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) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
@ -76,19 +113,28 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
type LoadBalanceOption struct { type LoadBalanceOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Proxies []string `proxy:"proxies"` Proxies []string `proxy:"proxies"`
URL string `proxy:"url"`
Interval int `proxy:"interval"`
} }
func NewLoadBalance(name string, proxies []C.Proxy) (*LoadBalance, error) { func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) {
if len(proxies) == 0 { if len(proxies) == 0 {
return nil, errors.New("Provide at least one proxy") return nil, errors.New("Provide at least one proxy")
} }
return &LoadBalance{ interval := time.Duration(option.Interval) * time.Second
lb := &LoadBalance{
Base: &Base{ Base: &Base{
name: name, name: option.Name,
tp: C.LoadBalance, tp: C.LoadBalance,
}, },
proxies: proxies, proxies: proxies,
maxRetry: 3, maxRetry: 3,
}, nil 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

@ -52,10 +52,11 @@ type v2rayObfsOption struct {
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"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,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)

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

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

@ -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,7 +194,7 @@ 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
err := fmt.Errorf("can't parse") err := fmt.Errorf("can't parse")
switch proxyType { switch proxyType {
case "ss": case "ss":
@ -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,7 +250,7 @@ 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
ps := []C.Proxy{} ps := []C.Proxy{}
err := fmt.Errorf("can't parse") err := fmt.Errorf("can't parse")
@ -302,12 +302,12 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
} }
group, err = adapters.NewLoadBalance(loadBalanceOption.Name, ps) 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)
} }
ps := []C.Proxy{} ps := []C.Proxy{}
@ -315,7 +315,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
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
} }
@ -338,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
@ -387,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
} }
@ -427,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
@ -23,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

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,28 +54,21 @@ 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)
if r.mapping {
ips, err := r.msgToIP(msg)
if err != nil {
log.Debugln("[DNS] msg to ip error: %s", err.Error())
return return
} }
putMsgToCache(r.cache, q.String(), msg)
if r.mapping {
ips := r.msgToIP(msg)
for _, ip := range ips { for _, ip := range ips {
putMsgToCache(r.cache, ip.String(), msg) putMsgToCache(r.cache, ip.String(), msg)
} }
} }
}
}() }()
isIPReq := isIPRequest(q) isIPReq := isIPRequest(q)
@ -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,15 +34,20 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
w.WriteMsg(msg) w.WriteMsg(msg)
} }
func ReCreateServer(addr string, resolver *Resolver) error { func (s *Server) setReslover(r *Resolver) {
if server.Server != nil { s.r = r
server.Shutdown()
} }
func ReCreateServer(addr string, resolver *Resolver) error {
if addr == address { if addr == address {
server.setReslover(resolver)
return nil return nil
} }
if server.Server != nil {
server.Shutdown()
}
_, port, err := net.SplitHostPort(addr) _, port, err := net.SplitHostPort(addr)
if port == "0" || port == "" || err != nil { if port == "0" || port == "" || err != nil {
return nil return nil

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

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

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

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

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

@ -115,6 +115,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
defer localConn.Close() defer localConn.Close()
metadata := localConn.Metadata() metadata := localConn.Metadata()
if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
if t.needLookupIP(metadata) { if t.needLookupIP(metadata) {
host, exist := t.resolver.IPToHost(*metadata.IP) host, exist := t.resolver.IPToHost(*metadata.IP)
if exist { if exist {
@ -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