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
sudo: false
go:
- "1.11"
- '1.12'
install:
- "go mod download"
env:

View File

@ -1,6 +1,10 @@
NAME=clash
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 = \
darwin-amd64 \
@ -67,7 +71,7 @@ freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
@ -77,10 +81,10 @@ zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
$(gz_releases): %.gz : %
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 -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)

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)
Requires Go >= 1.11.
Requires Go >= 1.12.
Checkout Clash version:
```sh
clash -v
```
## Daemon
@ -108,7 +114,7 @@ external-controller: 127.0.0.1:9090
# - 114.114.114.114
# - tls://dns.rubyfish.cn:853 # dns over tls
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
# - 8.8.8.8
# - tcp://1.1.1.1
Proxy:
@ -143,6 +149,8 @@ Proxy:
# skip-cert-verify: true
# host: bing.com
# path: "/"
# headers:
# custom: value
# vmess
# 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 }
# 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
# you can use RESTful API to switch proxy, is recommended for use in GUI.

View File

@ -2,7 +2,11 @@ package adapters
import (
"encoding/json"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant"
)
@ -19,8 +23,116 @@ func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) Destroy() {}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]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
}
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)
if metadata.IP != nil {
address = net.JoinHostPort(metadata.IP.String(), metadata.Port)

View File

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

View File

@ -35,7 +35,7 @@ type HttpOption struct {
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)
if err == nil && h.tls {
cc := tls.Client(c, h.tlsConfig)
@ -101,8 +101,6 @@ func NewHttp(option HttpOption) *Http {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
MinVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS12,
ServerName: option.Server,
}
}

View File

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

View File

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

View File

@ -48,14 +48,15 @@ type simpleObfsOption struct {
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,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)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
@ -131,10 +132,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
ClientSessionCache: getClientSessionCache(),
}
}
wsOption = &v2rayObfs.WebsocketOption{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
TLSConfig: tlsConfig,
}
}

View File

@ -34,7 +34,7 @@ type Socks5Option struct {
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)
if err == nil && ss.tls {
@ -118,8 +118,6 @@ func NewSocks5(option Socks5Option) *Socks5 {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
MinVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS12,
ServerName: option.Server,
}
}

View File

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

View File

@ -4,7 +4,6 @@ import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
@ -21,39 +20,6 @@ var (
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) {
u, err := url.Parse(rawURL)
if err != nil {
@ -81,21 +47,6 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
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) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)

View File

@ -30,7 +30,7 @@ type VmessOption struct {
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)
if err != nil {
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]
if !ok {
if !ok || value == nil {
if omitempty {
continue
}
return fmt.Errorf("key %s missing", key)
return fmt.Errorf("key '%s' missing", key)
}
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))
default:
err = fmt.Errorf(
"'%s' expected type'%s', got unconvertible type '%s'",
"'%s' expected type '%s', got unconvertible type '%s'",
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)
default:
err = fmt.Errorf(
"'%s' expected type'%s', got unconvertible type '%s'",
"'%s' expected type '%s', got unconvertible type '%s'",
name, val.Type(), dataVal.Type(),
)
}

View File

@ -11,6 +11,7 @@ import (
type WebsocketOption struct {
Host string
Path string
Headers map[string]string
TLSConfig *tls.Config
}
@ -20,6 +21,7 @@ func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error)
Host: option.Host,
Path: option.Path,
TLS: option.TLSConfig != nil,
Headers: option.Headers,
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})
proxies["DIRECT"] = adapters.NewDirect()
proxies["REJECT"] = adapters.NewReject()
proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
// parse proxy
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)
}
var proxy C.Proxy
var proxy C.ProxyAdapter
err := fmt.Errorf("can't parse")
switch proxyType {
case "ss":
@ -236,7 +236,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
if _, exist := proxies[proxy.Name()]; exist {
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
@ -250,7 +250,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
if _, exist := proxies[groupName]; exist {
return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
}
var group C.Proxy
var group C.ProxyAdapter
ps := []C.Proxy{}
err := fmt.Errorf("can't parse")
@ -302,12 +302,12 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
if err != nil {
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 {
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
}
proxies[groupName] = group
proxies[groupName] = adapters.NewProxy(group)
}
ps := []C.Proxy{}
@ -315,7 +315,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
ps = append(ps, v)
}
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps)
global, _ := adapters.NewSelector("GLOBAL", ps)
proxies["GLOBAL"] = adapters.NewProxy(global)
return proxies, nil
}
@ -338,28 +339,39 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
payload = rule[1]
target = rule[2]
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)
var parsed C.Rule
switch rule[0] {
case "DOMAIN":
rules = append(rules, R.NewDomain(payload, target))
parsed = R.NewDomain(payload, target)
case "DOMAIN-SUFFIX":
rules = append(rules, R.NewDomainSuffix(payload, target))
parsed = R.NewDomainSuffix(payload, target)
case "DOMAIN-KEYWORD":
rules = append(rules, R.NewDomainKeyword(payload, target))
parsed = R.NewDomainKeyword(payload, target)
case "GEOIP":
rules = append(rules, R.NewGEOIP(payload, target))
parsed = R.NewGEOIP(payload, target)
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":
rules = append(rules, R.NewIPCIDR(payload, target, true))
if rule := R.NewIPCIDR(payload, target, true); rule != nil {
parsed = rule
}
case "MATCH":
fallthrough
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
@ -387,33 +399,40 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
for idx, server := range servers {
// parse without scheme .e.g 8.8.8.8:53
if host, err := hostWithDefaultPort(server, "53"); err == nil {
nameservers = append(
nameservers,
dns.NameServer{Addr: host},
)
continue
if !strings.Contains(server, "://") {
server = "udp://" + server
}
u, err := url.Parse(server)
if err != nil {
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)
}
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,
dns.NameServer{
Net: "tcp-tls",
Net: dnsNetType,
Addr: host,
},
)
}
return nameservers, nil
}
@ -427,13 +446,13 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
Listen: cfg.Listen,
EnhancedMode: cfg.EnhancedMode,
}
if nameserver, err := parseNameServer(cfg.NameServer); err == nil {
dnsCfg.NameServer = nameserver
var err error
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
return nil, err
}
if fallback, err := parseNameServer(cfg.Fallback); err == nil {
dnsCfg.Fallback = fallback
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback); err != nil {
return nil, err
}
return dnsCfg, nil

