Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
744728cb84 | |||
2036f8cb7a | |||
531f487629 | |||
18f885a92a | |||
d3b280a7e5 | |||
d1f6886558 | |||
791d72e05b | |||
14600a8170 | |||
bb267e4a1f | |||
f99da37168 | |||
7a9d986ff3 | |||
63446da5fa | |||
acf55a7f64 | |||
8c608f5d7a | |||
7f0c7d7802 | |||
7683271fe6 | |||
23bb01a4df | |||
0011c7acfe | |||
d75f9ff783 | |||
815e80f720 | |||
ca5399a16e | |||
04927229ff | |||
c0bd82d62b | |||
5c8bb24121 | |||
575720e0cc |
@ -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:
|
||||||
|
12
Makefile
12
Makefile
@ -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)
|
||||||
|
|
||||||
|
14
README.md
14
README.md
@ -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.
|
||||||
|
@ -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}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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{}),
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
71
common/queue/queue.go
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
@ -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))
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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}
|
||||||
}
|
}
|
||||||
|
@ -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
6
constant/version.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
var (
|
||||||
|
Version = "unknown version"
|
||||||
|
BuildTime = "unknown time"
|
||||||
|
)
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
28
dns/util.go
28
dns/util.go
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
12
main.go
@ -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()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user