Compare commits

...

42 Commits

Author SHA1 Message Date
e7997a035b Chore: update README.md 2019-02-15 22:01:11 +08:00
287ad5bc53 Fix: vmess handshake block (#117) 2019-02-15 21:55:15 +08:00
c295c5e412 Feature: add load-balance group 2019-02-15 14:25:20 +08:00
8636a4f589 Fix: return 502 in http outbound (#116) 2019-02-14 11:37:47 +08:00
7a0717830c Fix: api invalid returning 2019-02-13 23:45:43 +08:00
5920b05752 Fix: crash when ip is nil 2019-02-12 12:29:33 +08:00
26a87f9d34 Fix: redir-host mode crash 2019-02-11 17:20:42 +08:00
8da19e81a4 Update: README.md 2019-02-11 15:55:17 +08:00
1339487ce4 Fix: tun2socks not lookup IP 2019-02-11 15:44:42 +08:00
2383cca2ce Feature: add v2ray-plugin 2019-02-11 15:25:39 +08:00
53b5ef199f Fix: parse proxies shadow variable 2019-02-04 09:39:17 +08:00
754df5ba9b Chore: update dependencies 2019-02-02 21:53:30 +08:00
1016355ef6 Chore: log dns server address when success 2019-02-02 21:37:36 +08:00
b594cbc68d Fix: parse ip string when use socks proxy (#100) 2019-02-02 21:11:27 +08:00
42d33fe629 Feature: SOURCE-IP-CIDR rule type (#96) 2019-02-02 21:03:13 +08:00
bfe51e46b0 Improve: lazy resolve ip 2019-02-02 20:47:38 +08:00
e30a628702 Fix: shadow variable 2019-01-30 20:22:52 +08:00
5581698908 Chore: improve programming style (#109) 2019-01-29 23:46:18 +08:00
36b5d1f18f Fix: DNS server returns the correct TTL 2019-01-25 15:38:14 +08:00
bd6c6a9ad1 Chore: print origin rule when format error (#92) 2019-01-14 10:35:11 +08:00
83fac44010 Fix: nghttpx return 400 error (#84) 2019-01-07 10:47:25 +08:00
15a77fa71b Fix: print log when start dns server failed (#87) 2019-01-06 14:31:42 +08:00
7768c5b933 Chore: add more platform release (#83) 2019-01-03 10:49:09 +08:00
4e91118a05 Feature: add freebsd release (#80)
add freebsd support
2018-12-31 20:57:21 +08:00
7b5e1f759c Fix: authentication with stream api 2018-12-29 14:11:54 +08:00
3f6c707aa9 Fix: patch config field 2018-12-23 20:25:49 +08:00
532ec88964 Chore: make a consistent code style 2018-12-23 00:42:08 +08:00
cb118d4371 Chore: improve outbound architecture 2018-12-22 23:56:42 +08:00
a7cfc81885 Fix: ignore some general configuration 2018-12-21 22:51:37 +08:00
551ab68c1e Fix: allow access to external-ui without authentication (#75) 2018-12-21 17:48:29 +08:00
ef6260282f Fix: parse external-ui 2018-12-21 10:55:21 +08:00
49635eab6c Chore: update external-ui explanation 2018-12-20 22:34:38 +08:00
a46041b81c Fix: force param make no sense 2018-12-20 22:23:31 +08:00
a6bbc67afb Feature: add custom ui support in API 2018-12-20 01:29:13 +08:00
afc4644dd1 Feature: FreeBSD compatibility patch (#63) 2018-12-18 10:37:00 +08:00
1607d3253f Feature: add websocket headers support in vmess 2018-12-11 00:25:05 +08:00
34c8655974 Fix: don't keepalive when connection is close (#65)
fixed #60
2018-12-10 11:48:57 +08:00
5e4b35e03a Chore: standardize API returns 2018-12-10 11:33:37 +08:00
fa9077969c Fix: dns crash & remove unused debug log 2018-12-10 11:00:52 +08:00
fcb1a7813a Fix: dns msg to ip 2018-12-06 13:29:43 +08:00
6f1bc3d65b Fix: add PATCH for CORS 2018-12-06 10:54:45 +08:00
2b93c9d4c9 Fix: resolve ip crash 2018-12-06 10:51:37 +08:00
45 changed files with 1305 additions and 489 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
bin/*
# Test binary, build with `go test -c` # Test binary, build with `go test -c`
*.test *.test

View File

@ -2,22 +2,88 @@ NAME=clash
BINDIR=bin BINDIR=bin
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s'
all: linux macos win64 PLATFORM_LIST = \
darwin-amd64 \
linux-386 \
linux-amd64 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle \
linux-mips64 \
linux-mips64le \
freebsd-386 \
freebsd-amd64
linux: WINDOWS_ARCH_LIST = \
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ windows-386 \
windows-amd64
macos: all: linux-amd64 darwin-amd64 windows-amd64 # Most used
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
win64: linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv6:
GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-hardfloat:
GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mipsle:
GOARCH=mipsle GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64:
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
releases: linux macos win64 windows-amd64:
chmod +x $(BINDIR)/$(NAME)-* GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
gzip $(BINDIR)/$(NAME)-linux
gzip $(BINDIR)/$(NAME)-macos
zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
$(gz_releases): %.gz : %
chmod +x $(BINDIR)/$(NAME)-$(basename $@)
gzip -f $(BINDIR)/$(NAME)-$(basename $@)
$(zip_releases): %.zip : %
zip -m -j $(BINDIR)/$(NAME)-$(basename $@).zip $(BINDIR)/$(NAME)-$(basename $@).exe
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases)
clean: clean:
rm $(BINDIR)/* rm $(BINDIR)/*

View File

@ -1,11 +1,9 @@
<h1 align="center"> <h1 align="center">
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200"> <img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200">
<br> <br>Clash<br>
Clash
<br>
</h1> </h1>
<h4 align="center">A rule based tunnel in Go.</h4> <h4 align="center">A rule-based tunnel in Go.</h4>
<p align="center"> <p align="center">
<a href="https://travis-ci.org/Dreamacro/clash"> <a href="https://travis-ci.org/Dreamacro/clash">
@ -13,7 +11,7 @@
alt="Travis-CI"> alt="Travis-CI">
</a> </a>
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash"> <a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square"> <img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
</a> </a>
<a href="https://github.com/Dreamacro/clash/releases"> <a href="https://github.com/Dreamacro/clash/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square"> <img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
@ -58,8 +56,6 @@ If you have Docker installed, you can run clash directly using `docker-compose`.
## Config ## Config
**NOTE: after v0.8.0, clash using yaml as configuration file**
The default configuration directory is `$HOME/.config/clash` The default configuration directory is `$HOME/.config/clash`
The name of the configuration file is `config.yml` The name of the configuration file is `config.yml`
@ -96,10 +92,14 @@ log-level: info
# A RESTful API for clash # A RESTful API for clash
external-controller: 127.0.0.1:9090 external-controller: 127.0.0.1:9090
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
# input is a relative path to the configuration directory or an absolute path
# external-ui: folder
# Secret for RESTful API (Optional) # Secret for RESTful API (Optional)
# secret: "" # secret: ""
dns: # dns:
# enable: true # set true to enable dns (default is false) # enable: true # set true to enable dns (default is false)
# ipv6: false # default is false # ipv6: false # default is false
# listen: 0.0.0.0:53 # listen: 0.0.0.0:53
@ -117,7 +117,32 @@ Proxy:
# support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20
# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 # In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305
- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } - { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" }
- { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com }
# old obfs configuration remove after prerelease
- name: "ss2"
type: ss
server: server
port: 443
cipher: AEAD_CHACHA20_POLY1305
password: "password"
plugin: obfs
plugin-opts:
mode: tls # or http
# host: bing.com
- name: "ss3"
type: ss
server: server
port: 443
cipher: AEAD_CHACHA20_POLY1305
password: "password"
plugin: v2ray-plugin
plugin-opts:
mode: websocket # no QUIC now
# tls: true # wss
# skip-cert-verify: true
# host: bing.com
# path: "/"
# vmess # vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none # cipher support auto/aes-128-gcm/chacha20-poly1305/none
@ -126,8 +151,8 @@ Proxy:
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true }
# with tls and skip-cert-verify # with tls and skip-cert-verify
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true }
# with ws # with ws-path and ws-headers
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } }
# with ws + tls # with ws + tls
- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true } - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true }
@ -151,25 +176,29 @@ Proxy:
Proxy Group: Proxy Group:
# url-test select which proxy will be used by benchmarking speed to a URL. # url-test select which proxy will be used by benchmarking speed to a URL.
- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } - { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 }
# 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"] }
# 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
- GEOIP,CN,DIRECT - GEOIP,CN,DIRECT
# FINAL would remove after prerelease # FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
- MATCH,Proxy - MATCH,auto
``` ```
## Thanks ## Thanks

View File

@ -32,8 +32,10 @@ func (h *HTTPAdapter) Conn() net.Conn {
// NewHTTP is HTTPAdapter generator // NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
metadata := parseHTTPAddr(request)
metadata.SourceIP = parseSourceIP(conn)
return &HTTPAdapter{ return &HTTPAdapter{
metadata: parseHTTPAddr(request), metadata: metadata,
R: request, R: request,
conn: conn, conn: conn,
} }

View File

@ -7,8 +7,10 @@ import (
// NewHTTPS is HTTPAdapter generator // NewHTTPS is HTTPAdapter generator
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
metadata := parseHTTPAddr(request)
metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{ return &SocketAdapter{
metadata: parseHTTPAddr(request), metadata: metadata,
conn: conn, conn: conn,
} }
} }

View File

@ -32,6 +32,7 @@ func (s *SocketAdapter) Conn() net.Conn {
func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter { func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter {
metadata := parseSocksAddr(target) metadata := parseSocksAddr(target)
metadata.Source = source metadata.Source = source
metadata.SourceIP = parseSourceIP(conn)
return &SocketAdapter{ return &SocketAdapter{
conn: conn, conn: conn,

View File

@ -61,3 +61,10 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata return metadata
} }
func parseSourceIP(conn net.Conn) *net.IP {
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
return &addr.IP
}
return nil
}

26
adapters/outbound/base.go Normal file
View File

@ -0,0 +1,26 @@
package adapters
import (
"encoding/json"
C "github.com/Dreamacro/clash/constant"
)
type Base struct {
name string
tp C.AdapterType
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}

View File

@ -1,38 +1,16 @@
package adapters package adapters
import ( import (
"encoding/json"
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// DirectAdapter is a directly connected adapter type Direct struct {
type DirectAdapter struct { *Base
conn net.Conn
} }
// Close is used to close connection func (d *Direct) Generator(metadata *C.Metadata) (net.Conn, error) {
func (d *DirectAdapter) Close() {
d.conn.Close()
}
// Conn is used to http request
func (d *DirectAdapter) Conn() net.Conn {
return d.conn
}
type Direct struct{}
func (d *Direct) Name() string {
return "DIRECT"
}
func (d *Direct) Type() C.AdapterType {
return C.Direct
}
func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err 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)
@ -40,18 +18,17 @@ func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er
c, err := net.DialTimeout("tcp", address, tcpTimeout) c, err := net.DialTimeout("tcp", address, tcpTimeout)
if err != nil { if err != nil {
return return nil, err
} }
tcpKeepAlive(c) tcpKeepAlive(c)
return &DirectAdapter{conn: c}, nil return c, nil
}
func (d *Direct) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": d.Type().String(),
})
} }
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{} return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
},
}
} }

View File

@ -3,6 +3,7 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"sync" "sync"
"time" "time"
@ -15,7 +16,7 @@ type proxy struct {
} }
type Fallback struct { type Fallback struct {
name string *Base
proxies []*proxy proxies []*proxy
rawURL string rawURL string
interval time.Duration interval time.Duration
@ -29,14 +30,6 @@ type FallbackOption struct {
Interval int `proxy:"interval"` Interval int `proxy:"interval"`
} }
func (f *Fallback) Name() string {
return f.name
}
func (f *Fallback) Type() C.AdapterType {
return C.Fallback
}
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
_, proxy := f.findNextValidProxy(0) _, proxy := f.findNextValidProxy(0)
if proxy != nil { if proxy != nil {
@ -45,7 +38,7 @@ func (f *Fallback) Now() string {
return f.proxies[0].RawProxy.Name() return f.proxies[0].RawProxy.Name()
} }
func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) {
idx := 0 idx := 0
var proxy *proxy var proxy *proxy
for { for {
@ -53,13 +46,13 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err
if proxy == nil { if proxy == nil {
break break
} }
adapter, err = proxy.RawProxy.Generator(metadata) adapter, err := proxy.RawProxy.Generator(metadata)
if err != nil { if err != nil {
proxy.Valid = false proxy.Valid = false
idx++ idx++
continue continue
} }
return return adapter, err
} }
return f.proxies[0].RawProxy.Generator(metadata) return f.proxies[0].RawProxy.Generator(metadata)
} }
@ -138,7 +131,10 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) {
} }
Fallback := &Fallback{ Fallback := &Fallback{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.Fallback,
},
proxies: warpperProxies, proxies: warpperProxies,
rawURL: option.URL, rawURL: option.URL,
interval: interval, interval: interval,

View File

@ -5,7 +5,6 @@ import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -16,23 +15,9 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// HTTPAdapter is a proxy adapter
type HTTPAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ha *HTTPAdapter) Close() {
ha.conn.Close()
}
func (ha *HTTPAdapter) Conn() net.Conn {
return ha.conn
}
type Http struct { type Http struct {
*Base
addr string addr string
name string
user string user string
pass string pass string
tls bool tls bool
@ -50,15 +35,7 @@ type HttpOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (h *Http) Name() string { func (h *Http) Generator(metadata *C.Metadata) (net.Conn, error) {
return h.name
}
func (h *Http) Type() C.AdapterType {
return C.Http
}
func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err 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)
@ -74,16 +51,16 @@ func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err erro
return nil, err return nil, err
} }
return &HTTPAdapter{conn: c}, nil return c, nil
} }
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 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
buf.WriteString("CONNECT ") addr := net.JoinHostPort(metadata.String(), metadata.Port)
buf.WriteString(net.JoinHostPort(metadata.Host, metadata.Port)) buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
buf.WriteString(" HTTP/1.1\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 != "" {
@ -118,12 +95,6 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
} }
func (h *Http) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": h.Type().String(),
})
}
func NewHttp(option HttpOption) *Http { func NewHttp(option HttpOption) *Http {
var tlsConfig *tls.Config var tlsConfig *tls.Config
if option.TLS { if option.TLS {
@ -137,8 +108,11 @@ func NewHttp(option HttpOption) *Http {
} }
return &Http{ return &Http{
Base: &Base{
name: option.Name,
tp: C.Http,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tls: option.TLS, tls: option.TLS,

View File

@ -0,0 +1,94 @@
package adapters
import (
"encoding/json"
"errors"
"net"
"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
}
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) Generator(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++ {
idx := jumpHash(key, buckets)
if proxy, err := lb.proxies[idx].Generator(metadata); err == nil {
return proxy, nil
}
key++
}
return lb.proxies[0].Generator(metadata)
}
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"`
}
func NewLoadBalance(name string, proxies []C.Proxy) (*LoadBalance, error) {
if len(proxies) == 0 {
return nil, errors.New("Provide at least one proxy")
}
return &LoadBalance{
Base: &Base{
name: name,
tp: C.LoadBalance,
},
proxies: proxies,
maxRetry: 3,
}, nil
}

View File

@ -1,7 +1,6 @@
package adapters package adapters
import ( import (
"encoding/json"
"io" "io"
"net" "net"
"time" "time"
@ -9,42 +8,21 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// RejectAdapter is a reject connected adapter
type RejectAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (r *RejectAdapter) Close() {}
// Conn is used to http request
func (r *RejectAdapter) Conn() net.Conn {
return r.conn
}
type Reject struct { type Reject struct {
*Base
} }
func (r *Reject) Name() string { func (r *Reject) Generator(metadata *C.Metadata) (net.Conn, error) {
return "REJECT" return &NopConn{}, nil
}
func (r *Reject) Type() C.AdapterType {
return C.Reject
}
func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
return &RejectAdapter{conn: &NopConn{}}, nil
}
func (r *Reject) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": r.Type().String(),
})
} }
func NewReject() *Reject { func NewReject() *Reject {
return &Reject{} return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
},
}
} }
type NopConn struct{} type NopConn struct{}

View File

@ -3,13 +3,14 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"sort" "sort"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Selector struct { type Selector struct {
name string *Base
selected C.Proxy selected C.Proxy
proxies map[string]C.Proxy proxies map[string]C.Proxy
} }
@ -19,15 +20,7 @@ type SelectorOption struct {
Proxies []string `proxy:"proxies"` Proxies []string `proxy:"proxies"`
} }
func (s *Selector) Name() string { func (s *Selector) Generator(metadata *C.Metadata) (net.Conn, error) {
return s.name
}
func (s *Selector) Type() C.AdapterType {
return C.Selector
}
func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) {
return s.selected.Generator(metadata) return s.selected.Generator(metadata)
} }
@ -68,7 +61,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) {
} }
s := &Selector{ s := &Selector{
name: name, Base: &Base{
name: name,
tp: C.Selector,
},
proxies: mapping, proxies: mapping,
selected: proxies[0], selected: proxies[0],
} }

View File

@ -2,74 +2,81 @@ package adapters
import ( import (
"bytes" "bytes"
"crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/simple-obfs" "github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/core"
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/go-shadowsocks2/socks"
) )
// ShadowsocksAdapter is a shadowsocks adapter
type ShadowsocksAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *ShadowsocksAdapter) Close() {
ss.conn.Close()
}
func (ss *ShadowsocksAdapter) Conn() net.Conn {
return ss.conn
}
type ShadowSocks struct { type ShadowSocks struct {
server string *Base
name string server string
obfs string cipher core.Cipher
obfsHost string
cipher core.Cipher // obfs
obfsMode string
obfsOption *simpleObfsOption
wsOption *v2rayObfs.WebsocketOption
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"` Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"` ObfsHost string `proxy:"obfs-host,omitempty"`
} }
func (ss *ShadowSocks) Name() string { type simpleObfsOption struct {
return ss.name Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
} }
func (ss *ShadowSocks) Type() C.AdapterType { type v2rayObfsOption struct {
return C.Shadowsocks Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
} }
func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (ss *ShadowSocks) Generator(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", ss.server) return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
} }
tcpKeepAlive(c) tcpKeepAlive(c)
switch ss.obfs { switch ss.obfsMode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost) c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http": case "http":
_, port, _ := net.SplitHostPort(ss.server) _, port, _ := net.SplitHostPort(ss.server)
c = obfs.NewHTTPObfs(c, ss.obfsHost, port) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewWebsocketObfs(c, ss.wsOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error())
}
} }
c = ss.cipher.StreamConn(c) c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata)) _, err = c.Write(serializesSocksAddr(metadata))
return &ShadowsocksAdapter{conn: c}, err return c, err
} }
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
@ -87,18 +94,62 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error())
} }
obfs := option.Obfs var wsOption *v2rayObfs.WebsocketOption
obfsHost := "bing.com" var obfsOption *simpleObfsOption
if option.ObfsHost != "" { obfsMode := ""
obfsHost = option.ObfsHost
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error())
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error())
}
obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
wsOption = &v2rayObfs.WebsocketOption{
Host: opts.Host,
Path: opts.Path,
TLSConfig: tlsConfig,
}
} }
return &ShadowSocks{ return &ShadowSocks{
server: server, Base: &Base{
name: option.Name, name: option.Name,
cipher: ciph, tp: C.Shadowsocks,
obfs: obfs, },
obfsHost: obfsHost, server: server,
cipher: ciph,
obfsMode: obfsMode,
wsOption: wsOption,
obfsOption: obfsOption,
}, nil }, nil
} }

View File

@ -3,7 +3,6 @@ package adapters
import ( import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -15,23 +14,9 @@ import (
"github.com/Dreamacro/go-shadowsocks2/socks" "github.com/Dreamacro/go-shadowsocks2/socks"
) )
// Socks5Adapter is a shadowsocks adapter
type Socks5Adapter struct {
conn net.Conn
}
// Close is used to close connection
func (ss *Socks5Adapter) Close() {
ss.conn.Close()
}
func (ss *Socks5Adapter) Conn() net.Conn {
return ss.conn
}
type Socks5 struct { type Socks5 struct {
*Base
addr string addr string
name string
user string user string
pass string pass string
tls bool tls bool
@ -49,15 +34,7 @@ type Socks5Option struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (ss *Socks5) Name() string { func (ss *Socks5) Generator(metadata *C.Metadata) (net.Conn, error) {
return ss.name
}
func (ss *Socks5) Type() C.AdapterType {
return C.Socks5
}
func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err 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 {
@ -73,13 +50,7 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e
if err := ss.shakeHand(metadata, c); err != nil { if err := ss.shakeHand(metadata, c); err != nil {
return nil, err return nil, err
} }
return &Socks5Adapter{conn: c}, nil return c, nil
}
func (ss *Socks5) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
} }
func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
@ -154,8 +125,11 @@ func NewSocks5(option Socks5Option) *Socks5 {
} }
return &Socks5{ return &Socks5{
Base: &Base{
name: option.Name,
tp: C.Socks5,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
name: option.Name,
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tls: option.TLS, tls: option.TLS,

View File

@ -3,6 +3,7 @@ package adapters
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -12,7 +13,7 @@ import (
) )
type URLTest struct { type URLTest struct {
name string *Base
proxies []C.Proxy proxies []C.Proxy
rawURL string rawURL string
fast C.Proxy fast C.Proxy
@ -28,19 +29,11 @@ type URLTestOption struct {
Interval int `proxy:"interval"` Interval int `proxy:"interval"`
} }
func (u *URLTest) Name() string {
return u.name
}
func (u *URLTest) Type() C.AdapterType {
return C.URLTest
}
func (u *URLTest) Now() string { func (u *URLTest) Now() string {
return u.fast.Name() return u.fast.Name()
} }
func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { func (u *URLTest) Generator(metadata *C.Metadata) (net.Conn, error) {
a, err := u.fast.Generator(metadata) a, err := u.fast.Generator(metadata)
if err != nil { if err != nil {
go u.speedTest() go u.speedTest()
@ -128,7 +121,10 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) {
interval := time.Duration(option.Interval) * time.Second interval := time.Duration(option.Interval) * time.Second
urlTest := &URLTest{ urlTest := &URLTest{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.URLTest,
},
proxies: proxies[:], proxies: proxies[:],
rawURL: option.URL, rawURL: option.URL,
fast: proxies[0], fast: proxies[0],

View File

@ -36,7 +36,7 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) {
defer instance.Close() defer instance.Close()
transport := &http.Transport{ transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) { Dial: func(string, string) (net.Conn, error) {
return instance.Conn(), nil return instance, nil
}, },
// from http.DefaultTransport // from http.DefaultTransport
MaxIdleConns: 100, MaxIdleConns: 100,

View File

@ -1,7 +1,6 @@
package adapters package adapters
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -11,83 +10,60 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
// VmessAdapter is a vmess adapter
type VmessAdapter struct {
conn net.Conn
}
// Close is used to close connection
func (v *VmessAdapter) Close() {
v.conn.Close()
}
func (v *VmessAdapter) Conn() net.Conn {
return v.conn
}
type Vmess struct { type Vmess struct {
name string *Base
server string server string
client *vmess.Client client *vmess.Client
} }
type VmessOption struct { type VmessOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"` AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
} }
func (v *Vmess) Name() string { func (v *Vmess) Generator(metadata *C.Metadata) (net.Conn, error) {
return v.name
}
func (v *Vmess) Type() C.AdapterType {
return C.Vmess
}
func (v *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err 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)
} }
tcpKeepAlive(c) tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata)) c, err = v.client.New(c, parseVmessAddr(metadata))
return &VmessAdapter{conn: c}, err return c, err
}
func (v *Vmess) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"type": v.Type().String(),
})
} }
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID, UUID: option.UUID,
AlterID: uint16(option.AlterID), AlterID: uint16(option.AlterID),
Security: security, Security: security,
TLS: option.TLS, TLS: option.TLS,
HostName: option.Server, HostName: option.Server,
Port: strconv.Itoa(option.Port), Port: strconv.Itoa(option.Port),
NetWork: option.Network, NetWork: option.Network,
WebSocketPath: option.WSPath, WebSocketPath: option.WSPath,
SkipCertVerify: option.SkipCertVerify, WebSocketHeaders: option.WSHeaders,
SessionCacahe: getClientSessionCache(), SkipCertVerify: option.SkipCertVerify,
SessionCacahe: getClientSessionCache(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Vmess{ return &Vmess{
name: option.Name, Base: &Base{
name: option.Name,
tp: C.Vmess,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client, client: client,
}, nil }, nil

15
common/cache/cache.go vendored
View File

@ -44,6 +44,21 @@ func (c *cache) Get(key interface{}) interface{} {
return elm.Payload return elm.Payload
} }
// GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) {
item, exist := c.mapping.Load(key)
if !exist {
return
}
elm := item.(*element)
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return
}
return elm.Payload, elm.Expired
}
func (c *cache) cleanup() { func (c *cache) cleanup() {
c.mapping.Range(func(k, v interface{}) bool { c.mapping.Range(func(k, v interface{}) bool {
key := k.(string) key := k.(string)

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

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

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

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

View File

@ -1,5 +1,7 @@
package structure package structure
// references: https://github.com/mitchellh/mapstructure
import ( import (
"fmt" "fmt"
"reflect" "reflect"
@ -70,6 +72,10 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error
return d.decodeBool(name, data, val) return d.decodeBool(name, data, val)
case reflect.Slice: case reflect.Slice:
return d.decodeSlice(name, data, val) return d.decodeSlice(name, data, val)
case reflect.Map:
return d.decodeMap(name, data, val)
case reflect.Interface:
return d.setInterface(name, data, val)
default: default:
return fmt.Errorf("type %s not support", val.Kind().String()) return fmt.Errorf("type %s not support", val.Kind().String())
} }
@ -158,3 +164,76 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
val.Set(valSlice) val.Set(valSlice)
return nil return nil
} }
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
valMap := val
if valMap.IsNil() {
mapType := reflect.MapOf(valKeyType, valElemType)
valMap = reflect.MakeMap(mapType)
}
dataVal := reflect.Indirect(reflect.ValueOf(data))
if dataVal.Kind() != reflect.Map {
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
}
return d.decodeMapFromMap(name, dataVal, val, valMap)
}
func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()
errors := make([]string, 0)
if dataVal.Len() == 0 {
if dataVal.IsNil() {
if !val.IsNil() {
val.Set(dataVal)
}
} else {
val.Set(valMap)
}
return nil
}
for _, k := range dataVal.MapKeys() {
fieldName := fmt.Sprintf("%s[%s]", name, k)
currentKey := reflect.Indirect(reflect.New(valKeyType))
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
errors = append(errors, err.Error())
continue
}
v := dataVal.MapIndex(k).Interface()
currentVal := reflect.Indirect(reflect.New(valElemType))
if err := d.decode(fieldName, v, currentVal); err != nil {
errors = append(errors, err.Error())
continue
}
valMap.SetMapIndex(currentKey, currentVal)
}
val.Set(valMap)
if len(errors) > 0 {
return fmt.Errorf(strings.Join(errors, ","))
}
return nil
}
func (d *Decoder) setInterface(name string, data interface{}, val reflect.Value) (err error) {
dataVal := reflect.ValueOf(data)
val.Set(dataVal)
return nil
}

View File

@ -0,0 +1,173 @@
package obfs
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
)
type SessionStatus = byte
const (
SessionStatusNew SessionStatus = 0x01
SessionStatusKeep SessionStatus = 0x02
SessionStatusEnd SessionStatus = 0x03
SessionStatusKeepAlive SessionStatus = 0x04
)
const (
OptionNone = byte(0x00)
OptionData = byte(0x01)
OptionError = byte(0x02)
)
type MuxOption struct {
ID [2]byte
Port uint16
Host string
Type string
}
// Mux is an mux-compatible client for v2ray-plugin, not a complete implementation
type Mux struct {
net.Conn
buf bytes.Buffer
id [2]byte
length [2]byte
status [2]byte
otb []byte
remain int
}
func (m *Mux) Read(b []byte) (int, error) {
if m.remain != 0 {
length := m.remain
if len(b) < m.remain {
length = len(b)
}
n, err := m.Conn.Read(b[:length])
if err != nil {
return 0, err
}
m.remain = m.remain - n
return n, nil
}
for {
_, err := io.ReadFull(m.Conn, m.length[:])
if err != nil {
return 0, err
}
length := binary.BigEndian.Uint16(m.length[:])
if length > 512 {
return 0, errors.New("invalid metalen")
}
_, err = io.ReadFull(m.Conn, m.id[:])
if err != nil {
return 0, err
}
_, err = m.Conn.Read(m.status[:])
if err != nil {
return 0, err
}
opcode := m.status[0]
if opcode == SessionStatusKeepAlive {
continue
}
opts := m.status[1]
if opts != OptionData {
continue
}
_, err = io.ReadFull(m.Conn, m.length[:])
if err != nil {
return 0, err
}
dataLen := int(binary.BigEndian.Uint16(m.length[:]))
m.remain = dataLen
if dataLen > len(b) {
dataLen = len(b)
}
n, err := m.Conn.Read(b[:dataLen])
m.remain -= n
return n, err
}
}
func (m *Mux) Write(b []byte) (int, error) {
if m.otb != nil {
// create a sub connection
if _, err := m.Conn.Write(m.otb); err != nil {
return 0, err
}
m.otb = nil
}
m.buf.Reset()
binary.Write(&m.buf, binary.BigEndian, uint16(4))
m.buf.Write(m.id[:])
m.buf.WriteByte(SessionStatusKeep)
m.buf.WriteByte(OptionData)
binary.Write(&m.buf, binary.BigEndian, uint16(len(b)))
m.buf.Write(b)
return m.Conn.Write(m.buf.Bytes())
}
func (m *Mux) Close() error {
_, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone})
if err != nil {
return err
}
return m.Conn.Close()
}
func NewMux(conn net.Conn, option MuxOption) *Mux {
buf := &bytes.Buffer{}
// fill empty length
buf.Write([]byte{0x0, 0x0})
buf.Write(option.ID[:])
buf.WriteByte(SessionStatusNew)
buf.WriteByte(OptionNone)
// tcp
netType := byte(0x1)
if option.Type == "udp" {
netType = byte(0x2)
}
buf.WriteByte(netType)
// port
binary.Write(buf, binary.BigEndian, option.Port)
// address
ip := net.ParseIP(option.Host)
if ip == nil {
buf.WriteByte(0x2)
buf.WriteString(option.Host)
} else if ipv4 := ip.To4(); ipv4 != nil {
buf.WriteByte(0x1)
buf.Write(ipv4)
} else {
buf.WriteByte(0x3)
buf.Write(ip.To16())
}
metadata := buf.Bytes()
binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2))
return &Mux{
Conn: conn,
id: option.ID,
otb: metadata,
}
}

View File

@ -0,0 +1,37 @@
package obfs
import (
"crypto/tls"
"net"
"github.com/Dreamacro/clash/component/vmess"
)
// WebsocketOption is options of websocket obfs
type WebsocketOption struct {
Host string
Path string
TLSConfig *tls.Config
}
// NewWebsocketObfs return a HTTPObfs
func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) {
config := &vmess.WebsocketConfig{
Host: option.Host,
Path: option.Path,
TLS: option.TLSConfig != nil,
TLSConfig: option.TLSConfig,
}
var err error
conn, err = vmess.NewWebsocketConn(conn, config)
if err != nil {
return nil, err
}
conn = NewMux(conn, MuxOption{
ID: [2]byte{0, 0},
Host: "127.0.0.1",
Port: 0,
})
return conn, nil
}

View File

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

View File

@ -69,22 +69,23 @@ type Client struct {
security Security security Security
tls bool tls bool
host string host string
wsConfig *websocketConfig wsConfig *WebsocketConfig
tlsConfig *tls.Config tlsConfig *tls.Config
} }
// Config of vmess // Config of vmess
type Config struct { type Config struct {
UUID string UUID string
AlterID uint16 AlterID uint16
Security string Security string
TLS bool TLS bool
HostName string HostName string
Port string Port string
NetWork string NetWork string
WebSocketPath string WebSocketPath string
SkipCertVerify bool WebSocketHeaders map[string]string
SessionCacahe tls.ClientSessionCache SkipCertVerify bool
SessionCacahe tls.ClientSessionCache
} }
// New return a Conn with net.Conn and DstAddr // New return a Conn with net.Conn and DstAddr
@ -92,14 +93,14 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
var err error var err error
r := rand.Intn(len(c.user)) r := rand.Intn(len(c.user))
if c.wsConfig != nil { if c.wsConfig != nil {
conn, err = newWebsocketConn(conn, c.wsConfig) conn, err = NewWebsocketConn(conn, c.wsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} 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
@ -144,13 +145,14 @@ func NewClient(config Config) (*Client, error) {
} }
} }
var wsConfig *websocketConfig var wsConfig *WebsocketConfig
if config.NetWork == "ws" { if config.NetWork == "ws" {
wsConfig = &websocketConfig{ wsConfig = &WebsocketConfig{
host: host, Host: host,
path: config.WebSocketPath, Path: config.WebSocketPath,
tls: config.TLS, Headers: config.WebSocketHeaders,
tlsConfig: tlsConfig, TLS: config.TLS,
TLSConfig: tlsConfig,
} }
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -18,11 +19,12 @@ type websocketConn struct {
remoteAddr net.Addr remoteAddr net.Addr
} }
type websocketConfig struct { type WebsocketConfig struct {
host string Host string
path string Path string
tls bool Headers map[string]string
tlsConfig *tls.Config TLS bool
TLSConfig *tls.Config
} }
// Read implements net.Conn.Read() // Read implements net.Conn.Read()
@ -100,7 +102,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
return wsc.conn.SetWriteDeadline(t) return wsc.conn.SetWriteDeadline(t)
} }
func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) { func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
dialer := &websocket.Dialer{ dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) { NetDial: func(network, addr string) (net.Conn, error) {
return conn, nil return conn, nil
@ -111,23 +113,30 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) {
} }
scheme := "ws" scheme := "ws"
if c.tls { if c.TLS {
scheme = "wss" scheme = "wss"
dialer.TLSClientConfig = c.tlsConfig dialer.TLSClientConfig = c.TLSConfig
} }
host, port, err := net.SplitHostPort(c.host) host, port, _ := net.SplitHostPort(c.Host)
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
host = c.host host = c.Host
} }
uri := url.URL{ uri := url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: host,
Path: c.path, Path: c.Path,
} }
wsConn, resp, err := dialer.Dial(uri.String(), nil) headers := http.Header{}
if c.Headers != nil {
for k, v := range c.Headers {
headers.Set(k, v)
}
}
wsConn, resp, err := dialer.Dial(uri.String(), headers)
if err != nil { if err != nil {
var reason string var reason string
if resp != nil { if resp != nil {

View File

@ -6,6 +6,7 @@ import (
"net" "net"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strings" "strings"
adapters "github.com/Dreamacro/clash/adapters/outbound" adapters "github.com/Dreamacro/clash/adapters/outbound"
@ -27,8 +28,9 @@ type General struct {
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
Mode T.Mode `json:"mode"` Mode T.Mode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"` LogLevel log.LogLevel `json:"log-level"`
ExternalController string `json:"external-controller,omitempty"` ExternalController string `json:"-"`
Secret string `json:"secret,omitempty"` ExternalUI string `json:"-"`
Secret string `json:"-"`
} }
// DNS config // DNS config
@ -66,9 +68,10 @@ type rawConfig struct {
Mode T.Mode `yaml:"mode"` Mode T.Mode `yaml:"mode"`
LogLevel log.LogLevel `yaml:"log-level"` LogLevel log.LogLevel `yaml:"log-level"`
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
DNS *rawDNS `yaml:"dns"` DNS rawDNS `yaml:"dns"`
Proxy []map[string]interface{} `yaml:"Proxy"` Proxy []map[string]interface{} `yaml:"Proxy"`
ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` ProxyGroup []map[string]interface{} `yaml:"Proxy Group"`
Rule []string `yaml:"Rule"` Rule []string `yaml:"Rule"`
@ -95,7 +98,7 @@ func readConfig(path string) (*rawConfig, error) {
Rule: []string{}, Rule: []string{},
Proxy: []map[string]interface{}{}, Proxy: []map[string]interface{}{},
ProxyGroup: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{},
DNS: &rawDNS{ DNS: rawDNS{
Enable: false, Enable: false,
}, },
} }
@ -145,10 +148,21 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
redirPort := cfg.RedirPort redirPort := cfg.RedirPort
allowLan := cfg.AllowLan allowLan := cfg.AllowLan
externalController := cfg.ExternalController externalController := cfg.ExternalController
externalUI := cfg.ExternalUI
secret := cfg.Secret secret := cfg.Secret
mode := cfg.Mode mode := cfg.Mode
logLevel := cfg.LogLevel logLevel := cfg.LogLevel
if externalUI != "" {
if !filepath.IsAbs(externalUI) {
externalUI = filepath.Join(C.Path.HomeDir(), externalUI)
}
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
}
}
general := &General{ general := &General{
Port: port, Port: port,
SocksPort: socksPort, SocksPort: socksPort,
@ -157,6 +171,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) {
Mode: mode, Mode: mode,
LogLevel: logLevel, LogLevel: logLevel,
ExternalController: externalController, ExternalController: externalController,
ExternalUI: externalUI,
Secret: secret, Secret: secret,
} }
return general, nil return general, nil
@ -180,7 +195,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
} }
var proxy C.Proxy var proxy C.Proxy
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 +251,9 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
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.Proxy
var err error ps := []C.Proxy{}
err := fmt.Errorf("can't parse")
switch groupType { switch groupType {
case "url-test": case "url-test":
urlTestOption := &adapters.URLTestOption{} urlTestOption := &adapters.URLTestOption{}
@ -245,7 +262,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
break break
} }
ps, err := getProxies(proxies, urlTestOption.Proxies) ps, err = getProxies(proxies, urlTestOption.Proxies)
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())
} }
@ -257,7 +274,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
break break
} }
ps, err := getProxies(proxies, selectorOption.Proxies) ps, err = getProxies(proxies, selectorOption.Proxies)
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())
} }
@ -269,11 +286,23 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
break break
} }
ps, err := getProxies(proxies, fallbackOption.Proxies) ps, err = getProxies(proxies, fallbackOption.Proxies)
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.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.Name, 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())
@ -281,7 +310,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) {
proxies[groupName] = group proxies[groupName] = group
} }
var ps []C.Proxy ps := []C.Proxy{}
for _, v := range proxies { for _, v := range proxies {
ps = append(ps, v) ps = append(ps, v)
} }
@ -309,7 +338,7 @@ 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] error: format invalid", idx) return nil, fmt.Errorf("Rules[%d] [- %s] error: format invalid", idx, line)
} }
rule = trimArr(rule) rule = trimArr(rule)
@ -323,7 +352,9 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) {
case "GEOIP": case "GEOIP":
rules = append(rules, R.NewGEOIP(payload, target)) rules = append(rules, R.NewGEOIP(payload, target))
case "IP-CIDR", "IP-CIDR6": case "IP-CIDR", "IP-CIDR6":
rules = append(rules, R.NewIPCIDR(payload, target)) rules = append(rules, R.NewIPCIDR(payload, target, false))
case "SOURCE-IP-CIDR":
rules = append(rules, R.NewIPCIDR(payload, target, true))
case "MATCH": case "MATCH":
fallthrough fallthrough
case "FINAL": case "FINAL":
@ -353,7 +384,6 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
func parseNameServer(servers []string) ([]dns.NameServer, error) { func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers := []dns.NameServer{} nameservers := []dns.NameServer{}
log.Debugln("%#v", servers)
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
@ -387,7 +417,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nameservers, nil return nameservers, nil
} }
func parseDNS(cfg *rawDNS) (*DNS, error) { func parseDNS(cfg rawDNS) (*DNS, error) {
if cfg.Enable && len(cfg.NameServer) == 0 { if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty")
} }

View File

@ -15,13 +15,9 @@ const (
Http Http
URLTest URLTest
Vmess Vmess
LoadBalance
) )
type ProxyAdapter interface {
Conn() net.Conn
Close()
}
type ServerAdapter interface { type ServerAdapter interface {
Metadata() *Metadata Metadata() *Metadata
Close() Close()
@ -30,7 +26,7 @@ type ServerAdapter interface {
type Proxy interface { type Proxy interface {
Name() string Name() string
Type() AdapterType Type() AdapterType
Generator(metadata *Metadata) (ProxyAdapter, error) Generator(metadata *Metadata) (net.Conn, error)
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
} }
@ -57,6 +53,8 @@ func (at AdapterType) String() string {
return "URLTest" return "URLTest"
case Vmess: case Vmess:
return "Vmess" return "Vmess"
case LoadBalance:
return "LoadBalance"
default: default:
return "Unknow" return "Unknow"
} }

View File

@ -33,15 +33,20 @@ type SourceType int
type Metadata struct { type Metadata struct {
NetWork NetWork NetWork NetWork
Source SourceType Source SourceType
SourceIP *net.IP
AddrType int AddrType int
Host string Host string
IP *net.IP IP *net.IP
Port string Port string
} }
func (addr *Metadata) String() string { func (m *Metadata) String() string {
if addr.Host == "" { if m.Host == "" {
return addr.IP.String() return m.IP.String()
} }
return addr.Host return m.Host
}
func (m *Metadata) Valid() bool {
return m.Host != "" || m.IP != nil
} }

View File

@ -7,6 +7,7 @@ const (
DomainKeyword DomainKeyword
GEOIP GEOIP
IPCIDR IPCIDR
SourceIPCIDR
FINAL FINAL
) )
@ -24,6 +25,8 @@ func (rt RuleType) String() string {
return "GEOIP" return "GEOIP"
case IPCIDR: case IPCIDR:
return "IPCIDR" return "IPCIDR"
case SourceIPCIDR:
return "SourceIPCIDR"
case FINAL: case FINAL:
return "FINAL" return "FINAL"
default: default:

View File

@ -52,9 +52,16 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
} }
q := m.Question[0] q := m.Question[0]
cache := r.cache.Get(q.String()) cache, expireTime := r.cache.GetWithExpire(q.String())
if cache != nil { if cache != nil {
return cache.(*D.Msg).Copy(), nil msg = cache.(*D.Msg).Copy()
if len(msg.Answer) > 0 {
ttl := uint32(expireTime.Sub(time.Now()).Seconds())
for _, answer := range msg.Answer {
answer.Header().Ttl = ttl
}
}
return
} }
defer func() { defer func() {
if msg != nil { if msg != nil {
@ -131,12 +138,14 @@ 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, _ := r.msgToIP(res.Msg) ips, err := r.msgToIP(res.Msg)
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { if err == nil {
// release channel if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
go func() { <-fallbackMsg }() // release channel
msg = res.Msg go func() { <-fallbackMsg }()
return msg = res.Msg
return msg, err
}
} }
} }
@ -146,6 +155,11 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) {
} }
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ip = net.ParseIP(host)
if ip != nil {
return ip, nil
}
query := &D.Msg{} query := &D.Msg{}
dnsType := D.TypeA dnsType := D.TypeA
if r.ipv6 { if r.ipv6 {
@ -172,20 +186,12 @@ func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) {
var ips []net.IP var ips []net.IP
for _, answer := range msg.Answer { for _, answer := range msg.Answer {
if r.ipv6 { switch ans := answer.(type) {
ans, ok := answer.(*D.AAAA) case *D.AAAA:
if !ok {
continue
}
ips = append(ips, ans.AAAA) ips = append(ips, ans.AAAA)
continue case *D.A:
ips = append(ips, ans.A)
} }
ans, ok := answer.(*D.A)
if !ok {
continue
}
ips = append(ips, ans.A)
} }
if len(ips) == 0 { if len(ips) == 0 {
@ -213,6 +219,10 @@ func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result {
return ch return ch
} }
func (r *Resolver) IsMapping() bool {
return r.mapping
}
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
@ -250,8 +260,6 @@ func New(config Config) *Resolver {
mmdb, _ = geoip2.Open(C.Path.MMDB()) mmdb, _ = geoip2.Open(C.Path.MMDB())
}) })
println(config.EnhancedMode)
r := &Resolver{ r := &Resolver{
main: transform(config.Main), main: transform(config.Main),
ipv6: config.IPv6, ipv6: config.IPv6,

14
go.mod
View File

@ -1,19 +1,19 @@
module github.com/Dreamacro/clash module github.com/Dreamacro/clash
require ( require (
github.com/Dreamacro/go-shadowsocks2 v0.1.2 github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112
github.com/eapache/queue v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect
github.com/go-chi/chi v3.3.3+incompatible github.com/go-chi/chi v4.0.1+incompatible
github.com/go-chi/cors v1.0.0 github.com/go-chi/cors v1.0.0
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v3.1.0+incompatible github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.0 github.com/gorilla/websocket v1.4.0
github.com/miekg/dns v1.1.0 github.com/miekg/dns v1.1.4
github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/geoip2-golang v1.2.1
github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/sirupsen/logrus v1.2.0 github.com/sirupsen/logrus v1.3.0
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 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

24
go.sum
View File

@ -1,39 +1,39 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.2 h1:8KgWbwAw5PJF+i6F3tI2iW/Em9WDtAuDG4obot8bGLM= github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 h1:1axYxE0ZLJy40+ulq46XQt7MaJDJr4iGer1NQz7jmKw=
github.com/Dreamacro/go-shadowsocks2 v0.1.2/go.mod h1:J5YbNUiKtaD7EJmQ4O9ruUTY9+IgrflPgm63K1nUE0I= github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8= github.com/go-chi/chi v4.0.1+incompatible h1:RSRC5qmFPtO90t7pTL0DBMNpZFsb/sHF3RXVlDgFisA=
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.0 h1:yv9O9RJbvVFkvW8PKYqp4x7HQkc5RWwmUY/L8MdUaIg= github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
github.com/miekg/dns v1.1.0/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=

View File

@ -27,7 +27,6 @@ func ApplyConfig(cfg *config.Config, force bool) {
} }
updateProxies(cfg.Proxies) updateProxies(cfg.Proxies)
updateRules(cfg.Rules) updateRules(cfg.Rules)
updateGeneral(cfg.General)
updateDNS(cfg.DNS) updateDNS(cfg.DNS)
} }
@ -56,7 +55,11 @@ func updateDNS(c *config.DNS) {
EnhancedMode: c.EnhancedMode, EnhancedMode: c.EnhancedMode,
}) })
T.Instance().SetResolver(r) T.Instance().SetResolver(r)
dns.ReCreateServer(c.Listen, r) if err := dns.ReCreateServer(c.Listen, r); err != nil {
log.Errorln("Start DNS server error: %s", err.Error())
return
}
log.Infoln("DNS server listening at: %s", c.Listen)
} }
func updateProxies(proxies map[string]C.Proxy) { func updateProxies(proxies map[string]C.Proxy) {

View File

@ -12,6 +12,10 @@ func Parse() error {
return err return err
} }
if cfg.General.ExternalUI != "" {
route.SetUIPath(cfg.General.ExternalUI)
}
if cfg.General.ExternalController != "" { if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.Secret) go route.Start(cfg.General.ExternalController, cfg.General.Secret)
} }

View File

@ -23,7 +23,7 @@ func configRouter() http.Handler {
type configSchema struct { type configSchema struct {
Port *int `json:"port"` Port *int `json:"port"`
SocksPort *int `json:"socket-port"` SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"` RedirPort *int `json:"redir-port"`
AllowLan *bool `json:"allow-lan"` AllowLan *bool `json:"allow-lan"`
Mode *T.Mode `json:"mode"` Mode *T.Mode `json:"mode"`
@ -32,7 +32,7 @@ type configSchema struct {
func getConfigs(w http.ResponseWriter, r *http.Request) { func getConfigs(w http.ResponseWriter, r *http.Request) {
general := executor.GetGeneral() general := executor.GetGeneral()
render.Respond(w, r, general) render.JSON(w, r, general)
} }
func pointerOrDefault(p *int, def int) int { func pointerOrDefault(p *int, def int) int {
@ -46,8 +46,8 @@ func pointerOrDefault(p *int, def int) int {
func patchConfigs(w http.ResponseWriter, r *http.Request) { func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{} general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil { if err := render.DecodeJSON(r.Body, general); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
@ -68,7 +68,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
log.SetLevel(*general.LogLevel) log.SetLevel(*general.LogLevel)
} }
w.WriteHeader(http.StatusNoContent) render.NoContent(w, r)
} }
type updateConfigRequest struct { type updateConfigRequest struct {
@ -78,25 +78,25 @@ type updateConfigRequest struct {
func updateConfigs(w http.ResponseWriter, r *http.Request) { func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := updateConfigRequest{} req := updateConfigRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
if !filepath.IsAbs(req.Path) { if !filepath.IsAbs(req.Path) {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, newError("path is not a absoluted path")) render.JSON(w, r, newError("path is not a absoluted path"))
return return
} }
force := r.URL.Query().Get("force") == "true" force := r.URL.Query().Get("force") == "true"
cfg, err := executor.ParseWithPath(req.Path) cfg, err := executor.ParseWithPath(req.Path)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, newError(err.Error())) render.JSON(w, r, newError(err.Error()))
return return
} }
executor.ApplyConfig(cfg, force) executor.ApplyConfig(cfg, force)
w.WriteHeader(http.StatusNoContent) render.NoContent(w, r)
} }

View File

@ -47,8 +47,8 @@ func findProxyByName(next http.Handler) http.Handler {
proxies := T.Instance().Proxies() proxies := T.Instance().Proxies()
proxy, exist := proxies[name] proxy, exist := proxies[name]
if !exist { if !exist {
w.WriteHeader(http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.Respond(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)
return return
} }
@ -59,14 +59,14 @@ 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.Respond(w, r, map[string]map[string]C.Proxy{ render.JSON(w, r, render.M{
"proxies": proxies, "proxies": proxies,
}) })
} }
func getProxy(w http.ResponseWriter, r *http.Request) { func getProxy(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
render.Respond(w, r, proxy) render.JSON(w, r, proxy)
} }
type UpdateProxyRequest struct { type UpdateProxyRequest struct {
@ -76,8 +76,8 @@ type UpdateProxyRequest struct {
func updateProxy(w http.ResponseWriter, r *http.Request) { func updateProxy(w http.ResponseWriter, r *http.Request) {
req := UpdateProxyRequest{} req := UpdateProxyRequest{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
@ -85,18 +85,18 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
selector, ok := proxy.(*A.Selector) selector, ok := proxy.(*A.Selector)
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
if err := selector.Set(req.Name); err != nil { if err := selector.Set(req.Name); err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error()))) render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
return return
} }
w.WriteHeader(http.StatusNoContent) render.NoContent(w, r)
} }
func getProxyDelay(w http.ResponseWriter, r *http.Request) { func getProxyDelay(w http.ResponseWriter, r *http.Request) {
@ -104,8 +104,8 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
url := query.Get("url") url := query.Get("url")
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }
@ -122,14 +122,14 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
select { select {
case <-time.After(time.Millisecond * time.Duration(timeout)): case <-time.After(time.Millisecond * time.Duration(timeout)):
w.WriteHeader(http.StatusRequestTimeout) render.Status(r, http.StatusRequestTimeout)
render.Respond(w, r, ErrRequestTimeout) render.JSON(w, r, ErrRequestTimeout)
case t := <-sigCh: case t := <-sigCh:
if t == 0 { if t == 0 {
w.WriteHeader(http.StatusServiceUnavailable) render.Status(r, http.StatusServiceUnavailable)
render.Respond(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.Respond(w, r, map[string]int16{ render.JSON(w, r, render.M{
"delay": t, "delay": t,
}) })
} }

View File

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

View File

@ -17,6 +17,8 @@ import (
var ( var (
serverSecret = "" serverSecret = ""
serverAddr = "" serverAddr = ""
uiPath = ""
) )
type Traffic struct { type Traffic struct {
@ -24,6 +26,10 @@ type Traffic struct {
Down int64 `json:"down"` Down int64 `json:"down"`
} }
func SetUIPath(path string) {
uiPath = path
}
func Start(addr string, secret string) { func Start(addr string, secret string) {
if serverAddr != "" { if serverAddr != "" {
return return
@ -36,18 +42,34 @@ func Start(addr string, secret string) {
cors := cors.New(cors.Options{ cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"}, AllowedHeaders: []string{"Content-Type", "Authorization"},
MaxAge: 300, MaxAge: 300,
}) })
r.Use(cors.Handler, authentication) root := chi.NewRouter().With(jsonContentType)
root.Get("/traffic", traffic)
root.Get("/logs", getLogs)
r.With(jsonContentType).Get("/traffic", traffic) r.Get("/", hello)
r.With(jsonContentType).Get("/logs", getLogs) r.Group(func(r chi.Router) {
r.Mount("/configs", configRouter()) r.Use(cors.Handler, authentication)
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter()) r.Mount("/", root)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter())
})
if uiPath != "" {
r.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
log.Infoln("RESTful API listening at: %s", addr) log.Infoln("RESTful API listening at: %s", addr)
err := http.ListenAndServe(addr, r) err := http.ListenAndServe(addr, r)
@ -77,8 +99,8 @@ func authentication(next http.Handler) http.Handler {
hasUnvalidHeader := text[0] != "Bearer" hasUnvalidHeader := text[0] != "Bearer"
hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret
if hasUnvalidHeader || hasUnvalidSecret { if hasUnvalidHeader || hasUnvalidSecret {
w.WriteHeader(http.StatusUnauthorized) render.Status(r, http.StatusUnauthorized)
render.Respond(w, r, ErrUnauthorized) render.JSON(w, r, ErrUnauthorized)
return return
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -86,8 +108,12 @@ func authentication(next http.Handler) http.Handler {
return http.HandlerFunc(fn) return http.HandlerFunc(fn)
} }
func hello(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"hello": "clash"})
}
func traffic(w http.ResponseWriter, r *http.Request) { func traffic(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) render.Status(r, http.StatusOK)
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
t := T.Instance().Traffic() t := T.Instance().Traffic()
@ -116,8 +142,8 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
level, ok := log.LogLevelMapping[levelText] level, ok := log.LogLevelMapping[levelText]
if !ok { if !ok {
w.WriteHeader(http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.Respond(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
} }

View File

@ -0,0 +1,52 @@
package redir
import (
"errors"
"net"
"syscall"
"unsafe"
"github.com/Dreamacro/go-shadowsocks2/socks"
)
const (
SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
)
func parserPacket(conn net.Conn) (socks.Addr, error) {
c, ok := conn.(*net.TCPConn)
if !ok {
return nil, errors.New("only work with TCP connection")
}
rc, err := c.SyscallConn()
if err != nil {
return nil, err
}
var addr socks.Addr
rc.Control(func(fd uintptr) {
addr, err = getorigdst(fd)
})
return addr, err
}
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
func getorigdst(fd uintptr) (socks.Addr, error) {
raw := syscall.RawSockaddrInet4{}
siz := unsafe.Sizeof(raw)
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0)
if err != 0 {
return nil, err
}
addr := make([]byte, 1+net.IPv4len+2)
addr[0] = socks.AtypIPv4
copy(addr[1:1+net.IPv4len], raw.Addr[:])
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
return addr, nil
}

View File

@ -7,20 +7,24 @@ import (
) )
type IPCIDR struct { type IPCIDR struct {
ipnet *net.IPNet ipnet *net.IPNet
adapter string adapter string
isSourceIP bool
} }
func (i *IPCIDR) RuleType() C.RuleType { func (i *IPCIDR) RuleType() C.RuleType {
if i.isSourceIP {
return C.SourceIPCIDR
}
return C.IPCIDR return C.IPCIDR
} }
func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool {
if metadata.IP == nil { ip := metadata.IP
return false if i.isSourceIP {
ip = metadata.SourceIP
} }
return ip != nil && i.ipnet.Contains(*ip)
return i.ipnet.Contains(*metadata.IP)
} }
func (i *IPCIDR) Adapter() string { func (i *IPCIDR) Adapter() string {
@ -31,12 +35,13 @@ func (i *IPCIDR) Payload() string {
return i.ipnet.String() return i.ipnet.String()
} }
func NewIPCIDR(s string, adapter string) *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 &IPCIDR{ return &IPCIDR{
ipnet: ipnet, ipnet: ipnet,
adapter: adapter, adapter: adapter,
isSourceIP: isSourceIP,
} }
} }

View File

@ -5,11 +5,11 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/adapters/inbound" adapters "github.com/Dreamacro/clash/adapters/inbound"
C "github.com/Dreamacro/clash/constant"
) )
const ( const (
@ -21,12 +21,17 @@ const (
var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }}
func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
conn := newTrafficTrack(proxy.Conn(), 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 = false
}
req.Header.Set("Connection", "close") req.Header.Set("Connection", "close")
req.RequestURI = "" req.RequestURI = ""
adapters.RemoveHopByHopHeaders(req.Header) adapters.RemoveHopByHopHeaders(req.Header)
@ -53,6 +58,10 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
break break
} }
if !keepalive {
break
}
req, err = http.ReadRequest(bufio.NewReader(request.Conn())) req, err = http.ReadRequest(bufio.NewReader(request.Conn()))
if err != nil { if err != nil {
break break
@ -66,8 +75,8 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter)
} }
} }
func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, outbound net.Conn) {
conn := newTrafficTrack(proxy.Conn(), t.traffic) conn := newTrafficTrack(outbound, t.traffic)
relay(request.Conn(), conn) relay(request.Conn(), conn)
} }

View File

@ -1,6 +1,7 @@
package tunnel package tunnel
import ( import (
"fmt"
"net" "net"
"sync" "sync"
"time" "time"
@ -10,7 +11,7 @@ import (
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"gopkg.in/eapache/channels.v1" channels "gopkg.in/eapache/channels.v1"
) )
var ( var (
@ -80,6 +81,10 @@ func (t *Tunnel) SetResolver(resolver *dns.Resolver) {
t.resolver = resolver t.resolver = resolver
} }
func (t *Tunnel) hasResolver() bool {
return t.resolver != nil
}
func (t *Tunnel) process() { func (t *Tunnel) process() {
queue := t.queue.Out() queue := t.queue.Out()
for { for {
@ -102,26 +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(metadata *C.Metadata) bool {
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 metadata.Source == C.REDIR && t.resolver != nil { 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
metadata.AddrType = C.AtypDomainName metadata.AddrType = C.AtypDomainName
} }
} else if metadata.IP == nil && metadata.AddrType == C.AtypDomainName {
ip, err := t.resolveIP(metadata.Host)
if err != nil {
log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
} else {
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.IP = &ip
}
} else {
log.Debugln("[DNS] unknown%#v", metadata)
} }
var proxy C.Proxy var proxy C.Proxy
@ -132,11 +131,21 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
proxy = t.proxies["GLOBAL"] proxy = t.proxies["GLOBAL"]
// Rule // Rule
default: default:
proxy = t.match(metadata) var err error
proxy, err = t.match(metadata)
if err != nil {
return
}
} }
if !metadata.Valid() {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
remoConn, err := proxy.Generator(metadata) remoConn, err := proxy.Generator(metadata)
if err != nil { if err != nil {
log.Warnln("Proxy[%s] connect [%s] error: %s", proxy.Name(), metadata.String(), err.Error()) log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error())
return return
} }
defer remoConn.Close() defer remoConn.Close()
@ -149,22 +158,33 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) {
} }
} }
func (t *Tunnel) match(metadata *C.Metadata) C.Proxy { func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.IP == nil
}
func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) {
t.configLock.RLock() t.configLock.RLock()
defer t.configLock.RUnlock() defer t.configLock.RUnlock()
for _, rule := range t.rules { for _, rule := range t.rules {
if rule.IsMatch(metadata) { if t.shouldResolveIP(rule, metadata) {
a, ok := t.proxies[rule.Adapter()] ip, err := t.resolveIP(metadata.Host)
if !ok { if err != nil {
continue return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error())
}
log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String())
metadata.IP = &ip
}
if rule.IsMatch(metadata) {
if a, ok := t.proxies[rule.Adapter()]; ok {
log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter())
return a, nil
} }
log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter())
return a
} }
} }
log.Infoln("%v doesn't match any rule using DIRECT", metadata.String()) log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String())
return t.proxies["DIRECT"] return t.proxies["DIRECT"], nil
} }
func newTunnel() *Tunnel { func newTunnel() *Tunnel {