View File

@ -2,6 +2,7 @@ package constant
import (
"net"
"time"
)
// Adapter Type
@ -23,13 +24,27 @@ type ServerAdapter interface {
Close()
}
type Proxy interface {
type ProxyAdapter interface {
Name() string
Type() AdapterType
Generator(metadata *Metadata) (net.Conn, error)
Dial(metadata *Metadata) (net.Conn, error)
Destroy()
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
type AdapterType int

View File

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

View File

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

View File

@ -1,8 +1,10 @@
package dns
import (
"fmt"
"net"
"github.com/Dreamacro/clash/log"
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)
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)
return
}
@ -27,13 +34,18 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
w.WriteMsg(msg)
}
func (s *Server) setReslover(r *Resolver) {
s.r = r
}
func ReCreateServer(addr string, resolver *Resolver) error {
if server.Server != nil {
server.Shutdown()
if addr == address {
server.setReslover(resolver)
return nil
}
if addr == address {
return nil
if server.Server != nil {
server.Shutdown()
}
_, 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) {
if len(msg.Answer) == 0 {
log.Debugln("[DNS] answer length is zero: %#v", msg)
var ttl time.Duration
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
}
ttl := time.Duration(msg.Answer[0].Header().Ttl) * time.Second
c.Put(key, msg, ttl)
c.Put(key, msg.Copy(), 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
import (
adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
@ -66,14 +65,9 @@ func updateProxies(proxies map[string]C.Proxy) {
tunnel := T.Instance()
oldProxies := tunnel.Proxies()
// close old goroutine
// close proxy group goroutine
for _, proxy := range oldProxies {
switch raw := proxy.(type) {
case *adapters.URLTest:
raw.Close()
case *adapters.Fallback:
raw.Close()
}
proxy.Destroy()
}
tunnel.UpdateProxies(proxies)

View File

@ -81,12 +81,11 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return
}
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
selector, ok := proxy.(*A.Selector)
proxy := r.Context().Value(CtxKeyProxy).(*A.Proxy)
selector, ok := proxy.ProxyAdapter.(*A.Selector)
if !ok {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
render.JSON(w, r, newError("Must be a Selector"))
return
}
@ -111,9 +110,9 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
sigCh := make(chan int16)
sigCh := make(chan uint16)
go func() {
t, err := A.DelayTest(proxy, url)
t, err := proxy.URLTest(url)
if err != nil {
sigCh <- 0
}

12
main.go
View File

@ -2,9 +2,11 @@ package main
import (
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime"
"syscall"
"github.com/Dreamacro/clash/config"
@ -15,15 +17,25 @@ import (
)
var (
version bool
homedir string
)
func init() {
flag.StringVar(&homedir, "d", "", "set configuration directory")
flag.BoolVar(&version, "v", false, "show current version of clash")
flag.Parse()
}
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 !filepath.IsAbs(homedir) {
currentDir, _ := os.Getwd()

View File

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

View File

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

View File

@ -38,6 +38,7 @@ func (i *IPCIDR) Payload() string {
func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR {
_, ipnet, err := net.ParseCIDR(s)
if err != nil {
return nil
}
return &IPCIDR{
ipnet: ipnet,

View File

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

View File

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