Compare commits

...

25 Commits

Author SHA1 Message Date
d3b14c325f Fix: the priority of fake-ip-filter 2020-10-09 00:04:24 +08:00
4859b158b4 Chore: make builds reproducible (#1006) 2020-10-08 17:54:38 +08:00
d65b51c62b Feature: http support custom sni 2020-10-02 11:34:40 +08:00
a6444bb449 Feature: support domain in fallback filter (#964) 2020-09-28 22:17:10 +08:00
e09931dcf7 Chore: remove broken test temporarily 2020-09-26 20:36:52 +08:00
5bd189f2d0 Feature: support VMess HTTP/2 transport (#903) 2020-09-26 20:33:57 +08:00
8766287e72 Chore: sync necessary changes from premium 2020-09-21 22:22:07 +08:00
10f9571c9e Fix: pool gc test 2020-09-21 00:44:47 +08:00
96a8259c42 Feature: support snell v2 (#952)
Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com>
2020-09-21 00:33:13 +08:00
68dd0622b8 Chore: code style 2020-09-20 15:53:27 +08:00
558ac6b965 Chore: split enhanced mode instance (#936)
Co-authored-by: Dreamacro <305009791@qq.com>
2020-09-17 10:48:42 +08:00
e773f95f21 Fix: PROCESS-NAME on FreeBSD 11.x (#947) 2020-09-07 17:43:34 +08:00
314ce1c249 Feature: vmess network http support TLS (https) 2020-09-04 21:27:19 +08:00
13275b1aa6 Chore: use only one goroutine to handle statistic (#940) 2020-09-03 10:30:18 +08:00
02d9169b5d Fix: potential PCB buffer overflow on bsd systems (#941) 2020-09-03 10:27:20 +08:00
7631bcc99e Improve: use atomic for connection statistic (#938) 2020-09-02 16:34:12 +08:00
a32ee13fc9 Feature: reuse dns resolver cache when hot reload 2020-08-31 00:32:18 +08:00
b8ed738238 Chore: update actions version 2020-08-30 23:06:21 +08:00
687c2a21cf Fix: vmess UDP option should be effect 2020-08-30 22:49:55 +08:00
ad18064e6b Chore: code style (#933) 2020-08-30 19:53:00 +08:00
c9735ef75b Fix: static check 2020-08-25 22:36:38 +08:00
b70882f01a Chore: add static check 2020-08-25 22:32:23 +08:00
5805334ccd Chore: pass staticcheck 2020-08-25 22:19:59 +08:00
c1b4382fe8 Feature: add Windows ARM32 build (#902)
Co-authored-by: MarksonHon <50002150+MarksonHon@users.noreply.github.com>
2020-08-16 13:50:56 +08:00
008743f20b Chore: update dependencies 2020-08-16 11:32:51 +08:00
60 changed files with 1225 additions and 368 deletions

View File

@ -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: |

View File

@ -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/')

View File

@ -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'

View File

@ -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))

View File

@ -13,10 +13,6 @@ import (
C "github.com/Dreamacro/clash/constant"
)
var (
defaultURLTestTimeout = time.Second * 5
)
type Base struct {
name string
addr string

View File

@ -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,
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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,

View File

@ -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)

View File

@ -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]

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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"))
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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()

View File

@ -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")
}

View File

@ -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
View 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
}

View 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))
}

View 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
}

View File

@ -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
View 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}
}

View File

@ -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)}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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
View 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
}

View File

@ -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)

View File

@ -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{

View File

@ -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{

View File

@ -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

View File

@ -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
}

View File

@ -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
View 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,
}
}

View File

@ -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
}

View File

@ -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))

View File

@ -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
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
}

View File

@ -64,7 +64,6 @@ func Subscribe() observable.Subscription {
func UnSubscribe(sub observable.Subscription) {
source.UnSubscribe(sub)
return
}
func Level() LogLevel {

View File

@ -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
}

View File

@ -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) {

View File

@ -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"`

View File

@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
func (c *packet) Drop() {
pool.Put(c.buf)
return
}

View File

@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr {
func (c *packet) Drop() {
pool.Put(c.bufRef)
return
}

View File

@ -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")

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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()
}