Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
d3b14c325f | |||
4859b158b4 | |||
d65b51c62b | |||
a6444bb449 | |||
e09931dcf7 | |||
5bd189f2d0 | |||
8766287e72 | |||
10f9571c9e | |||
96a8259c42 | |||
68dd0622b8 | |||
558ac6b965 | |||
e773f95f21 | |||
314ce1c249 | |||
13275b1aa6 | |||
02d9169b5d | |||
7631bcc99e | |||
a32ee13fc9 | |||
b8ed738238 | |||
687c2a21cf | |||
ad18064e6b | |||
c9735ef75b | |||
b70882f01a | |||
5805334ccd | |||
c1b4382fe8 | |||
008743f20b |
5
.github/workflows/docker.yml
vendored
5
.github/workflows/docker.yml
vendored
@ -19,10 +19,9 @@ jobs:
|
||||
|
||||
- name: Set up docker buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v2
|
||||
uses: crazy-max/ghaction-docker-buildx@v3
|
||||
with:
|
||||
buildx-version: latest
|
||||
skip-cache: false
|
||||
qemu-version: latest
|
||||
|
||||
- name: Docker login
|
||||
@ -39,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Replace tag without `v`
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/github-script@v1
|
||||
uses: actions/github-script@v3
|
||||
id: version
|
||||
with:
|
||||
script: |
|
||||
|
5
.github/workflows/go.yml
vendored
5
.github/workflows/go.yml
vendored
@ -22,9 +22,12 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Get dependencies and run test
|
||||
- name: Get dependencies, run test and static check
|
||||
run: |
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go get -u honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck -- $(go list ./...)
|
||||
|
||||
- name: Build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v1
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
|
8
Makefile
8
Makefile
@ -4,7 +4,7 @@ VERSION=$(shell git describe --tags || echo "unknown version")
|
||||
BUILDTIME=$(shell date -u)
|
||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
|
||||
-w -s'
|
||||
-w -s -buildid='
|
||||
|
||||
PLATFORM_LIST = \
|
||||
darwin-amd64 \
|
||||
@ -25,7 +25,8 @@ PLATFORM_LIST = \
|
||||
|
||||
WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64
|
||||
windows-amd64 \
|
||||
windows-arm32v7
|
||||
|
||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||
|
||||
@ -82,6 +83,9 @@ windows-386:
|
||||
|
||||
windows-amd64:
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm32v7:
|
||||
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||
|
@ -13,10 +13,6 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultURLTestTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
addr string
|
||||
|
@ -31,6 +31,7 @@ type HttpOption struct {
|
||||
UserName string `proxy:"username,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
}
|
||||
|
||||
@ -114,10 +115,14 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||
func NewHttp(option HttpOption) *Http {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
sni := option.Server
|
||||
if option.SNI != "" {
|
||||
sni = option.SNI
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ClientSessionCache: getClientSessionCache(),
|
||||
ServerName: option.Server,
|
||||
ServerName: sni,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
|
||||
proxyType, existType := mapping["type"].(string)
|
||||
if !existType {
|
||||
return nil, fmt.Errorf("Missing type")
|
||||
return nil, fmt.Errorf("missing type")
|
||||
}
|
||||
|
||||
var proxy C.ProxyAdapter
|
||||
err := fmt.Errorf("Cannot parse")
|
||||
var (
|
||||
proxy C.ProxyAdapter
|
||||
err error
|
||||
)
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &ShadowSocksOption{}
|
||||
@ -72,7 +74,7 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
|
||||
}
|
||||
proxy, err = NewTrojan(*trojanOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -16,7 +16,9 @@ import (
|
||||
type Snell struct {
|
||||
*Base
|
||||
psk []byte
|
||||
pool *snell.Pool
|
||||
obfsOption *simpleObfsOption
|
||||
version int
|
||||
}
|
||||
|
||||
type SnellOption struct {
|
||||
@ -24,24 +26,47 @@ type SnellOption struct {
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Psk string `proxy:"psk"`
|
||||
Version int `proxy:"version,omitempty"`
|
||||
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
switch s.obfsOption.Mode {
|
||||
type streamOption struct {
|
||||
psk []byte
|
||||
version int
|
||||
addr string
|
||||
obfsOption *simpleObfsOption
|
||||
}
|
||||
|
||||
func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||
switch option.obfsOption.Mode {
|
||||
case "tls":
|
||||
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
|
||||
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
||||
case "http":
|
||||
_, port, _ := net.SplitHostPort(s.addr)
|
||||
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
|
||||
_, port, _ := net.SplitHostPort(option.addr)
|
||||
c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
|
||||
}
|
||||
c = snell.StreamConn(c, s.psk)
|
||||
return snell.StreamConn(c, option.psk, option.version)
|
||||
}
|
||||
|
||||
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(port))
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
if s.version == snell.Version2 {
|
||||
c, err := s.pool.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
err = snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
return NewConn(c, s), err
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
@ -66,7 +91,15 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
|
||||
}
|
||||
|
||||
return &Snell{
|
||||
// backward compatible
|
||||
if option.Version == 0 {
|
||||
option.Version = snell.DefaultSnellVersion
|
||||
}
|
||||
if option.Version != snell.Version1 && option.Version != snell.Version2 {
|
||||
return nil, fmt.Errorf("snell version error: %d", option.Version)
|
||||
}
|
||||
|
||||
s := &Snell{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
@ -74,5 +107,19 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
},
|
||||
psk: psk,
|
||||
obfsOption: obfsOption,
|
||||
}, nil
|
||||
version: option.Version,
|
||||
}
|
||||
|
||||
if option.Version == snell.Version2 {
|
||||
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
|
||||
c, err := dialer.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ type VmessOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
@ -44,6 +45,11 @@ type HTTPOptions struct {
|
||||
Headers map[string][]string `proxy:"headers,omitempty"`
|
||||
}
|
||||
|
||||
type HTTP2Options struct {
|
||||
Host []string `proxy:"host,omitempty"`
|
||||
Path string `proxy:"path,omitempty"`
|
||||
}
|
||||
|
||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
switch v.option.Network {
|
||||
@ -71,6 +77,25 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
}
|
||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||
case "http":
|
||||
// readability first, so just copy default TLS logic
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
httpOpts := &vmess.HTTPConfig{
|
||||
Host: host,
|
||||
@ -80,6 +105,30 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
}
|
||||
|
||||
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||
case "h2":
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, &tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h2Opts := &vmess.H2Config{
|
||||
Hosts: v.option.HTTP2Opts.Host,
|
||||
Path: v.option.HTTP2Opts.Path,
|
||||
}
|
||||
|
||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
@ -152,13 +201,16 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.Network == "h2" && !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||
}
|
||||
|
||||
return &Vmess{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Vmess,
|
||||
udp: true,
|
||||
udp: option.UDP,
|
||||
},
|
||||
client: client,
|
||||
option: &option,
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
var (
|
||||
errFormat = errors.New("format error")
|
||||
errType = errors.New("unsupport type")
|
||||
errMissUse = errors.New("`use` field should not be empty")
|
||||
errMissProxy = errors.New("`use` or `proxies` missing")
|
||||
errMissHealthCheck = errors.New("`url` or `interval` missing")
|
||||
errDuplicateProvider = errors.New("`duplicate provider name")
|
||||
@ -63,6 +62,10 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||
|
||||
providers = append(providers, pd)
|
||||
} else {
|
||||
if _, ok := providersMap[groupName]; ok {
|
||||
return nil, errDuplicateProvider
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
|
@ -22,7 +22,7 @@ type Relay struct {
|
||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxies := r.proxies(metadata)
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("Proxy does not exist")
|
||||
return nil, errors.New("proxy does not exist")
|
||||
}
|
||||
first := proxies[0]
|
||||
last := proxies[len(proxies)-1]
|
||||
|
@ -64,7 +64,7 @@ func (s *Selector) Set(name string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("Proxy does not exist")
|
||||
return errors.New("proxy not exist")
|
||||
}
|
||||
|
||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
|
@ -78,7 +78,7 @@ func (u *URLTest) fast() C.Proxy {
|
||||
}
|
||||
|
||||
// tolerance
|
||||
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay() + u.tolerance {
|
||||
if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
u.fastNode = fast
|
||||
}
|
||||
|
||||
|
@ -120,20 +120,20 @@ func proxiesParse(buf []byte) (interface{}, error) {
|
||||
}
|
||||
|
||||
if schema.Proxies == nil {
|
||||
return nil, errors.New("File must have a `proxies` field")
|
||||
return nil, errors.New("file must have a `proxies` field")
|
||||
}
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Proxy %d error: %w", idx, err)
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
}
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("File doesn't have any valid proxy")
|
||||
return nil, errors.New("file doesn't have any valid proxy")
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
|
19
common/cache/lrucache.go
vendored
19
common/cache/lrucache.go
vendored
@ -146,6 +146,23 @@ func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires tim
|
||||
c.maybeDeleteOldest()
|
||||
}
|
||||
|
||||
// CloneTo clone and overwrite elements to another LruCache
|
||||
func (c *LruCache) CloneTo(n *LruCache) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
n.lru = list.New()
|
||||
n.cache = make(map[interface{}]*list.Element)
|
||||
|
||||
for e := c.lru.Front(); e != nil; e = e.Next() {
|
||||
elm := e.Value.(*entry)
|
||||
n.cache[elm.key] = n.lru.PushBack(elm)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LruCache) get(key interface{}) *entry {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@ -171,7 +188,7 @@ func (c *LruCache) get(key interface{}) *entry {
|
||||
}
|
||||
|
||||
// Delete removes the value associated with a key.
|
||||
func (c *LruCache) Delete(key string) {
|
||||
func (c *LruCache) Delete(key interface{}) {
|
||||
c.mu.Lock()
|
||||
|
||||
if le, ok := c.cache[key]; ok {
|
||||
|
18
common/cache/lrucache_test.go
vendored
18
common/cache/lrucache_test.go
vendored
@ -164,3 +164,21 @@ func TestStale(t *testing.T) {
|
||||
assert.Equal(t, tenSecBefore, expires)
|
||||
assert.Equal(t, true, exist)
|
||||
}
|
||||
|
||||
func TestCloneTo(t *testing.T) {
|
||||
o := NewLRUCache(WithSize(10))
|
||||
o.Set("1", 1)
|
||||
o.Set("2", 2)
|
||||
|
||||
n := NewLRUCache(WithSize(2))
|
||||
n.Set("3", 3)
|
||||
n.Set("4", 4)
|
||||
|
||||
o.CloneTo(n)
|
||||
|
||||
assert.False(t, n.Exist("3"))
|
||||
assert.True(t, n.Exist("1"))
|
||||
|
||||
n.Set("5", 5)
|
||||
assert.False(t, n.Exist("1"))
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ func (alloc *Allocator) Put(buf []byte) error {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
||||
return errors.New("allocator Put() incorrect buffer size")
|
||||
}
|
||||
|
||||
//lint:ignore SA6002 ignore temporarily
|
||||
alloc.buffers[bits].Put(buf)
|
||||
return nil
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ func TestAllocGet(t *testing.T) {
|
||||
func TestAllocPut(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3, 3)), "put elem:3 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 4, 4)), "put elem:4 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65536, 65536)), "put elem:65536 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
}
|
||||
|
||||
func TestAllocPutThenGet(t *testing.T) {
|
||||
|
@ -24,6 +24,8 @@ type Result struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Do single.Do likes sync.singleFilght
|
||||
//lint:ignore ST1008 it likes sync.singleFilght
|
||||
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
|
||||
s.mux.Lock()
|
||||
now := time.Now()
|
||||
|
@ -147,28 +147,27 @@ func dualStackDialContext(ctx context.Context, network, address string) (net.Con
|
||||
go startRacer(ctx, network+"4", host, false)
|
||||
go startRacer(ctx, network+"6", host, true)
|
||||
|
||||
for {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
for res := range results {
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
|
||||
if primary.done && fallback.done {
|
||||
if primary.resolved {
|
||||
return nil, primary.error
|
||||
} else if fallback.resolved {
|
||||
return nil, fallback.error
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
|
||||
if primary.done && fallback.done {
|
||||
if primary.resolved {
|
||||
return nil, primary.error
|
||||
} else if fallback.resolved {
|
||||
return nil, fallback.error
|
||||
} else {
|
||||
return nil, primary.error
|
||||
}
|
||||
return nil, primary.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("never touched")
|
||||
}
|
||||
|
@ -89,6 +89,11 @@ func (p *Pool) Gateway() net.IP {
|
||||
return uintToIP(p.gateway)
|
||||
}
|
||||
|
||||
// PatchFrom clone cache from old pool
|
||||
func (p *Pool) PatchFrom(o *Pool) {
|
||||
o.cache.CloneTo(p.cache)
|
||||
}
|
||||
|
||||
func (p *Pool) get(host string) net.IP {
|
||||
current := p.offset
|
||||
for {
|
||||
@ -116,7 +121,7 @@ func ipToUint(ip net.IP) uint32 {
|
||||
}
|
||||
|
||||
func uintToIP(v uint32) net.IP {
|
||||
return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
|
||||
}
|
||||
|
||||
// New return Pool instance
|
||||
|
114
component/pool/pool.go
Normal file
114
component/pool/pool.go
Normal file
@ -0,0 +1,114 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Factory = func(context.Context) (interface{}, error)
|
||||
|
||||
type entry struct {
|
||||
elm interface{}
|
||||
time time.Time
|
||||
}
|
||||
|
||||
type Option func(*pool)
|
||||
|
||||
// WithEvict set the evict callback
|
||||
func WithEvict(cb func(interface{})) Option {
|
||||
return func(p *pool) {
|
||||
p.evict = cb
|
||||
}
|
||||
}
|
||||
|
||||
// WithAge defined element max age (millisecond)
|
||||
func WithAge(maxAge int64) Option {
|
||||
return func(p *pool) {
|
||||
p.maxAge = maxAge
|
||||
}
|
||||
}
|
||||
|
||||
// WithSize defined max size of Pool
|
||||
func WithSize(maxSize int) Option {
|
||||
return func(p *pool) {
|
||||
p.ch = make(chan interface{}, maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
// Pool is for GC, see New for detail
|
||||
type Pool struct {
|
||||
*pool
|
||||
}
|
||||
|
||||
type pool struct {
|
||||
ch chan interface{}
|
||||
factory Factory
|
||||
evict func(interface{})
|
||||
maxAge int64
|
||||
}
|
||||
|
||||
func (p *pool) GetContext(ctx context.Context) (interface{}, error) {
|
||||
now := time.Now()
|
||||
for {
|
||||
select {
|
||||
case item := <-p.ch:
|
||||
elm := item.(*entry)
|
||||
if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge {
|
||||
if p.evict != nil {
|
||||
p.evict(elm.elm)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return elm.elm, nil
|
||||
default:
|
||||
return p.factory(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pool) Get() (interface{}, error) {
|
||||
return p.GetContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *pool) Put(item interface{}) {
|
||||
e := &entry{
|
||||
elm: item,
|
||||
time: time.Now(),
|
||||
}
|
||||
|
||||
select {
|
||||
case p.ch <- e:
|
||||
return
|
||||
default:
|
||||
// pool is full
|
||||
if p.evict != nil {
|
||||
p.evict(item)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func recycle(p *Pool) {
|
||||
for item := range p.pool.ch {
|
||||
if p.pool.evict != nil {
|
||||
p.pool.evict(item.(*entry).elm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func New(factory Factory, options ...Option) *Pool {
|
||||
p := &pool{
|
||||
ch: make(chan interface{}, 10),
|
||||
factory: factory,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(p)
|
||||
}
|
||||
|
||||
P := &Pool{p}
|
||||
runtime.SetFinalizer(P, recycle)
|
||||
return P
|
||||
}
|
73
component/pool/pool_test.go
Normal file
73
component/pool/pool_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func lg() Factory {
|
||||
initial := -1
|
||||
return func(context.Context) (interface{}, error) {
|
||||
initial++
|
||||
return initial, nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestPool_Basic(t *testing.T) {
|
||||
g := lg()
|
||||
pool := New(g)
|
||||
|
||||
elm, _ := pool.Get()
|
||||
assert.Equal(t, 0, elm.(int))
|
||||
pool.Put(elm)
|
||||
elm, _ = pool.Get()
|
||||
assert.Equal(t, 0, elm.(int))
|
||||
elm, _ = pool.Get()
|
||||
assert.Equal(t, 1, elm.(int))
|
||||
}
|
||||
|
||||
func TestPool_MaxSize(t *testing.T) {
|
||||
g := lg()
|
||||
size := 5
|
||||
pool := New(g, WithSize(size))
|
||||
|
||||
items := []interface{}{}
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
item, _ := pool.Get()
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
extra, _ := pool.Get()
|
||||
assert.Equal(t, size, extra.(int))
|
||||
|
||||
for _, item := range items {
|
||||
pool.Put(item)
|
||||
}
|
||||
|
||||
pool.Put(extra)
|
||||
|
||||
for _, item := range items {
|
||||
elm, _ := pool.Get()
|
||||
assert.Equal(t, item.(int), elm.(int))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPool_MaxAge(t *testing.T) {
|
||||
g := lg()
|
||||
pool := New(g, WithAge(20))
|
||||
|
||||
elm, _ := pool.Get()
|
||||
pool.Put(elm)
|
||||
|
||||
elm, _ = pool.Get()
|
||||
assert.Equal(t, 0, elm.(int))
|
||||
pool.Put(elm)
|
||||
|
||||
time.Sleep(time.Millisecond * 22)
|
||||
elm, _ = pool.Get()
|
||||
assert.Equal(t, 1, elm.(int))
|
||||
}
|
46
component/resolver/enhancer.go
Normal file
46
component/resolver/enhancer.go
Normal file
@ -0,0 +1,46 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
var DefaultHostMapper Enhancer
|
||||
|
||||
type Enhancer interface {
|
||||
FakeIPEnabled() bool
|
||||
MappingEnabled() bool
|
||||
IsFakeIP(net.IP) bool
|
||||
FindHostByIP(net.IP) (string, bool)
|
||||
}
|
||||
|
||||
func FakeIPEnabled() bool {
|
||||
if mapper := DefaultHostMapper; mapper != nil {
|
||||
return mapper.FakeIPEnabled()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func MappingEnabled() bool {
|
||||
if mapper := DefaultHostMapper; mapper != nil {
|
||||
return mapper.MappingEnabled()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsFakeIP(ip net.IP) bool {
|
||||
if mapper := DefaultHostMapper; mapper != nil {
|
||||
return mapper.IsFakeIP(ip)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func FindHostByIP(ip net.IP) (string, bool) {
|
||||
if mapper := DefaultHostMapper; mapper != nil {
|
||||
return mapper.FindHostByIP(ip)
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
@ -1,21 +1,54 @@
|
||||
package snell
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type snellCipher struct {
|
||||
psk []byte
|
||||
keySize int
|
||||
makeAEAD func(key []byte) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
func (sc *snellCipher) KeySize() int { return 32 }
|
||||
func (sc *snellCipher) KeySize() int { return sc.keySize }
|
||||
func (sc *snellCipher) SaltSize() int { return 16 }
|
||||
func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
||||
return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize())))
|
||||
return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize()))
|
||||
}
|
||||
func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
||||
return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize())))
|
||||
return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize()))
|
||||
}
|
||||
|
||||
func snellKDF(psk, salt []byte, keySize int) []byte {
|
||||
// snell use a special kdf function
|
||||
return argon2.IDKey(psk, salt, 3, 8, 1, 32)[:keySize]
|
||||
}
|
||||
|
||||
func aesGCM(key []byte) (cipher.AEAD, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewGCM(blk)
|
||||
}
|
||||
|
||||
func NewAES128GCM(psk []byte) shadowaead.Cipher {
|
||||
return &snellCipher{
|
||||
psk: psk,
|
||||
keySize: 16,
|
||||
makeAEAD: aesGCM,
|
||||
}
|
||||
}
|
||||
|
||||
func NewChacha20Poly1305(psk []byte) shadowaead.Cipher {
|
||||
return &snellCipher{
|
||||
psk: psk,
|
||||
keySize: 32,
|
||||
makeAEAD: chacha20poly1305.New,
|
||||
}
|
||||
}
|
||||
|
80
component/snell/pool.go
Normal file
80
component/snell/pool.go
Normal file
@ -0,0 +1,80 @@
|
||||
package snell
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/pool"
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
pool *pool.Pool
|
||||
}
|
||||
|
||||
func (p *Pool) Get() (net.Conn, error) {
|
||||
return p.GetContext(context.Background())
|
||||
}
|
||||
|
||||
func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) {
|
||||
elm, err := p.pool.GetContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PoolConn{elm.(*Snell), p}, nil
|
||||
}
|
||||
|
||||
func (p *Pool) Put(conn net.Conn) {
|
||||
if err := HalfClose(conn); err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
p.pool.Put(conn)
|
||||
}
|
||||
|
||||
type PoolConn struct {
|
||||
*Snell
|
||||
pool *Pool
|
||||
}
|
||||
|
||||
func (pc *PoolConn) Read(b []byte) (int, error) {
|
||||
// save old status of reply (it mutable by Read)
|
||||
reply := pc.Snell.reply
|
||||
|
||||
n, err := pc.Snell.Read(b)
|
||||
if err == shadowaead.ErrZeroChunk {
|
||||
// if reply is false, it should be client halfclose.
|
||||
// ignore error and read data again.
|
||||
if !reply {
|
||||
pc.Snell.reply = false
|
||||
return pc.Snell.Read(b)
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (pc *PoolConn) Write(b []byte) (int, error) {
|
||||
return pc.Snell.Write(b)
|
||||
}
|
||||
|
||||
func (pc *PoolConn) Close() error {
|
||||
pc.pool.Put(pc.Snell)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPool(factory func(context.Context) (*Snell, error)) *Pool {
|
||||
p := pool.New(
|
||||
func(ctx context.Context) (interface{}, error) {
|
||||
return factory(ctx)
|
||||
},
|
||||
pool.WithAge(15000),
|
||||
pool.WithSize(10),
|
||||
pool.WithEvict(func(item interface{}) {
|
||||
item.(*Snell).Close()
|
||||
}),
|
||||
)
|
||||
|
||||
return &Pool{p}
|
||||
}
|
@ -10,14 +10,21 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
const (
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
Version1 = 1
|
||||
Version2 = 2
|
||||
DefaultSnellVersion = Version1
|
||||
)
|
||||
|
||||
const (
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
CommandConnectV2 byte = 5
|
||||
|
||||
CommandTunnel byte = 0
|
||||
CommandPong byte = 1
|
||||
CommandError byte = 2
|
||||
|
||||
Version byte = 1
|
||||
@ -25,6 +32,7 @@ const (
|
||||
|
||||
var (
|
||||
bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||
endSignal = []byte{}
|
||||
)
|
||||
|
||||
type Snell struct {
|
||||
@ -46,7 +54,7 @@ func (s *Snell) Read(b []byte) (int, error) {
|
||||
if s.buffer[0] == CommandTunnel {
|
||||
return s.Conn.Read(b)
|
||||
} else if s.buffer[0] != CommandError {
|
||||
return 0, errors.New("Command not support")
|
||||
return 0, errors.New("command not support")
|
||||
}
|
||||
|
||||
// CommandError
|
||||
@ -70,12 +78,16 @@ func (s *Snell) Read(b []byte) (int, error) {
|
||||
return 0, fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg))
|
||||
}
|
||||
|
||||
func WriteHeader(conn net.Conn, host string, port uint) error {
|
||||
func WriteHeader(conn net.Conn, host string, port uint, version int) error {
|
||||
buf := bufferPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer bufferPool.Put(buf)
|
||||
buf.WriteByte(Version)
|
||||
buf.WriteByte(CommandConnect)
|
||||
if version == Version2 {
|
||||
buf.WriteByte(CommandConnectV2)
|
||||
} else {
|
||||
buf.WriteByte(CommandConnect)
|
||||
}
|
||||
|
||||
// clientID length & id
|
||||
buf.WriteByte(0)
|
||||
@ -92,7 +104,24 @@ func WriteHeader(conn net.Conn, host string, port uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StreamConn(conn net.Conn, psk []byte) net.Conn {
|
||||
cipher := &snellCipher{psk, chacha20poly1305.New}
|
||||
// HalfClose works only on version2
|
||||
func HalfClose(conn net.Conn) error {
|
||||
if _, err := conn.Write(endSignal); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s, ok := conn.(*Snell); ok {
|
||||
s.reply = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
|
||||
var cipher shadowaead.Cipher
|
||||
if version == Version2 {
|
||||
cipher = NewAES128GCM(psk)
|
||||
} else {
|
||||
cipher = NewChacha20Poly1305(psk)
|
||||
}
|
||||
return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/ssr/tools"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type tlsAuthData struct {
|
||||
@ -63,7 +63,7 @@ func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) {
|
||||
var h [5]byte
|
||||
t.recvBuffer.Read(h[:])
|
||||
if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) {
|
||||
log.Println("incorrect magic number", h[:3], ", 0x170303 is expected")
|
||||
log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3])
|
||||
return nil, false, errTLS12TicketAuthIncorrectMagicNumber
|
||||
}
|
||||
size := int(binary.BigEndian.Uint16(h[3:5]))
|
||||
@ -287,5 +287,4 @@ func packData(buffer *bytes.Buffer, suffix []byte) {
|
||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF))
|
||||
buffer.Write(d)
|
||||
buffer.Write(suffix)
|
||||
return
|
||||
}
|
||||
|
@ -282,7 +282,6 @@ func (a *authChain) packData(outData []byte, data []byte, randLength int) {
|
||||
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
||||
a.lastClientHash = a.hmac(key, outData[:outLength])
|
||||
copy(outData[outLength:], a.lastClientHash[:2])
|
||||
return
|
||||
}
|
||||
|
||||
const authHeadLength = 4 + 8 + 4 + 16 + 4
|
||||
|
@ -86,7 +86,7 @@ func (r *aeadReader) Read(b []byte) (int, error) {
|
||||
|
||||
size := int(binary.BigEndian.Uint16(r.sizeBuf))
|
||||
if size > maxSize {
|
||||
return 0, errors.New("Buffer is larger than standard")
|
||||
return 0, errors.New("buffer is larger than standard")
|
||||
}
|
||||
|
||||
buf := pool.Get(size)
|
||||
|
@ -47,7 +47,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) {
|
||||
|
||||
size := int(binary.BigEndian.Uint16(cr.sizeBuf))
|
||||
if size > maxSize {
|
||||
return 0, errors.New("Buffer is larger than standard")
|
||||
return 0, errors.New("buffer is larger than standard")
|
||||
}
|
||||
|
||||
if len(b) >= size {
|
||||
|
111
component/vmess/h2.go
Normal file
111
component/vmess/h2.go
Normal file
@ -0,0 +1,111 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type h2Conn struct {
|
||||
net.Conn
|
||||
*http2.ClientConn
|
||||
pwriter *io.PipeWriter
|
||||
res *http.Response
|
||||
cfg *H2Config
|
||||
}
|
||||
|
||||
type H2Config struct {
|
||||
Hosts []string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (hc *h2Conn) establishConn() error {
|
||||
preader, pwriter := io.Pipe()
|
||||
|
||||
host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))]
|
||||
path := hc.cfg.Path
|
||||
// TODO: connect use VMess Host instead of H2 Host
|
||||
req := http.Request{
|
||||
Method: "PUT",
|
||||
Host: host,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: host,
|
||||
Path: path,
|
||||
},
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Body: preader,
|
||||
Header: map[string][]string{
|
||||
"Accept-Encoding": {"identity"},
|
||||
},
|
||||
}
|
||||
|
||||
res, err := hc.ClientConn.RoundTrip(&req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hc.pwriter = pwriter
|
||||
hc.res = res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read implements net.Conn.Read()
|
||||
func (hc *h2Conn) Read(b []byte) (int, error) {
|
||||
if hc.res != nil && !hc.res.Close {
|
||||
n, err := hc.res.Body.Read(b)
|
||||
return n, err
|
||||
}
|
||||
|
||||
if err := hc.establishConn(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hc.res.Body.Read(b)
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (hc *h2Conn) Write(b []byte) (int, error) {
|
||||
if hc.pwriter != nil {
|
||||
return hc.pwriter.Write(b)
|
||||
}
|
||||
|
||||
if err := hc.establishConn(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hc.pwriter.Write(b)
|
||||
}
|
||||
|
||||
func (hc *h2Conn) Close() error {
|
||||
if err := hc.pwriter.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hc.Conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) {
|
||||
transport := &http2.Transport{}
|
||||
|
||||
cconn, err := transport.NewClientConn(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &h2Conn{
|
||||
Conn: conn,
|
||||
ClientConn: cconn,
|
||||
cfg: cfg,
|
||||
}, nil
|
||||
}
|
@ -9,6 +9,7 @@ type TLSConfig struct {
|
||||
Host string
|
||||
SkipCertVerify bool
|
||||
SessionCache tls.ClientSessionCache
|
||||
NextProtos []string
|
||||
}
|
||||
|
||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||
@ -16,6 +17,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||
ServerName: cfg.Host,
|
||||
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||
ClientSessionCache: cfg.SessionCache,
|
||||
NextProtos: cfg.NextProtos,
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
@ -1,12 +1,10 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
@ -37,11 +35,6 @@ var CipherMapping = map[string]byte{
|
||||
"chacha20-poly1305": SecurityCHACHA20POLY1305,
|
||||
}
|
||||
|
||||
var (
|
||||
clientSessionCache tls.ClientSessionCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Command types
|
||||
const (
|
||||
CommandTCP byte = 1
|
||||
@ -106,7 +99,7 @@ func NewClient(config Config) (*Client, error) {
|
||||
security = SecurityAES128GCM
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
||||
return nil, fmt.Errorf("unknown security type: %s", config.Security)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
|
@ -73,7 +73,7 @@ func (wsc *websocketConn) Close() error {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ","))
|
||||
return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -159,7 +159,7 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||
if resp != nil {
|
||||
reason = resp.Status
|
||||
}
|
||||
return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason)
|
||||
return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason)
|
||||
}
|
||||
|
||||
return &websocketConn{
|
||||
|
@ -69,6 +69,7 @@ type DNS struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
@ -103,6 +104,7 @@ type RawDNS struct {
|
||||
type RawFallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
@ -264,11 +266,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for idx, mapping := range proxiesConfig {
|
||||
proxy, err := outbound.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Proxy %d: %w", idx, err)
|
||||
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
|
||||
}
|
||||
|
||||
if _, exist := proxies[proxy.Name()]; exist {
|
||||
return nil, nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name())
|
||||
return nil, nil, fmt.Errorf("proxy %s is the duplicate name", proxy.Name())
|
||||
}
|
||||
proxies[proxy.Name()] = proxy
|
||||
proxyList = append(proxyList, proxy.Name())
|
||||
@ -278,7 +280,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for idx, mapping := range groupsConfig {
|
||||
groupName, existName := mapping["name"].(string)
|
||||
if !existName {
|
||||
return nil, nil, fmt.Errorf("ProxyGroup %d: missing name", idx)
|
||||
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
|
||||
}
|
||||
proxyList = append(proxyList, groupName)
|
||||
}
|
||||
@ -313,12 +315,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
for idx, mapping := range groupsConfig {
|
||||
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("ProxyGroup[%d]: %w", idx, err)
|
||||
return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err)
|
||||
}
|
||||
|
||||
groupName := group.Name()
|
||||
if _, exist := proxies[groupName]; exist {
|
||||
return nil, nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName)
|
||||
return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName)
|
||||
}
|
||||
|
||||
proxies[groupName] = outbound.NewProxy(group)
|
||||
@ -373,11 +375,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
target = rule[2]
|
||||
params = rule[3:]
|
||||
default:
|
||||
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
}
|
||||
|
||||
if _, ok := proxies[target]; !ok {
|
||||
return nil, fmt.Errorf("Rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||
}
|
||||
|
||||
rule = trimArr(rule)
|
||||
@ -389,7 +391,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||
log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||
}
|
||||
|
||||
rules = append(rules, parsed)
|
||||
@ -403,7 +405,7 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
|
||||
|
||||
// add default hosts
|
||||
if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil {
|
||||
println(err.Error())
|
||||
log.Errorln("insert localhost to host error: %s", err.Error())
|
||||
}
|
||||
|
||||
if len(cfg.Hosts) != 0 {
|
||||
@ -499,7 +501,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||
|
||||
func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
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")
|
||||
}
|
||||
|
||||
dnsCfg := &DNS{
|
||||
@ -561,6 +563,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
}
|
||||
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
|
||||
|
||||
if cfg.UseHosts {
|
||||
dnsCfg.Hosts = hosts
|
||||
|
@ -32,18 +32,18 @@ func initMMDB() error {
|
||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find MMDB, start download")
|
||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
||||
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !mmdb.Verify() {
|
||||
log.Warnln("MMDB invalid, remove and download")
|
||||
if err := os.Remove(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error())
|
||||
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||
return fmt.Errorf("Can't download MMDB: %s", err.Error())
|
||||
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ func Init(dir string) error {
|
||||
// initial homedir
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
||||
return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error())
|
||||
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ func Init(dir string) error {
|
||||
log.Infoln("Can't find config, create a initial config file")
|
||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error())
|
||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
||||
}
|
||||
f.Write([]byte(`port: 7890`))
|
||||
f.Close()
|
||||
@ -72,7 +72,7 @@ func Init(dir string) error {
|
||||
|
||||
// initial mmdb
|
||||
if err := initMMDB(); err != nil {
|
||||
return fmt.Errorf("Can't initial MMDB: %w", err)
|
||||
return fmt.Errorf("can't initial MMDB: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -15,15 +15,6 @@ func trimArr(arr []string) (r []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func or(pointers ...*int) *int {
|
||||
for _, p := range pointers {
|
||||
if p != nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return pointers[len(pointers)-1]
|
||||
}
|
||||
|
||||
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
|
||||
// Meanwhile, record the original index in the config file.
|
||||
// If loop is detected, return an error with location of loop.
|
||||
@ -153,5 +144,5 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error {
|
||||
loopElements = append(loopElements, name)
|
||||
delete(graph, name)
|
||||
}
|
||||
return fmt.Errorf("Loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements)
|
||||
return fmt.Errorf("loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements)
|
||||
}
|
||||
|
76
dns/enhancer.go
Normal file
76
dns/enhancer.go
Normal file
@ -0,0 +1,76 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
)
|
||||
|
||||
type ResolverEnhancer struct {
|
||||
mode EnhancedMode
|
||||
fakePool *fakeip.Pool
|
||||
mapping *cache.LruCache
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) FakeIPEnabled() bool {
|
||||
return h.mode == FAKEIP
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) MappingEnabled() bool {
|
||||
return h.mode == FAKEIP || h.mode == MAPPING
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool {
|
||||
if !h.FakeIPEnabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
if pool := h.fakePool; pool != nil {
|
||||
return pool.Exist(ip)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) {
|
||||
if pool := h.fakePool; pool != nil {
|
||||
if host, existed := pool.LookBack(ip); existed {
|
||||
return host, true
|
||||
}
|
||||
}
|
||||
|
||||
if mapping := h.mapping; mapping != nil {
|
||||
if host, existed := h.mapping.Get(ip.String()); existed {
|
||||
return host.(string), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) {
|
||||
if h.mapping != nil && o.mapping != nil {
|
||||
o.mapping.CloneTo(h.mapping)
|
||||
}
|
||||
|
||||
if h.fakePool != nil && o.fakePool != nil {
|
||||
h.fakePool.PatchFrom(o.fakePool)
|
||||
}
|
||||
}
|
||||
|
||||
func NewEnhancer(cfg Config) *ResolverEnhancer {
|
||||
var fakePool *fakeip.Pool
|
||||
var mapping *cache.LruCache
|
||||
|
||||
if cfg.EnhancedMode != NORMAL {
|
||||
fakePool = cfg.Pool
|
||||
mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true))
|
||||
}
|
||||
|
||||
return &ResolverEnhancer{
|
||||
mode: cfg.EnhancedMode,
|
||||
fakePool: fakePool,
|
||||
mapping: mapping,
|
||||
}
|
||||
}
|
@ -4,9 +4,10 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
|
||||
type fallbackFilter interface {
|
||||
type fallbackIPFilter interface {
|
||||
Match(net.IP) bool
|
||||
}
|
||||
|
||||
@ -24,3 +25,22 @@ type ipnetFilter struct {
|
||||
func (inf *ipnetFilter) Match(ip net.IP) bool {
|
||||
return inf.ipnet.Contains(ip)
|
||||
}
|
||||
|
||||
type fallbackDomainFilter interface {
|
||||
Match(domain string) bool
|
||||
}
|
||||
type domainFilter struct {
|
||||
tree *trie.DomainTrie
|
||||
}
|
||||
|
||||
func NewDomainFilter(domains []string) *domainFilter {
|
||||
df := domainFilter{tree: trie.New()}
|
||||
for _, domain := range domains {
|
||||
df.tree.Insert(domain, "")
|
||||
}
|
||||
return &df
|
||||
}
|
||||
|
||||
func (df *domainFilter) Match(domain string) bool {
|
||||
return df.tree.Search(domain) != nil
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package dns
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -11,23 +13,21 @@ import (
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type handler func(w D.ResponseWriter, r *D.Msg)
|
||||
type handler func(r *D.Msg) (*D.Msg, error)
|
||||
type middleware func(next handler) handler
|
||||
|
||||
func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
return func(next handler) handler {
|
||||
return func(w D.ResponseWriter, r *D.Msg) {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
if !isIPRequest(q) {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
record := hosts.Search(strings.TrimRight(q.Name, "."))
|
||||
if record == nil {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
ip := record.Data.(net.IP)
|
||||
@ -46,25 +46,67 @@ func withHosts(hosts *trie.DomainTrie) middleware {
|
||||
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
msg.SetRcode(r, D.RcodeSuccess)
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withMapping(mapping *cache.LruCache) middleware {
|
||||
return func(next handler) handler {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
if !isIPRequest(q) {
|
||||
return next(r)
|
||||
}
|
||||
|
||||
msg, err := next(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
|
||||
for _, ans := range msg.Answer {
|
||||
var ip net.IP
|
||||
var ttl uint32
|
||||
|
||||
switch a := ans.(type) {
|
||||
case *D.A:
|
||||
ip = a.A
|
||||
ttl = a.Hdr.Ttl
|
||||
case *D.AAAA:
|
||||
ip = a.AAAA
|
||||
ttl = a.Hdr.Ttl
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
mapping.SetWithExpire(ip.String(), host, time.Now().Add(time.Second*time.Duration(ttl)))
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
return func(next handler) handler {
|
||||
return func(w D.ResponseWriter, r *D.Msg) {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
if fakePool.LookupHost(host) {
|
||||
return next(r)
|
||||
}
|
||||
|
||||
if q.Qtype == D.TypeAAAA {
|
||||
msg := &D.Msg{}
|
||||
msg.Answer = []D.RR{}
|
||||
@ -73,17 +115,9 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return msg, nil
|
||||
} else if q.Qtype != D.TypeA {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
host := strings.TrimRight(q.Name, ".")
|
||||
if fakePool.LookupHost(host) {
|
||||
next(w, r)
|
||||
return
|
||||
return next(r)
|
||||
}
|
||||
|
||||
rr := &D.A{}
|
||||
@ -98,14 +132,13 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withResolver(resolver *Resolver) handler {
|
||||
return func(w D.ResponseWriter, r *D.Msg) {
|
||||
return func(r *D.Msg) (*D.Msg, error) {
|
||||
q := r.Question[0]
|
||||
|
||||
// return a empty AAAA msg when ipv6 disabled
|
||||
@ -117,20 +150,18 @@ func withResolver(resolver *Resolver) handler {
|
||||
msg.Authoritative = true
|
||||
msg.RecursionAvailable = true
|
||||
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
msg, err := resolver.Exchange(r)
|
||||
if err != nil {
|
||||
log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err)
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
return msg, err
|
||||
}
|
||||
msg.SetRcode(r, msg.Rcode)
|
||||
msg.Authoritative = true
|
||||
w.WriteMsg(msg)
|
||||
return
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,15 +176,19 @@ func compose(middlewares []middleware, endpoint handler) handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func newHandler(resolver *Resolver) handler {
|
||||
func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
||||
middlewares := []middleware{}
|
||||
|
||||
if resolver.hosts != nil {
|
||||
middlewares = append(middlewares, withHosts(resolver.hosts))
|
||||
}
|
||||
|
||||
if resolver.FakeIPEnabled() {
|
||||
middlewares = append(middlewares, withFakeIP(resolver.pool))
|
||||
if mapper.mode == FAKEIP {
|
||||
middlewares = append(middlewares, withFakeIP(mapper.fakePool))
|
||||
}
|
||||
|
||||
if mapper.mode != NORMAL {
|
||||
middlewares = append(middlewares, withMapping(mapper.mapping))
|
||||
}
|
||||
|
||||
return compose(middlewares, withResolver(resolver))
|
||||
|
133
dns/resolver.go
133
dns/resolver.go
@ -35,16 +35,14 @@ type result struct {
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
ipv6 bool
|
||||
mapping bool
|
||||
fakeip bool
|
||||
hosts *trie.DomainTrie
|
||||
pool *fakeip.Pool
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackFilters []fallbackFilter
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
ipv6 bool
|
||||
hosts *trie.DomainTrie
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackDomainFilters []fallbackDomainFilter
|
||||
fallbackIPFilters []fallbackIPFilter
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
}
|
||||
|
||||
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
|
||||
@ -82,8 +80,8 @@ func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) {
|
||||
return r.resolveIP(host, D.TypeAAAA)
|
||||
}
|
||||
|
||||
func (r *Resolver) shouldFallback(ip net.IP) bool {
|
||||
for _, filter := range r.fallbackFilters {
|
||||
func (r *Resolver) shouldIPFallback(ip net.IP) bool {
|
||||
for _, filter := range r.fallbackIPFilters {
|
||||
if filter.Match(ip) {
|
||||
return true
|
||||
}
|
||||
@ -106,7 +104,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||
go r.exchangeWithoutCache(m)
|
||||
} else {
|
||||
setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds()))
|
||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -126,17 +124,11 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
msg := result.(*D.Msg)
|
||||
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
if r.mapping || r.fakeip {
|
||||
ips := r.msgToIP(msg)
|
||||
for _, ip := range ips {
|
||||
putMsgToCache(r.lruCache, ip.String(), msg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
if isIPReq {
|
||||
return r.fallbackExchange(m)
|
||||
return r.ipExchange(m)
|
||||
}
|
||||
|
||||
return r.batchExchange(r.main, m)
|
||||
@ -152,40 +144,6 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// IPToHost return fake-ip or redir-host mapping host
|
||||
func (r *Resolver) IPToHost(ip net.IP) (string, bool) {
|
||||
if r.fakeip {
|
||||
record, existed := r.pool.LookBack(ip)
|
||||
if existed {
|
||||
return record, true
|
||||
}
|
||||
}
|
||||
|
||||
cache, _ := r.lruCache.Get(ip.String())
|
||||
if cache == nil {
|
||||
return "", false
|
||||
}
|
||||
fqdn := cache.(*D.Msg).Question[0].Name
|
||||
return strings.TrimRight(fqdn, "."), true
|
||||
}
|
||||
|
||||
func (r *Resolver) IsMapping() bool {
|
||||
return r.mapping
|
||||
}
|
||||
|
||||
// FakeIPEnabled returns if fake-ip is enabled
|
||||
func (r *Resolver) FakeIPEnabled() bool {
|
||||
return r.fakeip
|
||||
}
|
||||
|
||||
// IsFakeIP determine if given ip is a fake-ip
|
||||
func (r *Resolver) IsFakeIP(ip net.IP) bool {
|
||||
if r.FakeIPEnabled() {
|
||||
return r.pool.Exist(ip)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||
for _, client := range clients {
|
||||
@ -203,7 +161,7 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
||||
|
||||
elm := fast.Wait()
|
||||
if elm == nil {
|
||||
err := errors.New("All DNS requests failed")
|
||||
err := errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
||||
}
|
||||
@ -214,19 +172,49 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
domain := r.msgToDomain(m)
|
||||
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, df := range r.fallbackDomainFilters {
|
||||
if df.Match(domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
|
||||
onlyFallback := r.shouldOnlyQueryFallback(m)
|
||||
|
||||
if onlyFallback {
|
||||
res := <-r.asyncExchange(r.fallback, m)
|
||||
return res.Msg, res.Error
|
||||
}
|
||||
|
||||
msgCh := r.asyncExchange(r.main, m)
|
||||
if r.fallback == nil {
|
||||
|
||||
if r.fallback == nil { // directly return if no fallback servers are available
|
||||
res := <-msgCh
|
||||
msg, err = res.Msg, res.Error
|
||||
return
|
||||
}
|
||||
|
||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||
if !r.shouldFallback(ips[0]) {
|
||||
msg = res.Msg
|
||||
if !r.shouldIPFallback(ips[0]) {
|
||||
msg = res.Msg // no need to wait for fallback result
|
||||
err = res.Error
|
||||
return msg, err
|
||||
}
|
||||
@ -284,6 +272,14 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||
return ips
|
||||
}
|
||||
|
||||
func (r *Resolver) msgToDomain(msg *D.Msg) string {
|
||||
if len(msg.Question) > 0 {
|
||||
return strings.TrimRight(msg.Question[0].Name, ".")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
@ -301,6 +297,7 @@ type NameServer struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool
|
||||
IPCIDR []*net.IPNet
|
||||
Domain []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -313,7 +310,7 @@ type Config struct {
|
||||
Hosts *trie.DomainTrie
|
||||
}
|
||||
|
||||
func New(config Config) *Resolver {
|
||||
func NewResolver(config Config) *Resolver {
|
||||
defaultResolver := &Resolver{
|
||||
main: transform(config.Default, nil),
|
||||
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||
@ -323,9 +320,6 @@ func New(config Config) *Resolver {
|
||||
ipv6: config.IPv6,
|
||||
main: transform(config.Main, defaultResolver),
|
||||
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
|
||||
mapping: config.EnhancedMode == MAPPING,
|
||||
fakeip: config.EnhancedMode == FAKEIP,
|
||||
pool: config.Pool,
|
||||
hosts: config.Hosts,
|
||||
}
|
||||
|
||||
@ -333,14 +327,19 @@ func New(config Config) *Resolver {
|
||||
r.fallback = transform(config.Fallback, defaultResolver)
|
||||
}
|
||||
|
||||
fallbackFilters := []fallbackFilter{}
|
||||
fallbackIPFilters := []fallbackIPFilter{}
|
||||
if config.FallbackFilter.GeoIP {
|
||||
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
||||
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{})
|
||||
}
|
||||
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||
fallbackFilters = append(fallbackFilters, &ipnetFilter{ipnet: ipnet})
|
||||
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
|
||||
}
|
||||
r.fallbackIPFilters = fallbackIPFilters
|
||||
|
||||
if len(config.FallbackFilter.Domain) != 0 {
|
||||
fallbackDomainFilters := []fallbackDomainFilter{NewDomainFilter(config.FallbackFilter.Domain)}
|
||||
r.fallbackDomainFilters = fallbackDomainFilters
|
||||
}
|
||||
r.fallbackFilters = fallbackFilters
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -27,16 +27,22 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) {
|
||||
return
|
||||
}
|
||||
|
||||
s.handler(w, r)
|
||||
msg, err := s.handler(r)
|
||||
if err != nil {
|
||||
D.HandleFailed(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteMsg(msg)
|
||||
}
|
||||
|
||||
func (s *Server) setHandler(handler handler) {
|
||||
s.handler = handler
|
||||
}
|
||||
|
||||
func ReCreateServer(addr string, resolver *Resolver) error {
|
||||
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) error {
|
||||
if addr == address && resolver != nil {
|
||||
handler := newHandler(resolver)
|
||||
handler := newHandler(resolver, mapper)
|
||||
server.setHandler(handler)
|
||||
return nil
|
||||
}
|
||||
@ -68,7 +74,7 @@ func ReCreateServer(addr string, resolver *Resolver) error {
|
||||
}
|
||||
|
||||
address = addr
|
||||
handler := newHandler(resolver)
|
||||
handler := newHandler(resolver, mapper)
|
||||
server = &Server{handler: handler}
|
||||
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
|
||||
|
||||
|
12
go.mod
12
go.mod
@ -3,21 +3,21 @@ module github.com/Dreamacro/clash
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.1.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/miekg/dns v1.1.29
|
||||
github.com/miekg/dns v1.1.31
|
||||
github.com/oschwald/geoip2-golang v1.4.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed
|
||||
gopkg.in/eapache/channels.v1 v1.1.0
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
26
go.sum
26
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a h1:JhQFrFOkCpRB8qsN6PrzHFzjy/8iQpFFk5cbOiplh6s=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
@ -17,8 +17,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
|
||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug=
|
||||
github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng=
|
||||
github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls=
|
||||
@ -27,7 +27,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@ -36,24 +35,27 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -34,7 +34,7 @@ func readConfig(path string) ([]byte, error) {
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("Configuration file %s is empty", path)
|
||||
return nil, fmt.Errorf("configuration file %s is empty", path)
|
||||
}
|
||||
|
||||
return data, err
|
||||
@ -101,13 +101,14 @@ func GetGeneral() *config.General {
|
||||
func updateExperimental(c *config.Config) {}
|
||||
|
||||
func updateDNS(c *config.DNS) {
|
||||
if c.Enable == false {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
tunnel.SetResolver(nil)
|
||||
dns.ReCreateServer("", nil)
|
||||
resolver.DefaultHostMapper = nil
|
||||
dns.ReCreateServer("", nil, nil)
|
||||
return
|
||||
}
|
||||
r := dns.New(dns.Config{
|
||||
|
||||
cfg := dns.Config{
|
||||
Main: c.NameServer,
|
||||
Fallback: c.Fallback,
|
||||
IPv6: c.IPv6,
|
||||
@ -117,12 +118,23 @@ func updateDNS(c *config.DNS) {
|
||||
FallbackFilter: dns.FallbackFilter{
|
||||
GeoIP: c.FallbackFilter.GeoIP,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
},
|
||||
Default: c.DefaultNameserver,
|
||||
})
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
m := dns.NewEnhancer(cfg)
|
||||
|
||||
// reuse cache of old host mapper
|
||||
if old := resolver.DefaultHostMapper; old != nil {
|
||||
m.PatchFrom(old.(*dns.ResolverEnhancer))
|
||||
}
|
||||
|
||||
resolver.DefaultResolver = r
|
||||
tunnel.SetResolver(r)
|
||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||
resolver.DefaultHostMapper = m
|
||||
|
||||
if err := dns.ReCreateServer(c.Listen, r, m); err != nil {
|
||||
log.Errorln("Start DNS server error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ func Subscribe() observable.Subscription {
|
||||
|
||||
func UnSubscribe(sub observable.Subscription) {
|
||||
source.UnSubscribe(sub)
|
||||
return
|
||||
}
|
||||
|
||||
func Level() LogLevel {
|
||||
|
5
main.go
5
main.go
@ -10,7 +10,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/hub"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
@ -76,10 +75,10 @@ func main() {
|
||||
if testConfig {
|
||||
if _, err := executor.Parse(); err != nil {
|
||||
log.Errorln(err.Error())
|
||||
fmt.Printf("configuration file %s test failed\n", constant.Path.Config())
|
||||
fmt.Printf("configuration file %s test failed\n", C.Path.Config())
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("configuration file %s test is successful\n", constant.Path.Config())
|
||||
fmt.Printf("configuration file %s test is successful\n", C.Path.Config())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ func (l *HttpListener) Address() string {
|
||||
func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) {
|
||||
if result := cache.Get(loginStr); result != nil {
|
||||
ret = result.(bool)
|
||||
return
|
||||
}
|
||||
loginData, err := base64.StdEncoding.DecodeString(loginStr)
|
||||
login := strings.Split(string(loginData), ":")
|
||||
@ -80,7 +81,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) {
|
||||
authenticator := authStore.Authenticator()
|
||||
if authenticator != nil {
|
||||
if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 {
|
||||
_, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
||||
conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n"))
|
||||
conn.Close()
|
||||
return
|
||||
} else if !canActivate(authStrings[1], authenticator, cache) {
|
||||
|
@ -30,14 +30,8 @@ var (
|
||||
httpMux sync.Mutex
|
||||
redirMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
tunMux sync.Mutex
|
||||
)
|
||||
|
||||
type listener interface {
|
||||
Close()
|
||||
Address() string
|
||||
}
|
||||
|
||||
type Ports struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
|
@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.buf)
|
||||
return
|
||||
}
|
||||
|
@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.bufRef)
|
||||
return
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
var (
|
||||
errPayload = errors.New("payload error")
|
||||
errParams = errors.New("params error")
|
||||
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||
ErrInvalidNetwork = errors.New("invalid network")
|
||||
|
||||
|
@ -127,8 +127,8 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
}
|
||||
// skip the first and last xinpgen(24 bytes) block
|
||||
for i := 24; i < len(buf)-24; i += itemSize {
|
||||
// skip the first xinpgen(24 bytes) block
|
||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||
// offset of xinpcb_n and xsocket_n
|
||||
inp, so := i, i+104
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@ -17,7 +18,15 @@ import (
|
||||
)
|
||||
|
||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
||||
var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
var (
|
||||
processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64))
|
||||
errNotFound = errors.New("process not found")
|
||||
matchMeta = func(p *Process, m *C.Metadata) bool { return false }
|
||||
|
||||
defaultSearcher *searcher
|
||||
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type Process struct {
|
||||
adapter string
|
||||
@ -28,7 +37,7 @@ func (ps *Process) RuleType() C.RuleType {
|
||||
return C.Process
|
||||
}
|
||||
|
||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
func match(ps *Process, metadata *C.Metadata) bool {
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
@ -45,6 +54,10 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
return strings.EqualFold(cached.(string), ps.process)
|
||||
}
|
||||
|
||||
func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
return matchMeta(ps, metadata)
|
||||
}
|
||||
|
||||
func (p *Process) Adapter() string {
|
||||
return p.adapter
|
||||
}
|
||||
@ -58,6 +71,15 @@ func (p *Process) ShouldResolveIP() bool {
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string) (*Process, error) {
|
||||
once.Do(func() {
|
||||
err := initSearcher()
|
||||
if err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
log.Warnln("All PROCESS-NAME rules will be skipped")
|
||||
return
|
||||
}
|
||||
matchMeta = match
|
||||
})
|
||||
return &Process{
|
||||
adapter: adapter,
|
||||
process: process,
|
||||
@ -85,28 +107,6 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
return filepath.Base(string(buf[:size-1])), nil
|
||||
}
|
||||
|
||||
func searchSocketPid(socket uint64) (uint32, error) {
|
||||
value, err := syscall.Sysctl("kern.file")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
|
||||
// struct xfile
|
||||
itemSize := 128
|
||||
for i := 0; i < len(buf); i += itemSize {
|
||||
// xfile.xf_data
|
||||
data := binary.BigEndian.Uint64(buf[i+56 : i+64])
|
||||
if data == socket {
|
||||
// xfile.xf_pid
|
||||
pid := readNativeUint32(buf[i+8 : i+12])
|
||||
return pid, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("pid not found")
|
||||
}
|
||||
|
||||
func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
ip := metadata.SrcIP
|
||||
port, err := strconv.Atoi(metadata.SrcPort)
|
||||
@ -115,25 +115,18 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
}
|
||||
|
||||
var spath string
|
||||
var itemSize int
|
||||
var inpOffset int
|
||||
var isTCP bool
|
||||
switch metadata.NetWork {
|
||||
case C.TCP:
|
||||
spath = "net.inet.tcp.pcblist"
|
||||
// struct xtcpcb
|
||||
itemSize = 744
|
||||
inpOffset = 8
|
||||
isTCP = true
|
||||
case C.UDP:
|
||||
spath = "net.inet.udp.pcblist"
|
||||
// struct xinpcb
|
||||
itemSize = 400
|
||||
inpOffset = 0
|
||||
isTCP = false
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
isIPv4 := ip.To4() != nil
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -141,27 +134,73 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
|
||||
buf := []byte(value)
|
||||
|
||||
// skip the first and last xinpgen(64 bytes) block
|
||||
for i := 64; i < len(buf)-64; i += itemSize {
|
||||
pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
type searcher struct {
|
||||
// sizeof(struct xinpgen)
|
||||
headSize int
|
||||
// sizeof(struct xtcpcb)
|
||||
tcpItemSize int
|
||||
// sizeof(struct xinpcb)
|
||||
udpItemSize int
|
||||
udpInpOffset int
|
||||
port int
|
||||
ip int
|
||||
vflag int
|
||||
socket int
|
||||
|
||||
// sizeof(struct xfile)
|
||||
fileItemSize int
|
||||
data int
|
||||
pid int
|
||||
}
|
||||
|
||||
func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) {
|
||||
var itemSize int
|
||||
var inpOffset int
|
||||
|
||||
if isTCP {
|
||||
// struct xtcpcb
|
||||
itemSize = s.tcpItemSize
|
||||
inpOffset = 8
|
||||
} else {
|
||||
// struct xinpcb
|
||||
itemSize = s.udpItemSize
|
||||
inpOffset = s.udpInpOffset
|
||||
}
|
||||
|
||||
isIPv4 := ip.To4() != nil
|
||||
// skip the first xinpgen block
|
||||
for i := s.headSize; i+itemSize <= len(buf); i += itemSize {
|
||||
inp := i + inpOffset
|
||||
|
||||
srcPort := binary.BigEndian.Uint16(buf[inp+254 : inp+256])
|
||||
srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2])
|
||||
|
||||
if uint16(port) != srcPort {
|
||||
if port != srcPort {
|
||||
continue
|
||||
}
|
||||
|
||||
// xinpcb.inp_vflag
|
||||
flag := buf[inp+392]
|
||||
flag := buf[inp+s.vflag]
|
||||
|
||||
var srcIP net.IP
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// ipv4
|
||||
srcIP = net.IP(buf[inp+284 : inp+288])
|
||||
srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4])
|
||||
case flag&0x2 > 0 && !isIPv4:
|
||||
// ipv6
|
||||
srcIP = net.IP(buf[inp+272 : inp+288])
|
||||
srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4])
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@ -171,17 +210,85 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) {
|
||||
}
|
||||
|
||||
// xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison
|
||||
socket := binary.BigEndian.Uint64(buf[inp+16 : inp+24])
|
||||
pid, err := searchSocketPid(socket)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getExecPathFromPID(pid)
|
||||
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
||||
return s.searchSocketPid(socket)
|
||||
}
|
||||
return 0, errNotFound
|
||||
}
|
||||
|
||||
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||
value, err := syscall.Sysctl("kern.file")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return "", errors.New("process not found")
|
||||
buf := []byte(value)
|
||||
|
||||
// struct xfile
|
||||
itemSize := s.fileItemSize
|
||||
for i := 0; i+itemSize <= len(buf); i += itemSize {
|
||||
// xfile.xf_data
|
||||
data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8])
|
||||
if data == socket {
|
||||
// xfile.xf_pid
|
||||
pid := readNativeUint32(buf[i+s.pid : i+s.pid+4])
|
||||
return pid, nil
|
||||
}
|
||||
}
|
||||
return 0, errNotFound
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
func newSearcher(major int) *searcher {
|
||||
var s *searcher = nil
|
||||
switch major {
|
||||
case 11:
|
||||
s = &searcher{
|
||||
headSize: 32,
|
||||
tcpItemSize: 1304,
|
||||
udpItemSize: 632,
|
||||
port: 198,
|
||||
ip: 228,
|
||||
vflag: 116,
|
||||
socket: 88,
|
||||
fileItemSize: 80,
|
||||
data: 56,
|
||||
pid: 8,
|
||||
udpInpOffset: 8,
|
||||
}
|
||||
case 12:
|
||||
s = &searcher{
|
||||
headSize: 64,
|
||||
tcpItemSize: 744,
|
||||
udpItemSize: 400,
|
||||
port: 254,
|
||||
ip: 284,
|
||||
vflag: 392,
|
||||
socket: 16,
|
||||
fileItemSize: 128,
|
||||
data: 56,
|
||||
pid: 8,
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func initSearcher() error {
|
||||
osRelease, err := syscall.Sysctl("kern.osrelease")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dot := strings.Index(osRelease, ".")
|
||||
if dot != -1 {
|
||||
osRelease = osRelease[:dot]
|
||||
}
|
||||
major, err := strconv.Atoi(osRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defaultSearcher = newSearcher(major)
|
||||
if defaultSearcher == nil {
|
||||
return fmt.Errorf("unsupported freebsd version %d", major)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Compare(buffer[:n], socket) == 0 {
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -9,14 +9,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) {
|
||||
req := request.R
|
||||
host := req.Host
|
||||
|
||||
@ -28,7 +27,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
|
||||
req.Header.Set("Connection", "close")
|
||||
req.RequestURI = ""
|
||||
adapters.RemoveHopByHopHeaders(req.Header)
|
||||
inbound.RemoveHopByHopHeaders(req.Header)
|
||||
err := req.Write(outbound)
|
||||
if err != nil {
|
||||
break
|
||||
@ -39,7 +38,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
adapters.RemoveHopByHopHeaders(resp.Header)
|
||||
inbound.RemoveHopByHopHeaders(resp.Header)
|
||||
|
||||
if resp.StatusCode == http.StatusContinue {
|
||||
err = resp.Write(request)
|
||||
@ -121,14 +120,14 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n
|
||||
from = fAddr
|
||||
}
|
||||
|
||||
n, err = packet.WriteBack(buf[:n], from)
|
||||
_, err = packet.WriteBack(buf[:n], from)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) {
|
||||
func handleSocket(request C.ServerAdapter, outbound net.Conn) {
|
||||
relay(request, outbound)
|
||||
}
|
||||
|
||||
|
@ -2,23 +2,20 @@ package tunnel
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DefaultManager *Manager
|
||||
|
||||
func init() {
|
||||
DefaultManager = &Manager{
|
||||
upload: make(chan int64),
|
||||
download: make(chan int64),
|
||||
}
|
||||
DefaultManager.handle()
|
||||
DefaultManager = &Manager{}
|
||||
|
||||
go DefaultManager.handle()
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
connections sync.Map
|
||||
upload chan int64
|
||||
download chan int64
|
||||
uploadTemp int64
|
||||
downloadTemp int64
|
||||
uploadBlip int64
|
||||
@ -35,16 +32,18 @@ func (m *Manager) Leave(c tracker) {
|
||||
m.connections.Delete(c.ID())
|
||||
}
|
||||
|
||||
func (m *Manager) Upload() chan<- int64 {
|
||||
return m.upload
|
||||
func (m *Manager) PushUploaded(size int64) {
|
||||
atomic.AddInt64(&m.uploadTemp, size)
|
||||
atomic.AddInt64(&m.uploadTotal, size)
|
||||
}
|
||||
|
||||
func (m *Manager) Download() chan<- int64 {
|
||||
return m.download
|
||||
func (m *Manager) PushDownloaded(size int64) {
|
||||
atomic.AddInt64(&m.downloadTemp, size)
|
||||
atomic.AddInt64(&m.downloadTotal, size)
|
||||
}
|
||||
|
||||
func (m *Manager) Now() (up int64, down int64) {
|
||||
return m.uploadBlip, m.downloadBlip
|
||||
return atomic.LoadInt64(&m.uploadBlip), atomic.LoadInt64(&m.downloadBlip)
|
||||
}
|
||||
|
||||
func (m *Manager) Snapshot() *Snapshot {
|
||||
@ -55,8 +54,8 @@ func (m *Manager) Snapshot() *Snapshot {
|
||||
})
|
||||
|
||||
return &Snapshot{
|
||||
UploadTotal: m.uploadTotal,
|
||||
DownloadTotal: m.downloadTotal,
|
||||
UploadTotal: atomic.LoadInt64(&m.uploadTotal),
|
||||
DownloadTotal: atomic.LoadInt64(&m.downloadTotal),
|
||||
Connections: connections,
|
||||
}
|
||||
}
|
||||
@ -71,21 +70,13 @@ func (m *Manager) ResetStatistic() {
|
||||
}
|
||||
|
||||
func (m *Manager) handle() {
|
||||
go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal)
|
||||
go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal)
|
||||
}
|
||||
|
||||
func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
for {
|
||||
select {
|
||||
case n := <-ch:
|
||||
*temp += n
|
||||
*total += n
|
||||
case <-ticker.C:
|
||||
*blip = *temp
|
||||
*temp = 0
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
atomic.StoreInt64(&m.uploadBlip, atomic.LoadInt64(&m.uploadTemp))
|
||||
atomic.StoreInt64(&m.uploadTemp, 0)
|
||||
atomic.StoreInt64(&m.downloadBlip, atomic.LoadInt64(&m.downloadTemp))
|
||||
atomic.StoreInt64(&m.downloadTemp, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ func (tt *tcpTracker) ID() string {
|
||||
func (tt *tcpTracker) Read(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Read(b)
|
||||
download := int64(n)
|
||||
tt.manager.Download() <- download
|
||||
tt.manager.PushDownloaded(download)
|
||||
tt.DownloadTotal += download
|
||||
return n, err
|
||||
}
|
||||
@ -45,7 +45,7 @@ func (tt *tcpTracker) Read(b []byte) (int, error) {
|
||||
func (tt *tcpTracker) Write(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Write(b)
|
||||
upload := int64(n)
|
||||
tt.manager.Upload() <- upload
|
||||
tt.manager.PushUploaded(upload)
|
||||
tt.UploadTotal += upload
|
||||
return n, err
|
||||
}
|
||||
@ -92,7 +92,7 @@ func (ut *udpTracker) ID() string {
|
||||
func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := ut.PacketConn.ReadFrom(b)
|
||||
download := int64(n)
|
||||
ut.manager.Download() <- download
|
||||
ut.manager.PushDownloaded(download)
|
||||
ut.DownloadTotal += download
|
||||
return n, addr, err
|
||||
}
|
||||
@ -100,7 +100,7 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
n, err := ut.PacketConn.WriteTo(b, addr)
|
||||
upload := int64(n)
|
||||
ut.manager.Upload() <- upload
|
||||
ut.manager.PushUploaded(upload)
|
||||
ut.UploadTotal += upload
|
||||
return n, err
|
||||
}
|
||||
|
@ -12,21 +12,19 @@ import (
|
||||
"github.com/Dreamacro/clash/component/nat"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
channels "gopkg.in/eapache/channels.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
tcpQueue = channels.NewInfiniteChannel()
|
||||
udpQueue = channels.NewInfiniteChannel()
|
||||
natTable = nat.New()
|
||||
rules []C.Rule
|
||||
proxies = make(map[string]C.Proxy)
|
||||
providers map[string]provider.ProxyProvider
|
||||
configMux sync.RWMutex
|
||||
enhancedMode *dns.Resolver
|
||||
tcpQueue = channels.NewInfiniteChannel()
|
||||
udpQueue = channels.NewInfiniteChannel()
|
||||
natTable = nat.New()
|
||||
rules []C.Rule
|
||||
proxies = make(map[string]C.Proxy)
|
||||
providers map[string]provider.ProxyProvider
|
||||
configMux sync.RWMutex
|
||||
|
||||
// Outbound Rule
|
||||
mode = Rule
|
||||
@ -89,11 +87,6 @@ func SetMode(m TunnelMode) {
|
||||
mode = m
|
||||
}
|
||||
|
||||
// SetResolver set custom dns resolver for enhanced mode
|
||||
func SetResolver(r *dns.Resolver) {
|
||||
enhancedMode = r
|
||||
}
|
||||
|
||||
// processUDP starts a loop to handle udp packet
|
||||
func processUDP() {
|
||||
queue := udpQueue.Out()
|
||||
@ -120,7 +113,7 @@ func process() {
|
||||
}
|
||||
|
||||
func needLookupIP(metadata *C.Metadata) bool {
|
||||
return enhancedMode != nil && (enhancedMode.IsMapping() || enhancedMode.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil
|
||||
return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP != nil
|
||||
}
|
||||
|
||||
func preHandleMetadata(metadata *C.Metadata) error {
|
||||
@ -131,17 +124,17 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
||||
|
||||
// preprocess enhanced-mode metadata
|
||||
if needLookupIP(metadata) {
|
||||
host, exist := enhancedMode.IPToHost(metadata.DstIP)
|
||||
host, exist := resolver.FindHostByIP(metadata.DstIP)
|
||||
if exist {
|
||||
metadata.Host = host
|
||||
metadata.AddrType = C.AtypDomainName
|
||||
if enhancedMode.FakeIPEnabled() {
|
||||
if resolver.FakeIPEnabled() {
|
||||
metadata.DstIP = nil
|
||||
} else if node := resolver.DefaultHosts.Search(host); node != nil {
|
||||
// redir-host should lookup the hosts
|
||||
metadata.DstIP = node.Data.(net.IP)
|
||||
}
|
||||
} else if enhancedMode.IsFakeIP(metadata.DstIP) {
|
||||
} else if resolver.IsFakeIP(metadata.DstIP) {
|
||||
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
||||
}
|
||||
}
|
||||
@ -177,7 +170,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
||||
|
||||
// make a fAddr if requset ip is fakeip
|
||||
var fAddr net.Addr
|
||||
if enhancedMode != nil && enhancedMode.IsFakeIP(metadata.DstIP) {
|
||||
if resolver.IsFakeIP(metadata.DstIP) {
|
||||
fAddr = metadata.UDPAddr()
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user