Compare commits
32 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 | |||
e7997a035b | |||
287ad5bc53 | |||
c295c5e412 | |||
8636a4f589 | |||
7a0717830c | |||
5920b05752 | |||
26a87f9d34 |
@ -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)
|
||||||
|
|
||||||
|
23
README.md
23
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
|
||||||
@ -181,21 +189,24 @@ Proxy Group:
|
|||||||
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
|
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
|
||||||
- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
|
- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
|
||||||
|
|
||||||
|
# load-balance: The request of the same eTLD will be dial on the same proxy.
|
||||||
|
- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
|
||||||
|
|
||||||
# select is used for selecting proxy or proxy group
|
# select is used for selecting proxy or proxy group
|
||||||
# you can use RESTful API to switch proxy, is recommended for use in GUI.
|
# you can use RESTful API to switch proxy, is recommended for use in GUI.
|
||||||
- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] }
|
- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] }
|
||||||
|
|
||||||
Rule:
|
Rule:
|
||||||
- DOMAIN-SUFFIX,google.com,Proxy
|
- DOMAIN-SUFFIX,google.com,auto
|
||||||
- DOMAIN-KEYWORD,google,Proxy
|
- DOMAIN-KEYWORD,google,auto
|
||||||
- DOMAIN,google.com,Proxy
|
- DOMAIN,google.com,auto
|
||||||
- DOMAIN-SUFFIX,ad.com,REJECT
|
- DOMAIN-SUFFIX,ad.com,REJECT
|
||||||
- IP-CIDR,127.0.0.0/8,DIRECT
|
- IP-CIDR,127.0.0.0/8,DIRECT
|
||||||
- SOURCE-IP-CIDR,192.168.1.201/32,DIRECT
|
- SOURCE-IP-CIDR,192.168.1.201/32,DIRECT
|
||||||
- GEOIP,CN,DIRECT
|
- GEOIP,CN,DIRECT
|
||||||
# FINAL would remove after prerelease
|
# FINAL would remove after prerelease
|
||||||
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
|
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
|
||||||
- MATCH,Proxy
|
- MATCH,auto
|
||||||
```
|
```
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
@ -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)
|
||||||
@ -58,9 +58,9 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
addr := net.JoinHostPort(metadata.Host, metadata.Port)
|
addr := net.JoinHostPort(metadata.String(), metadata.Port)
|
||||||
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
||||||
buf.WriteString("Host: " + metadata.Host + "\r\n")
|
buf.WriteString("Host: " + metadata.String() + "\r\n")
|
||||||
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
||||||
|
|
||||||
if h.user != "" && h.pass != "" {
|
if h.user != "" && h.pass != "" {
|
||||||
@ -101,8 +101,6 @@ func NewHttp(option HttpOption) *Http {
|
|||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ClientSessionCache: getClientSessionCache(),
|
ClientSessionCache: getClientSessionCache(),
|
||||||
MinVersion: tls.VersionTLS11,
|
|
||||||
MaxVersion: tls.VersionTLS12,
|
|
||||||
ServerName: option.Server,
|
ServerName: option.Server,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
140
adapters/outbound/loadbalance.go
Normal file
140
adapters/outbound/loadbalance.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package adapters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/murmur3"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoadBalance struct {
|
||||||
|
*Base
|
||||||
|
proxies []C.Proxy
|
||||||
|
maxRetry int
|
||||||
|
rawURL string
|
||||||
|
interval time.Duration
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKey(metadata *C.Metadata) string {
|
||||||
|
if metadata.Host != "" {
|
||||||
|
// ip host
|
||||||
|
if ip := net.ParseIP(metadata.Host); ip != nil {
|
||||||
|
return metadata.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
|
||||||
|
return etld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.IP == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata.IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func jumpHash(key uint64, buckets int32) int32 {
|
||||||
|
var b, j int64
|
||||||
|
|
||||||
|
for j < int64(buckets) {
|
||||||
|
b = j
|
||||||
|
key = key*2862933555777941757 + 1
|
||||||
|
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return int32(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||||
|
buckets := int32(len(lb.proxies))
|
||||||
|
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
|
||||||
|
idx := jumpHash(key, buckets)
|
||||||
|
proxy := lb.proxies[idx]
|
||||||
|
if proxy.Alive() {
|
||||||
|
return proxy.Dial(metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lb.proxies[0].Dial(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) Destroy() {
|
||||||
|
lb.done <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) validTest() {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(lb.proxies))
|
||||||
|
|
||||||
|
for _, p := range lb.proxies {
|
||||||
|
go func(p C.Proxy) {
|
||||||
|
p.URLTest(lb.rawURL)
|
||||||
|
wg.Done()
|
||||||
|
}(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) loop() {
|
||||||
|
tick := time.NewTicker(lb.interval)
|
||||||
|
go lb.validTest()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
go lb.validTest()
|
||||||
|
case <-lb.done:
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||||
|
var all []string
|
||||||
|
for _, proxy := range lb.proxies {
|
||||||
|
all = append(all, proxy.Name())
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"type": lb.Type().String(),
|
||||||
|
"all": all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadBalanceOption struct {
|
||||||
|
Name string `proxy:"name"`
|
||||||
|
Proxies []string `proxy:"proxies"`
|
||||||
|
URL string `proxy:"url"`
|
||||||
|
Interval int `proxy:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) {
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return nil, errors.New("Provide at least one proxy")
|
||||||
|
}
|
||||||
|
|
||||||
|
interval := time.Duration(option.Interval) * time.Second
|
||||||
|
|
||||||
|
lb := &LoadBalance{
|
||||||
|
Base: &Base{
|
||||||
|
name: option.Name,
|
||||||
|
tp: C.LoadBalance,
|
||||||
|
},
|
||||||
|
proxies: proxies,
|
||||||
|
maxRetry: 3,
|
||||||
|
rawURL: option.URL,
|
||||||
|
interval: interval,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go lb.loop()
|
||||||
|
return lb, nil
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -48,14 +48,15 @@ type simpleObfsOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type v2rayObfsOption struct {
|
type v2rayObfsOption struct {
|
||||||
Mode string `obfs:"mode"`
|
Mode string `obfs:"mode"`
|
||||||
Host string `obfs:"host,omitempty"`
|
Host string `obfs:"host,omitempty"`
|
||||||
Path string `obfs:"path,omitempty"`
|
Path string `obfs:"path,omitempty"`
|
||||||
TLS bool `obfs:"tls,omitempty"`
|
TLS bool `obfs:"tls,omitempty"`
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
Headers map[string]string `obfs:"headers,omitempty"`
|
||||||
|
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) {
|
func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
|
c, err := net.DialTimeout("tcp", ss.server, tcpTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
|
||||||
@ -131,10 +132,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
ClientSessionCache: getClientSessionCache(),
|
ClientSessionCache: getClientSessionCache(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wsOption = &v2rayObfs.WebsocketOption{
|
wsOption = &v2rayObfs.WebsocketOption{
|
||||||
Host: opts.Host,
|
Host: opts.Host,
|
||||||
Path: opts.Path,
|
Path: opts.Path,
|
||||||
|
Headers: opts.Headers,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
50
common/murmur3/murmur.go
Normal file
50
common/murmur3/murmur.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package murmur3
|
||||||
|
|
||||||
|
type bmixer interface {
|
||||||
|
bmix(p []byte) (tail []byte)
|
||||||
|
Size() (n int)
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
type digest struct {
|
||||||
|
clen int // Digested input cumulative length.
|
||||||
|
tail []byte // 0 to Size()-1 bytes view of `buf'.
|
||||||
|
buf [16]byte // Expected (but not required) to be Size() large.
|
||||||
|
seed uint32 // Seed for initializing the hash.
|
||||||
|
bmixer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *digest) BlockSize() int { return 1 }
|
||||||
|
|
||||||
|
func (d *digest) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
d.clen += n
|
||||||
|
|
||||||
|
if len(d.tail) > 0 {
|
||||||
|
// Stick back pending bytes.
|
||||||
|
nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1].
|
||||||
|
if nfree < len(p) {
|
||||||
|
// One full block can be formed.
|
||||||
|
block := append(d.tail, p[:nfree]...)
|
||||||
|
p = p[nfree:]
|
||||||
|
_ = d.bmix(block) // No tail.
|
||||||
|
} else {
|
||||||
|
// Tail's buf is large enough to prevent reallocs.
|
||||||
|
p = append(d.tail, p...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.tail = d.bmix(p)
|
||||||
|
|
||||||
|
// Keep own copy of the 0 to Size()-1 pending bytes.
|
||||||
|
nn := copy(d.buf[:], d.tail)
|
||||||
|
d.tail = d.buf[:nn]
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *digest) Reset() {
|
||||||
|
d.clen = 0
|
||||||
|
d.tail = nil
|
||||||
|
d.bmixer.reset()
|
||||||
|
}
|
149
common/murmur3/murmur32.go
Normal file
149
common/murmur3/murmur32.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package murmur3
|
||||||
|
|
||||||
|
// https://github.com/spaolacci/murmur3/blob/master/murmur32.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make sure interfaces are correctly implemented.
|
||||||
|
var (
|
||||||
|
_ hash.Hash32 = new(digest32)
|
||||||
|
_ bmixer = new(digest32)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
c1_32 uint32 = 0xcc9e2d51
|
||||||
|
c2_32 uint32 = 0x1b873593
|
||||||
|
)
|
||||||
|
|
||||||
|
// digest32 represents a partial evaluation of a 32 bites hash.
|
||||||
|
type digest32 struct {
|
||||||
|
digest
|
||||||
|
h1 uint32 // Unfinalized running hash.
|
||||||
|
}
|
||||||
|
|
||||||
|
// New32 returns new 32-bit hasher
|
||||||
|
func New32() hash.Hash32 { return New32WithSeed(0) }
|
||||||
|
|
||||||
|
// New32WithSeed returns new 32-bit hasher set with explicit seed value
|
||||||
|
func New32WithSeed(seed uint32) hash.Hash32 {
|
||||||
|
d := new(digest32)
|
||||||
|
d.seed = seed
|
||||||
|
d.bmixer = d
|
||||||
|
d.Reset()
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *digest32) Size() int { return 4 }
|
||||||
|
|
||||||
|
func (d *digest32) reset() { d.h1 = d.seed }
|
||||||
|
|
||||||
|
func (d *digest32) Sum(b []byte) []byte {
|
||||||
|
h := d.Sum32()
|
||||||
|
return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digest as many blocks as possible.
|
||||||
|
func (d *digest32) bmix(p []byte) (tail []byte) {
|
||||||
|
h1 := d.h1
|
||||||
|
|
||||||
|
nblocks := len(p) / 4
|
||||||
|
for i := 0; i < nblocks; i++ {
|
||||||
|
k1 := *(*uint32)(unsafe.Pointer(&p[i*4]))
|
||||||
|
|
||||||
|
k1 *= c1_32
|
||||||
|
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
||||||
|
k1 *= c2_32
|
||||||
|
|
||||||
|
h1 ^= k1
|
||||||
|
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
|
||||||
|
h1 = h1*4 + h1 + 0xe6546b64
|
||||||
|
}
|
||||||
|
d.h1 = h1
|
||||||
|
return p[nblocks*d.Size():]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *digest32) Sum32() (h1 uint32) {
|
||||||
|
|
||||||
|
h1 = d.h1
|
||||||
|
|
||||||
|
var k1 uint32
|
||||||
|
switch len(d.tail) & 3 {
|
||||||
|
case 3:
|
||||||
|
k1 ^= uint32(d.tail[2]) << 16
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
k1 ^= uint32(d.tail[1]) << 8
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
k1 ^= uint32(d.tail[0])
|
||||||
|
k1 *= c1_32
|
||||||
|
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
||||||
|
k1 *= c2_32
|
||||||
|
h1 ^= k1
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 ^= uint32(d.clen)
|
||||||
|
|
||||||
|
h1 ^= h1 >> 16
|
||||||
|
h1 *= 0x85ebca6b
|
||||||
|
h1 ^= h1 >> 13
|
||||||
|
h1 *= 0xc2b2ae35
|
||||||
|
h1 ^= h1 >> 16
|
||||||
|
|
||||||
|
return h1
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sum32(data []byte) uint32 { return Sum32WithSeed(data, 0) }
|
||||||
|
|
||||||
|
func Sum32WithSeed(data []byte, seed uint32) uint32 {
|
||||||
|
h1 := seed
|
||||||
|
|
||||||
|
nblocks := len(data) / 4
|
||||||
|
var p uintptr
|
||||||
|
if len(data) > 0 {
|
||||||
|
p = uintptr(unsafe.Pointer(&data[0]))
|
||||||
|
}
|
||||||
|
p1 := p + uintptr(4*nblocks)
|
||||||
|
for ; p < p1; p += 4 {
|
||||||
|
k1 := *(*uint32)(unsafe.Pointer(p))
|
||||||
|
|
||||||
|
k1 *= c1_32
|
||||||
|
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
||||||
|
k1 *= c2_32
|
||||||
|
|
||||||
|
h1 ^= k1
|
||||||
|
h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13)
|
||||||
|
h1 = h1*4 + h1 + 0xe6546b64
|
||||||
|
}
|
||||||
|
|
||||||
|
tail := data[nblocks*4:]
|
||||||
|
|
||||||
|
var k1 uint32
|
||||||
|
switch len(tail) & 3 {
|
||||||
|
case 3:
|
||||||
|
k1 ^= uint32(tail[2]) << 16
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
k1 ^= uint32(tail[1]) << 8
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
k1 ^= uint32(tail[0])
|
||||||
|
k1 *= c1_32
|
||||||
|
k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15)
|
||||||
|
k1 *= c2_32
|
||||||
|
h1 ^= k1
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 ^= uint32(len(data))
|
||||||
|
|
||||||
|
h1 ^= h1 >> 16
|
||||||
|
h1 *= 0x85ebca6b
|
||||||
|
h1 ^= h1 >> 13
|
||||||
|
h1 *= 0xc2b2ae35
|
||||||
|
h1 ^= h1 >> 16
|
||||||
|
|
||||||
|
return h1
|
||||||
|
}
|
71
common/queue/queue.go
Normal file
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))
|
||||||
@ -114,7 +114,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
|
|||||||
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf(
|
err = fmt.Errorf(
|
||||||
"'%s' expected type'%s', got unconvertible type '%s'",
|
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||||
name, val.Type(), dataVal.Type(),
|
name, val.Type(), dataVal.Type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (
|
|||||||
val.SetBool(dataVal.Int() != 0)
|
val.SetBool(dataVal.Int() != 0)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf(
|
err = fmt.Errorf(
|
||||||
"'%s' expected type'%s', got unconvertible type '%s'",
|
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||||
name, val.Type(), dataVal.Type(),
|
name, val.Type(), dataVal.Type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,20 +35,10 @@ type Conn struct {
|
|||||||
respV byte
|
respV byte
|
||||||
security byte
|
security byte
|
||||||
|
|
||||||
sent bool
|
|
||||||
received bool
|
received bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) Write(b []byte) (int, error) {
|
func (vc *Conn) Write(b []byte) (int, error) {
|
||||||
if vc.sent {
|
|
||||||
return vc.writer.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := vc.sendRequest(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vc.sent = true
|
|
||||||
return vc.writer.Write(b)
|
return vc.writer.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +143,7 @@ func hashTimestamp(t time.Time) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newConn return a Conn instance
|
// newConn return a Conn instance
|
||||||
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
|
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, error) {
|
||||||
randBytes := make([]byte, 33)
|
randBytes := make([]byte, 33)
|
||||||
rand.Read(randBytes)
|
rand.Read(randBytes)
|
||||||
reqBodyIV := make([]byte, 16)
|
reqBodyIV := make([]byte, 16)
|
||||||
@ -196,7 +186,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
|
|||||||
reader = newAEADReader(conn, aead, respBodyIV[:])
|
reader = newAEADReader(conn, aead, respBodyIV[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Conn{
|
c := &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
id: id,
|
id: id,
|
||||||
dst: dst,
|
dst: dst,
|
||||||
@ -209,4 +199,8 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn {
|
|||||||
writer: writer,
|
writer: writer,
|
||||||
security: security,
|
security: security,
|
||||||
}
|
}
|
||||||
|
if err := c.sendRequest(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
|||||||
} else if c.tls {
|
} else if c.tls {
|
||||||
conn = tls.Client(conn, c.tlsConfig)
|
conn = tls.Client(conn, c.tlsConfig)
|
||||||
}
|
}
|
||||||
return newConn(conn, c.user[r], dst, c.security), nil
|
return newConn(conn, c.user[r], dst, c.security)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient return Client instance
|
// NewClient return Client instance
|
||||||
|
102
config/config.go
102
config/config.go
@ -184,8 +184,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
|
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||||
|
|
||||||
proxies["DIRECT"] = adapters.NewDirect()
|
proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
|
||||||
proxies["REJECT"] = adapters.NewReject()
|
proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
|
||||||
|
|
||||||
// parse proxy
|
// parse proxy
|
||||||
for idx, mapping := range proxiesConfig {
|
for idx, mapping := range proxiesConfig {
|
||||||
@ -194,8 +194,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
return nil, fmt.Errorf("Proxy %d missing type", idx)
|
return nil, fmt.Errorf("Proxy %d missing type", idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy C.Proxy
|
var proxy C.ProxyAdapter
|
||||||
var err error
|
err := fmt.Errorf("can't parse")
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &adapters.ShadowSocksOption{}
|
ssOption := &adapters.ShadowSocksOption{}
|
||||||
@ -236,7 +236,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
if _, exist := proxies[proxy.Name()]; exist {
|
if _, exist := proxies[proxy.Name()]; exist {
|
||||||
return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
||||||
}
|
}
|
||||||
proxies[proxy.Name()] = proxy
|
proxies[proxy.Name()] = adapters.NewProxy(proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse proxy group
|
// parse proxy group
|
||||||
@ -250,9 +250,10 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
if _, exist := proxies[groupName]; exist {
|
if _, exist := proxies[groupName]; exist {
|
||||||
return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
|
return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
|
||||||
}
|
}
|
||||||
var group C.Proxy
|
var group C.ProxyAdapter
|
||||||
var ps []C.Proxy
|
ps := []C.Proxy{}
|
||||||
var err error
|
|
||||||
|
err := fmt.Errorf("can't parse")
|
||||||
switch groupType {
|
switch groupType {
|
||||||
case "url-test":
|
case "url-test":
|
||||||
urlTestOption := &adapters.URLTestOption{}
|
urlTestOption := &adapters.URLTestOption{}
|
||||||
@ -290,19 +291,32 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
|
|||||||
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
|
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
|
||||||
}
|
}
|
||||||
group, err = adapters.NewFallback(*fallbackOption, ps)
|
group, err = adapters.NewFallback(*fallbackOption, ps)
|
||||||
|
case "load-balance":
|
||||||
|
loadBalanceOption := &adapters.LoadBalanceOption{}
|
||||||
|
err = decoder.Decode(mapping, loadBalanceOption)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err = getProxies(proxies, loadBalanceOption.Proxies)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error())
|
||||||
|
}
|
||||||
|
group, err = adapters.NewLoadBalance(*loadBalanceOption, ps)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
|
return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error())
|
||||||
}
|
}
|
||||||
proxies[groupName] = group
|
proxies[groupName] = adapters.NewProxy(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps []C.Proxy
|
ps := []C.Proxy{}
|
||||||
for _, v := range proxies {
|
for _, v := range proxies {
|
||||||
ps = append(ps, v)
|
ps = append(ps, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps)
|
global, _ := adapters.NewSelector("GLOBAL", ps)
|
||||||
|
proxies["GLOBAL"] = adapters.NewProxy(global)
|
||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,28 +339,39 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
|
|||||||
payload = rule[1]
|
payload = rule[1]
|
||||||
target = rule[2]
|
target = rule[2]
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Rules[%d] [- %s] error: format invalid", idx, line)
|
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
rule = trimArr(rule)
|
rule = trimArr(rule)
|
||||||
|
var parsed C.Rule
|
||||||
switch rule[0] {
|
switch rule[0] {
|
||||||
case "DOMAIN":
|
case "DOMAIN":
|
||||||
rules = append(rules, R.NewDomain(payload, target))
|
parsed = R.NewDomain(payload, target)
|
||||||
case "DOMAIN-SUFFIX":
|
case "DOMAIN-SUFFIX":
|
||||||
rules = append(rules, R.NewDomainSuffix(payload, target))
|
parsed = R.NewDomainSuffix(payload, target)
|
||||||
case "DOMAIN-KEYWORD":
|
case "DOMAIN-KEYWORD":
|
||||||
rules = append(rules, R.NewDomainKeyword(payload, target))
|
parsed = R.NewDomainKeyword(payload, target)
|
||||||
case "GEOIP":
|
case "GEOIP":
|
||||||
rules = append(rules, R.NewGEOIP(payload, target))
|
parsed = R.NewGEOIP(payload, target)
|
||||||
case "IP-CIDR", "IP-CIDR6":
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
rules = append(rules, R.NewIPCIDR(payload, target, false))
|
if rule := R.NewIPCIDR(payload, target, false); rule != nil {
|
||||||
|
parsed = rule
|
||||||
|
}
|
||||||
case "SOURCE-IP-CIDR":
|
case "SOURCE-IP-CIDR":
|
||||||
rules = append(rules, R.NewIPCIDR(payload, target, true))
|
if rule := R.NewIPCIDR(payload, target, true); rule != nil {
|
||||||
|
parsed = rule
|
||||||
|
}
|
||||||
case "MATCH":
|
case "MATCH":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "FINAL":
|
case "FINAL":
|
||||||
rules = append(rules, R.NewFinal(target))
|
parsed = R.NewMatch(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parsed == nil {
|
||||||
|
return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rules, nil
|
return rules, nil
|
||||||
@ -374,33 +399,40 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||||||
|
|
||||||
for idx, server := range servers {
|
for idx, server := range servers {
|
||||||
// parse without scheme .e.g 8.8.8.8:53
|
// parse without scheme .e.g 8.8.8.8:53
|
||||||
if host, err := hostWithDefaultPort(server, "53"); err == nil {
|
if !strings.Contains(server, "://") {
|
||||||
nameservers = append(
|
server = "udp://" + server
|
||||||
nameservers,
|
|
||||||
dns.NameServer{Addr: host},
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(server)
|
u, err := url.Parse(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Scheme != "tls" {
|
var host, dnsNetType string
|
||||||
|
switch u.Scheme {
|
||||||
|
case "udp":
|
||||||
|
host, err = hostWithDefaultPort(u.Host, "53")
|
||||||
|
dnsNetType = "" // UDP
|
||||||
|
case "tcp":
|
||||||
|
host, err = hostWithDefaultPort(u.Host, "53")
|
||||||
|
dnsNetType = "tcp" // TCP
|
||||||
|
case "tls":
|
||||||
|
host, err = hostWithDefaultPort(u.Host, "853")
|
||||||
|
dnsNetType = "tcp-tls" // DNS over TLS
|
||||||
|
default:
|
||||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
host, err := hostWithDefaultPort(u.Host, "853")
|
|
||||||
nameservers = append(
|
nameservers = append(
|
||||||
nameservers,
|
nameservers,
|
||||||
dns.NameServer{
|
dns.NameServer{
|
||||||
Net: "tcp-tls",
|
Net: dnsNetType,
|
||||||
Addr: host,
|
Addr: host,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nameservers, nil
|
return nameservers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,13 +446,13 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
|||||||
Listen: cfg.Listen,
|
Listen: cfg.Listen,
|
||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
if nameserver, err := parseNameServer(cfg.NameServer); err == nil {
|
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
|
||||||
dnsCfg.NameServer = nameserver
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fallback, err := parseNameServer(cfg.Fallback); err == nil {
|
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback); err != nil {
|
||||||
dnsCfg.Fallback = fallback
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dnsCfg, nil
|
return dnsCfg, nil
|
||||||
|
@ -2,6 +2,7 @@ package constant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adapter Type
|
// Adapter Type
|
||||||
@ -15,6 +16,7 @@ const (
|
|||||||
Http
|
Http
|
||||||
URLTest
|
URLTest
|
||||||
Vmess
|
Vmess
|
||||||
|
LoadBalance
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerAdapter interface {
|
type ServerAdapter interface {
|
||||||
@ -22,13 +24,27 @@ type ServerAdapter interface {
|
|||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
Generator(metadata *Metadata) (net.Conn, error)
|
Dial(metadata *Metadata) (net.Conn, error)
|
||||||
|
Destroy()
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DelayHistory struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy interface {
|
||||||
|
ProxyAdapter
|
||||||
|
Alive() bool
|
||||||
|
DelayHistory() []DelayHistory
|
||||||
|
LastDelay() uint16
|
||||||
|
URLTest(url string) (uint16, error)
|
||||||
|
}
|
||||||
|
|
||||||
// AdapterType is enum of adapter type
|
// AdapterType is enum of adapter type
|
||||||
type AdapterType int
|
type AdapterType int
|
||||||
|
|
||||||
@ -52,6 +68,8 @@ func (at AdapterType) String() string {
|
|||||||
return "URLTest"
|
return "URLTest"
|
||||||
case Vmess:
|
case Vmess:
|
||||||
return "Vmess"
|
return "Vmess"
|
||||||
|
case LoadBalance:
|
||||||
|
return "LoadBalance"
|
||||||
default:
|
default:
|
||||||
return "Unknow"
|
return "Unknow"
|
||||||
}
|
}
|
||||||
|
@ -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,26 +54,19 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
cache, expireTime := r.cache.GetWithExpire(q.String())
|
cache, expireTime := r.cache.GetWithExpire(q.String())
|
||||||
if cache != nil {
|
if cache != nil {
|
||||||
msg = cache.(*D.Msg).Copy()
|
msg = cache.(*D.Msg).Copy()
|
||||||
if len(msg.Answer) > 0 {
|
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||||
ttl := uint32(expireTime.Sub(time.Now()).Seconds())
|
|
||||||
for _, answer := range msg.Answer {
|
|
||||||
answer.Header().Ttl = ttl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if msg != nil {
|
if msg == nil {
|
||||||
putMsgToCache(r.cache, q.String(), msg)
|
return
|
||||||
if r.mapping {
|
}
|
||||||
ips, err := r.msgToIP(msg)
|
|
||||||
if err != nil {
|
putMsgToCache(r.cache, q.String(), msg)
|
||||||
log.Debugln("[DNS] msg to ip error: %s", err.Error())
|
if r.mapping {
|
||||||
return
|
ips := r.msgToIP(msg)
|
||||||
}
|
for _, ip := range ips {
|
||||||
for _, ip := range ips {
|
putMsgToCache(r.cache, ip.String(), msg)
|
||||||
putMsgToCache(r.cache, ip.String(), msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -138,8 +130,7 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
|
|||||||
return nil, errors.New("GeoIP can't use")
|
return nil, errors.New("GeoIP can't use")
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := r.msgToIP(res.Msg)
|
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||||
if err == nil {
|
|
||||||
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
|
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
|
||||||
// release channel
|
// release channel
|
||||||
go func() { <-fallbackMsg }()
|
go func() { <-fallbackMsg }()
|
||||||
@ -172,18 +163,17 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []net.IP
|
ips := r.msgToIP(msg)
|
||||||
ips, err = r.msgToIP(msg)
|
if len(ips) == 0 {
|
||||||
if err != nil {
|
return nil, errors.New("can't found ip")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = ips[0]
|
ip = ips[0]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) {
|
func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||||
var ips []net.IP
|
ips := []net.IP{}
|
||||||
|
|
||||||
for _, answer := range msg.Answer {
|
for _, answer := range msg.Answer {
|
||||||
switch ans := answer.(type) {
|
switch ans := answer.(type) {
|
||||||
@ -194,11 +184,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ips) == 0 {
|
return ips
|
||||||
return nil, errors.New("Can't parse msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
||||||
@ -248,6 +234,7 @@ func transform(servers []NameServer) []*nameserver {
|
|||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
ClientSessionCache: globalSessionCache,
|
ClientSessionCache: globalSessionCache,
|
||||||
},
|
},
|
||||||
|
UDPSize: 4096,
|
||||||
},
|
},
|
||||||
Address: s.Addr,
|
Address: s.Addr,
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +22,11 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
|||||||
msg, err := s.r.Exchange(r)
|
msg, err := s.r.Exchange(r)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if len(r.Question) > 0 {
|
||||||
|
q := r.Question[0]
|
||||||
|
qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String())
|
||||||
|
log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err)
|
||||||
|
}
|
||||||
D.HandleFailed(w, r)
|
D.HandleFailed(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -27,13 +34,18 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
|||||||
w.WriteMsg(msg)
|
w.WriteMsg(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) setReslover(r *Resolver) {
|
||||||
|
s.r = r
|
||||||
|
}
|
||||||
|
|
||||||
func ReCreateServer(addr string, resolver *Resolver) error {
|
func ReCreateServer(addr string, resolver *Resolver) error {
|
||||||
if server.Server != nil {
|
if addr == address {
|
||||||
server.Shutdown()
|
server.setReslover(resolver)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr == address {
|
if server.Server != nil {
|
||||||
return nil
|
server.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, port, err := net.SplitHostPort(addr)
|
_, port, err := net.SplitHostPort(addr)
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.3.0
|
github.com/sirupsen/logrus v1.3.0
|
||||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
|
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
|
||||||
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect
|
golang.org/x/net v0.0.0-20181108082009-03003ca0c849
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||||
gopkg.in/eapache/channels.v1 v1.1.0
|
gopkg.in/eapache/channels.v1 v1.1.0
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
|
@ -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)
|
||||||
|
@ -59,7 +59,7 @@ func findProxyByName(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
proxies := T.Instance().Proxies()
|
proxies := T.Instance().Proxies()
|
||||||
render.JSON(w, r, map[string]map[string]C.Proxy{
|
render.JSON(w, r, render.M{
|
||||||
"proxies": proxies,
|
"proxies": proxies,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -81,12 +81,11 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
proxy := r.Context().Value(CtxKeyProxy).(*A.Proxy)
|
||||||
|
selector, ok := proxy.ProxyAdapter.(*A.Selector)
|
||||||
selector, ok := proxy.(*A.Selector)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.JSON(w, r, ErrBadRequest)
|
render.JSON(w, r, newError("Must be a Selector"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,9 +110,9 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||||
|
|
||||||
sigCh := make(chan int16)
|
sigCh := make(chan uint16)
|
||||||
go func() {
|
go func() {
|
||||||
t, err := A.DelayTest(proxy, url)
|
t, err := proxy.URLTest(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sigCh <- 0
|
sigCh <- 0
|
||||||
}
|
}
|
||||||
@ -129,7 +128,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
render.Status(r, http.StatusServiceUnavailable)
|
render.Status(r, http.StatusServiceUnavailable)
|
||||||
render.JSON(w, r, newError("An error occurred in the delay test"))
|
render.JSON(w, r, newError("An error occurred in the delay test"))
|
||||||
} else {
|
} else {
|
||||||
render.JSON(w, r, map[string]int16{
|
render.JSON(w, r, render.M{
|
||||||
"delay": t,
|
"delay": t,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ type Rule struct {
|
|||||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||||
rawRules := T.Instance().Rules()
|
rawRules := T.Instance().Rules()
|
||||||
|
|
||||||
var rules []Rule
|
rules := []Rule{}
|
||||||
for _, rule := range rawRules {
|
for _, rule := range rawRules {
|
||||||
rules = append(rules, Rule{
|
rules = append(rules, Rule{
|
||||||
Type: rule.RuleType().String(),
|
Type: rule.RuleType().String(),
|
||||||
@ -33,7 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render.JSON(w, r, map[string][]Rule{
|
render.JSON(w, r, render.M{
|
||||||
"rules": rules,
|
"rules": rules,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
12
main.go
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool {
|
|||||||
if i.isSourceIP {
|
if i.isSourceIP {
|
||||||
ip = metadata.SourceIP
|
ip = metadata.SourceIP
|
||||||
}
|
}
|
||||||
return i.ipnet.Contains(*ip)
|
return ip != nil && i.ipnet.Contains(*ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IPCIDR) Adapter() string {
|
func (i *IPCIDR) Adapter() string {
|
||||||
@ -38,6 +38,7 @@ func (i *IPCIDR) Payload() string {
|
|||||||
func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR {
|
func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR {
|
||||||
_, ipnet, err := net.ParseCIDR(s)
|
_, ipnet, err := net.ParseCIDR(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return &IPCIDR{
|
return &IPCIDR{
|
||||||
ipnet: ipnet,
|
ipnet: ipnet,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,15 +107,20 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) {
|
|||||||
return t.resolver.ResolveIP(host)
|
return t.resolver.ResolveIP(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) needLookupIP() bool {
|
func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
|
||||||
return t.hasResolver() && t.resolver.IsMapping()
|
return t.hasResolver() && t.resolver.IsMapping() && metadata.Host == "" && metadata.IP != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
||||||
defer localConn.Close()
|
defer localConn.Close()
|
||||||
metadata := localConn.Metadata()
|
metadata := localConn.Metadata()
|
||||||
|
|
||||||
if t.needLookupIP() {
|
if !metadata.Valid() {
|
||||||
|
log.Warnln("[Metadata] not valid: %#v", metadata)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.needLookupIP(metadata) {
|
||||||
host, exist := t.resolver.IPToHost(*metadata.IP)
|
host, exist := t.resolver.IPToHost(*metadata.IP)
|
||||||
if exist {
|
if exist {
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
@ -138,12 +143,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !metadata.Valid() {
|
remoConn, err := proxy.Dial(metadata)
|
||||||
log.Warnln("[Metadata] not valid: %#v", metadata)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
remoConn, err := proxy.Generator(metadata)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error())
|
log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error())
|
||||||
return
|
return
|
||||||
|
Reference in New Issue
Block a user