Merge branch 'Dev' into Meta
# Conflicts: # config/config.go
This commit is contained in:
commit
2f6f9ebc2e
64 changed files with 1536 additions and 438 deletions
44
.github/workflows/dev.yml
vendored
Normal file
44
.github/workflows/dev.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
name: Dev
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
dev-build:
|
||||||
|
if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get latest go version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
# - name: Get dependencies, run test
|
||||||
|
# run: |
|
||||||
|
# go test ./...
|
||||||
|
- name: Build
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
NAME: Clash.Meta
|
||||||
|
BINDIR: bin
|
||||||
|
run: make -j releases
|
||||||
|
|
||||||
|
- name: Upload Dev
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: ${{ env.GIT_BRANCH != 'Meta' && success() }}
|
||||||
|
with:
|
||||||
|
tag_name: develop
|
||||||
|
files: bin/*
|
||||||
|
prerelease: true
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -23,3 +23,5 @@ vendor
|
||||||
|
|
||||||
# test suite
|
# test suite
|
||||||
test/config/cache*
|
test/config/cache*
|
||||||
|
/output
|
||||||
|
/.vscode
|
12
Makefile
12
Makefile
|
@ -1,6 +1,10 @@
|
||||||
NAME=Clash.Meta
|
NAME=Clash.Meta
|
||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
VERSION=$(shell git describe --tags || echo "unknown version")
|
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
VERSION=$(shell git describe --tags || echo "unknown version" )
|
||||||
|
ifeq ($(BRANCH),Dev)
|
||||||
|
VERSION=develop-$(shell git rev-parse --short HEAD)
|
||||||
|
endif
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
AUTOIPTABLES=Enable
|
AUTOIPTABLES=Enable
|
||||||
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
|
||||||
|
@ -38,7 +42,11 @@ WINDOWS_ARCH_LIST = \
|
||||||
windows-arm32v7
|
windows-arm32v7
|
||||||
|
|
||||||
|
|
||||||
all: linux-arm64-AutoIptables linux-amd64-AutoIptables linux-arm64 linux-amd64 darwin-amd64 darwin-arm64 windows-amd64 windows-386 # Most used
|
all:linux-amd64-AutoIptables linux-amd64\
|
||||||
|
linux-arm64 linux-arm64-AutoIptables linux-armv7\
|
||||||
|
darwin-amd64 darwin-arm64\
|
||||||
|
windows-amd64 windows-386 \
|
||||||
|
linux-mips-hardfloat linux-mips-softfloat linux-mips64 linux-mips64le linux-mipsle-hardfloat linux-mipsle-softfloat# Most used
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
|
@ -20,3 +20,26 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
|
||||||
|
|
||||||
return context.NewConnContext(conn, metadata)
|
return context.NewConnContext(conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
|
||||||
|
metadata := &C.Metadata{}
|
||||||
|
metadata.NetWork = C.TCP
|
||||||
|
metadata.Type = C.INNER
|
||||||
|
metadata.DNSMode = C.DNSMapping
|
||||||
|
metadata.Host = host
|
||||||
|
metadata.AddrType = C.AtypDomainName
|
||||||
|
metadata.Process = C.ClashName
|
||||||
|
if ip, port, err := parseAddr(dst); err == nil {
|
||||||
|
metadata.DstPort = port
|
||||||
|
if host == "" {
|
||||||
|
metadata.DstIP = ip
|
||||||
|
if ip.To4() == nil {
|
||||||
|
metadata.AddrType = C.AtypIPv6
|
||||||
|
} else {
|
||||||
|
metadata.AddrType = C.AtypIPv4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.NewConnContext(conn, metadata)
|
||||||
|
}
|
||||||
|
|
|
@ -44,3 +44,13 @@ func NewDirect() *Direct {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCompatible() *Direct {
|
||||||
|
return &Direct{
|
||||||
|
Base: &Base{
|
||||||
|
name: "COMPATIBLE",
|
||||||
|
tp: C.Compatible,
|
||||||
|
udp: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ type SnellOption struct {
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Psk string `proxy:"psk"`
|
Psk string `proxy:"psk"`
|
||||||
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Version int `proxy:"version,omitempty"`
|
Version int `proxy:"version,omitempty"`
|
||||||
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -85,6 +86,24 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
|
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", s.addr, s.Base.DialOptions(opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||||
|
|
||||||
|
err = snell.WriteUDPHeader(c, s.version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := snell.PacketConn(c)
|
||||||
|
return newPacketConn(pc, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewSnell(option SnellOption) (*Snell, error) {
|
func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
psk := []byte(option.Psk)
|
psk := []byte(option.Psk)
|
||||||
|
@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
if option.Version == 0 {
|
if option.Version == 0 {
|
||||||
option.Version = snell.DefaultSnellVersion
|
option.Version = snell.DefaultSnellVersion
|
||||||
}
|
}
|
||||||
if option.Version != snell.Version1 && option.Version != snell.Version2 {
|
switch option.Version {
|
||||||
|
case snell.Version1, snell.Version2:
|
||||||
|
if option.UDP {
|
||||||
|
return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
|
||||||
|
}
|
||||||
|
case snell.Version3:
|
||||||
|
default:
|
||||||
return nil, fmt.Errorf("snell version error: %d", option.Version)
|
return nil, fmt.Errorf("snell version error: %d", option.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +140,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Snell,
|
tp: C.Snell,
|
||||||
|
udp: option.UDP,
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
},
|
},
|
||||||
psk: psk,
|
psk: psk,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package outboundgroup
|
package outboundgroup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter
|
||||||
proxies = append(proxies, provider.Proxies()...)
|
proxies = append(proxies, provider.Proxies()...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var filterReg *regexp.Regexp = nil
|
var filterReg *regexp.Regexp = nil
|
||||||
matchedProxies := []C.Proxy{}
|
matchedProxies := []C.Proxy{}
|
||||||
if len(filter) > 0 {
|
if len(filter) > 0 {
|
||||||
|
@ -30,10 +32,18 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter
|
||||||
matchedProxies = append(matchedProxies, p)
|
matchedProxies = append(matchedProxies, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if no proxy matched, means bad filter, return all proxies
|
|
||||||
if len(matchedProxies) > 0 {
|
if len(matchedProxies) > 0 {
|
||||||
proxies = matchedProxies
|
return matchedProxies
|
||||||
}
|
} else {
|
||||||
|
return append([]C.Proxy{}, tunnel.Proxies()["COMPATIBLE"])
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||||
|
} else {
|
||||||
return proxies
|
return proxies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,18 +66,20 @@ func (f *Fallback) onDialFailed() {
|
||||||
f.failedTime.Store(now)
|
f.failedTime.Store(now)
|
||||||
f.failedTimes.Store(1)
|
f.failedTimes.Store(1)
|
||||||
} else {
|
} else {
|
||||||
if f.failedTime.Load()-time.Now().UnixMilli() > 5*1000 {
|
if f.failedTime.Load()-time.Now().UnixMilli() > 5*time.Second.Milliseconds() {
|
||||||
f.failedTimes.Store(-1)
|
f.failedTimes.Store(-1)
|
||||||
f.failedTime.Store(-1)
|
f.failedTime.Store(-1)
|
||||||
} else {
|
} else {
|
||||||
f.failedTimes.Inc()
|
failedCount := f.failedTimes.Inc()
|
||||||
failedCount := f.failedTimes.Load()
|
|
||||||
log.Warnln("%s failed count: %d", f.Name(), failedCount)
|
log.Warnln("%s failed count: %d", f.Name(), failedCount)
|
||||||
if failedCount > 5 {
|
if failedCount >= 5 {
|
||||||
log.Debugln("%s failed multiple times.", f.Name())
|
log.Warnln("because %s failed multiple times, active health check", f.Name())
|
||||||
for _, proxyProvider := range f.providers {
|
for _, proxyProvider := range f.providers {
|
||||||
go proxyProvider.HealthCheck()
|
go proxyProvider.HealthCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.failedTimes.Store(-1)
|
||||||
|
f.failedTime.Store(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,10 +97,11 @@ func (f *Fallback) SupportUDP() bool {
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
all := make([]string, 0)
|
||||||
for _, proxy := range f.proxies(false) {
|
for _, proxy := range f.proxies(false) {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"type": f.Type().String(),
|
"type": f.Type().String(),
|
||||||
"now": f.Now(),
|
"now": f.Now(),
|
||||||
|
|
|
@ -150,10 +150,12 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
all := make([]string, 0)
|
||||||
|
|
||||||
for _, proxy := range lb.proxies(false) {
|
for _, proxy := range lb.proxies(false) {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"type": lb.Type().String(),
|
"type": lb.Type().String(),
|
||||||
"all": all,
|
"all": all,
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Relay struct {
|
||||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
var proxies []C.Proxy
|
var proxies []C.Proxy
|
||||||
for _, proxy := range r.proxies(metadata, true) {
|
for _, proxy := range r.proxies(metadata, true) {
|
||||||
if proxy.Type() != C.Direct {
|
if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
|
||||||
proxies = append(proxies, proxy)
|
proxies = append(proxies, proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,10 +69,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
all := make([]string, 0)
|
||||||
|
|
||||||
for _, proxy := range r.rawProxies(false) {
|
for _, proxy := range r.rawProxies(false) {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"type": r.Type().String(),
|
"type": r.Type().String(),
|
||||||
"all": all,
|
"all": all,
|
||||||
|
|
|
@ -50,7 +50,8 @@ func (s *Selector) SupportUDP() bool {
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
all := make([]string, 0)
|
||||||
|
|
||||||
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
|
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
|
@ -99,7 +100,6 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
|
||||||
selected := providers[0].Proxies()[0].Name()
|
|
||||||
return &Selector{
|
return &Selector{
|
||||||
Base: outbound.NewBase(outbound.BaseOption{
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
Name: option.Name,
|
Name: option.Name,
|
||||||
|
@ -109,7 +109,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
|
||||||
}),
|
}),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
selected: selected,
|
selected: "COMPATIBLE",
|
||||||
disableUDP: option.DisableUDP,
|
disableUDP: option.DisableUDP,
|
||||||
filter: option.Filter,
|
filter: option.Filter,
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,10 +125,12 @@ func (u *URLTest) SupportUDP() bool {
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
all := make([]string, 0)
|
||||||
|
|
||||||
for _, proxy := range u.proxies(false) {
|
for _, proxy := range u.proxies(false) {
|
||||||
all = append(all, proxy.Name())
|
all = append(all, proxy.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]interface{}{
|
||||||
"type": u.Type().String(),
|
"type": u.Type().String(),
|
||||||
"now": u.Now(),
|
"now": u.Now(),
|
||||||
|
@ -147,14 +149,16 @@ func (u *URLTest) onDialFailed() {
|
||||||
u.failedTimes.Store(-1)
|
u.failedTimes.Store(-1)
|
||||||
u.failedTime.Store(-1)
|
u.failedTime.Store(-1)
|
||||||
} else {
|
} else {
|
||||||
u.failedTimes.Inc()
|
failedCount := u.failedTimes.Inc()
|
||||||
failedCount := u.failedTimes.Load()
|
|
||||||
log.Warnln("%s failed count: %d", u.Name(), failedCount)
|
log.Warnln("%s failed count: %d", u.Name(), failedCount)
|
||||||
if failedCount > 5 {
|
if failedCount >= 5 {
|
||||||
log.Debugln("%s failed multiple times.", u.Name())
|
log.Warnln("because %s failed multiple times, active health check", u.Name())
|
||||||
for _, proxyProvider := range u.providers {
|
for _, proxyProvider := range u.providers {
|
||||||
go proxyProvider.HealthCheck()
|
go proxyProvider.HealthCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.failedTimes.Store(-1)
|
||||||
|
u.failedTime.Store(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,7 @@ func (f *fetcher) Update() (interface{}, bool, error) {
|
||||||
hash := md5.Sum(buf)
|
hash := md5.Sum(buf)
|
||||||
if bytes.Equal(f.hash[:], hash[:]) {
|
if bytes.Equal(f.hash[:], hash[:]) {
|
||||||
f.updatedAt = &now
|
f.updatedAt = &now
|
||||||
|
os.Chtimes(f.vehicle.Path(), now, now)
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,10 @@ func (pp *proxySetProvider) Initial() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
pp.onUpdate(elm)
|
pp.onUpdate(elm)
|
||||||
|
if pp.healthCheck.auto() {
|
||||||
|
go pp.healthCheck.process()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,10 +106,6 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
||||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hc.auto() {
|
|
||||||
go hc.process()
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := &proxySetProvider{
|
pd := &proxySetProvider{
|
||||||
proxies: []C.Proxy{},
|
proxies: []C.Proxy{},
|
||||||
healthCheck: hc,
|
healthCheck: hc,
|
||||||
|
@ -190,6 +190,10 @@ func (cp *compatibleProvider) Update() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) Initial() error {
|
func (cp *compatibleProvider) Initial() error {
|
||||||
|
if cp.healthCheck.auto() {
|
||||||
|
go cp.healthCheck.process()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,10 +223,6 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||||
return nil, errors.New("provider need one proxy at least")
|
return nil, errors.New("provider need one proxy at least")
|
||||||
}
|
}
|
||||||
|
|
||||||
if hc.auto() {
|
|
||||||
go hc.process()
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := &compatibleProvider{
|
pd := &compatibleProvider{
|
||||||
name: name,
|
name: name,
|
||||||
proxies: proxies,
|
proxies: proxies,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/Dreamacro/clash/listener/inner"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -10,7 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
netHttp "github.com/Dreamacro/clash/common/net"
|
netHttp "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -77,7 +77,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, network, address)
|
conn := inner.HandleTcp(address, uri.Hostname())
|
||||||
|
return conn, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
56
common/collections/stack.go
Normal file
56
common/collections/stack.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package collections
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type (
|
||||||
|
stack struct {
|
||||||
|
top *node
|
||||||
|
length int
|
||||||
|
lock *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
node struct {
|
||||||
|
value interface{}
|
||||||
|
prev *node
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewStack Create a new stack
|
||||||
|
func NewStack() *stack {
|
||||||
|
return &stack{nil, 0, &sync.RWMutex{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len Return the number of items in the stack
|
||||||
|
func (this *stack) Len() int {
|
||||||
|
return this.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek View the top item on the stack
|
||||||
|
func (this *stack) Peek() interface{} {
|
||||||
|
if this.length == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return this.top.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop the top item of the stack and return it
|
||||||
|
func (this *stack) Pop() interface{} {
|
||||||
|
this.lock.Lock()
|
||||||
|
defer this.lock.Unlock()
|
||||||
|
if this.length == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n := this.top
|
||||||
|
this.top = n.prev
|
||||||
|
this.length--
|
||||||
|
return n.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a value onto the top of the stack
|
||||||
|
func (this *stack) Push(value interface{}) {
|
||||||
|
this.lock.Lock()
|
||||||
|
defer this.lock.Unlock()
|
||||||
|
n := &node{value, this.top}
|
||||||
|
this.top = n
|
||||||
|
this.length++
|
||||||
|
}
|
46
common/net/tcpip.go
Normal file
46
common/net/tcpip.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SplitNetworkType(s string) (string, string, error) {
|
||||||
|
var (
|
||||||
|
shecme string
|
||||||
|
hostPort string
|
||||||
|
)
|
||||||
|
result := strings.Split(s, "://")
|
||||||
|
if len(result) == 2 {
|
||||||
|
shecme = result[0]
|
||||||
|
hostPort = result[1]
|
||||||
|
} else if len(result) == 1 {
|
||||||
|
hostPort = result[0]
|
||||||
|
} else {
|
||||||
|
return "", "", fmt.Errorf("tcp/udp style error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(shecme) == 0 {
|
||||||
|
shecme = "udp"
|
||||||
|
}
|
||||||
|
|
||||||
|
if shecme != "tcp" && shecme != "udp" {
|
||||||
|
return "", "", fmt.Errorf("scheme should be tcp:// or udp://")
|
||||||
|
} else {
|
||||||
|
return shecme, hostPort, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
|
||||||
|
temp := s
|
||||||
|
hasPort = true
|
||||||
|
|
||||||
|
if !strings.Contains(s, ":") && !strings.Contains(s, "]:") {
|
||||||
|
temp += ":0"
|
||||||
|
hasPort = false
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err = net.SplitHostPort(temp)
|
||||||
|
return
|
||||||
|
}
|
|
@ -109,13 +109,13 @@ func (t *DomainTrie) search(node *Node, parts []string) *Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c := node.getChild(parts[len(parts)-1]); c != nil {
|
if c := node.getChild(parts[len(parts)-1]); c != nil {
|
||||||
if n := t.search(c, parts[:len(parts)-1]); n != nil {
|
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c := node.getChild(wildcard); c != nil {
|
if c := node.getChild(wildcard); c != nil {
|
||||||
if n := t.search(c, parts[:len(parts)-1]); n != nil {
|
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != nil {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) {
|
||||||
assert.NotNil(t, tree.Insert("..dev", localIP))
|
assert.NotNil(t, tree.Insert("..dev", localIP))
|
||||||
assert.Nil(t, tree.Search("dev"))
|
assert.Nil(t, tree.Search("dev"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTrie_WildcardBoundary(t *testing.T) {
|
||||||
|
tree := New()
|
||||||
|
tree.Insert("+.*", localIP)
|
||||||
|
tree.Insert("stun.*.*.*", localIP)
|
||||||
|
|
||||||
|
assert.NotNil(t, tree.Search("example.com"))
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ package trie
|
||||||
|
|
||||||
// Node is the trie's node
|
// Node is the trie's node
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Data interface{}
|
|
||||||
children map[string]*Node
|
children map[string]*Node
|
||||||
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) getChild(s string) *Node {
|
func (n *Node) getChild(s string) *Node {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
R "github.com/Dreamacro/clash/rule"
|
||||||
|
RP "github.com/Dreamacro/clash/rule/provider"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -24,7 +26,6 @@ import (
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rule"
|
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
@ -209,7 +210,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Stack: "gvisor",
|
Stack: "gvisor",
|
||||||
DnsHijack: []string{"198.18.0.2:53"},
|
DnsHijack: []string{"198.18.0.2:53"},
|
||||||
AutoRoute: true,
|
AutoRoute: false,
|
||||||
},
|
},
|
||||||
DNS: RawDNS{
|
DNS: RawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
|
@ -228,8 +229,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
"1.0.0.1",
|
"1.0.0.1",
|
||||||
},
|
},
|
||||||
NameServer: []string{
|
NameServer: []string{
|
||||||
"https://8.8.8.8/dns-query",
|
"223.5.5.5",
|
||||||
"https://1.0.0.1/dns-query",
|
"119.29.29",
|
||||||
},
|
},
|
||||||
FakeIPFilter: []string{
|
FakeIPFilter: []string{
|
||||||
"dns.msftnsci.com",
|
"dns.msftnsci.com",
|
||||||
|
@ -350,6 +351,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||||
|
|
||||||
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||||
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||||
|
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
|
||||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||||
|
|
||||||
// parse proxy
|
// parse proxy
|
||||||
|
@ -396,13 +398,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||||
providersMap[name] = pd
|
providersMap[name] = pd
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rp := range providersMap {
|
|
||||||
log.Infoln("Start initial provider %s", rp.Name())
|
|
||||||
if err := rp.Initial(); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", rp.Name(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse proxy group
|
// parse proxy group
|
||||||
for idx, mapping := range groupsConfig {
|
for idx, mapping := range groupsConfig {
|
||||||
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap)
|
||||||
|
@ -418,18 +413,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||||
proxies[groupName] = adapter.NewProxy(group)
|
proxies[groupName] = adapter.NewProxy(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial compatible provider
|
|
||||||
for _, pd := range providersMap {
|
|
||||||
if pd.VehicleType() != providerTypes.Compatible {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infoln("Start initial compatible provider %s", pd.Name())
|
|
||||||
if err := pd.Initial(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ps []C.Proxy
|
var ps []C.Proxy
|
||||||
for _, v := range proxyList {
|
for _, v := range proxyList {
|
||||||
ps = append(ps, proxies[v])
|
ps = append(ps, proxies[v])
|
||||||
|
@ -502,20 +485,13 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
|
|
||||||
// parse rule provider
|
// parse rule provider
|
||||||
for name, mapping := range cfg.RuleProvider {
|
for name, mapping := range cfg.RuleProvider {
|
||||||
rp, err := R.ParseRuleProvider(name, mapping)
|
rp, err := RP.ParseRuleProvider(name, mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleProviders[name] = &rp
|
ruleProviders[name] = &rp
|
||||||
R.SetRuleProvider(rp)
|
RP.SetRuleProvider(rp)
|
||||||
}
|
|
||||||
|
|
||||||
for _, ruleProvider := range ruleProviders {
|
|
||||||
log.Infoln("Start initial provider %s", (*ruleProvider).Name())
|
|
||||||
if err := (*ruleProvider).Initial(); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("initial rule provider %s error: %w", (*ruleProvider).Name(), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []C.Rule
|
var rules []C.Rule
|
||||||
|
@ -536,6 +512,10 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
|
||||||
|
payload = strings.Join(rule[1:len(rule)-1], ",")
|
||||||
|
target = rule[len(rule)-1]
|
||||||
|
} else {
|
||||||
switch l := len(rule); {
|
switch l := len(rule); {
|
||||||
case l == 2:
|
case l == 2:
|
||||||
target = rule[1]
|
target = rule[1]
|
||||||
|
@ -555,6 +535,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := proxies[target]; mode != T.Script && !ok {
|
if _, ok := proxies[target]; mode != T.Script && !ok {
|
||||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||||
|
@ -562,6 +543,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
|
|
||||||
params = trimArr(params)
|
params = trimArr(params)
|
||||||
|
|
||||||
|
if ruleName == "GEOSITE" {
|
||||||
|
if err := initGeoSite(); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("can't initial GeoSite: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
|
||||||
|
@ -699,6 +685,11 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||||
|
|
||||||
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
|
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
|
||||||
var sites []*router.DomainMatcher
|
var sites []*router.DomainMatcher
|
||||||
|
if len(countries) > 0 {
|
||||||
|
if err := initGeoSite(); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't initial GeoSite: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, country := range countries {
|
for _, country := range countries {
|
||||||
found := false
|
found := false
|
||||||
|
|
|
@ -50,23 +50,6 @@ func initMMDB() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//func downloadGeoIP(path string) (err error) {
|
|
||||||
// resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat")
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// defer resp.Body.Close()
|
|
||||||
//
|
|
||||||
// f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// defer f.Close()
|
|
||||||
// _, err = io.Copy(f, resp.Body)
|
|
||||||
//
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
|
|
||||||
func downloadGeoSite(path string) (err error) {
|
func downloadGeoSite(path string) (err error) {
|
||||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
|
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -84,22 +67,9 @@ func downloadGeoSite(path string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//func initGeoIP() error {
|
|
||||||
// if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
|
||||||
// log.Infoln("Can't find GeoIP.dat, start download")
|
|
||||||
// if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
|
|
||||||
// return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
|
|
||||||
// }
|
|
||||||
// log.Infoln("Download GeoIP.dat finish")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
func initGeoSite() error {
|
func initGeoSite() error {
|
||||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find GeoSite.dat, start download")
|
log.Infoln("Need GeoSite but can't find GeoSite.dat, start download")
|
||||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -139,9 +109,5 @@ func Init(dir string) error {
|
||||||
return fmt.Errorf("can't initial MMDB: %w", err)
|
return fmt.Errorf("can't initial MMDB: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial GeoSite
|
|
||||||
if err := initGeoSite(); err != nil {
|
|
||||||
return fmt.Errorf("can't initial GeoSite: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
const (
|
const (
|
||||||
Direct AdapterType = iota
|
Direct AdapterType = iota
|
||||||
Reject
|
Reject
|
||||||
|
Compatible
|
||||||
Shadowsocks
|
Shadowsocks
|
||||||
ShadowsocksR
|
ShadowsocksR
|
||||||
Snell
|
Snell
|
||||||
|
@ -33,6 +33,7 @@ const (
|
||||||
const (
|
const (
|
||||||
DefaultTCPTimeout = 5 * time.Second
|
DefaultTCPTimeout = 5 * time.Second
|
||||||
DefaultUDPTimeout = DefaultTCPTimeout
|
DefaultUDPTimeout = DefaultTCPTimeout
|
||||||
|
DefaultTLSTimeout = DefaultTCPTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
|
@ -128,7 +129,8 @@ func (at AdapterType) String() string {
|
||||||
return "Direct"
|
return "Direct"
|
||||||
case Reject:
|
case Reject:
|
||||||
return "Reject"
|
return "Reject"
|
||||||
|
case Compatible:
|
||||||
|
return "Compatible"
|
||||||
case Shadowsocks:
|
case Shadowsocks:
|
||||||
return "Shadowsocks"
|
return "Shadowsocks"
|
||||||
case ShadowsocksR:
|
case ShadowsocksR:
|
||||||
|
|
|
@ -24,6 +24,7 @@ const (
|
||||||
REDIR
|
REDIR
|
||||||
TPROXY
|
TPROXY
|
||||||
TUN
|
TUN
|
||||||
|
INNER
|
||||||
)
|
)
|
||||||
|
|
||||||
type NetWork int
|
type NetWork int
|
||||||
|
@ -59,6 +60,8 @@ func (t Type) String() string {
|
||||||
return "TProxy"
|
return "TProxy"
|
||||||
case TUN:
|
case TUN:
|
||||||
return "Tun"
|
return "Tun"
|
||||||
|
case INNER:
|
||||||
|
return "Inner"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
@ -94,6 +97,10 @@ func (m *Metadata) SourceDetail() string {
|
||||||
if m.Process != "" {
|
if m.Process != "" {
|
||||||
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
|
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
|
||||||
} else {
|
} else {
|
||||||
|
if m.Type == INNER {
|
||||||
|
return fmt.Sprintf("[Clash]")
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s", m.SourceAddress())
|
return fmt.Sprintf("%s", m.SourceAddress())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,12 @@ const (
|
||||||
Process
|
Process
|
||||||
Script
|
Script
|
||||||
RuleSet
|
RuleSet
|
||||||
|
Network
|
||||||
|
Combination
|
||||||
MATCH
|
MATCH
|
||||||
|
AND
|
||||||
|
OR
|
||||||
|
NOT
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleType int
|
type RuleType int
|
||||||
|
@ -47,6 +52,14 @@ func (rt RuleType) String() string {
|
||||||
return "Match"
|
return "Match"
|
||||||
case RuleSet:
|
case RuleSet:
|
||||||
return "RuleSet"
|
return "RuleSet"
|
||||||
|
case Network:
|
||||||
|
return "Network"
|
||||||
|
case AND:
|
||||||
|
return "AND"
|
||||||
|
case OR:
|
||||||
|
return "OR"
|
||||||
|
case NOT:
|
||||||
|
return "NOT"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,5 @@ var (
|
||||||
Version = "1.9.0"
|
Version = "1.9.0"
|
||||||
BuildTime = "unknown time"
|
BuildTime = "unknown time"
|
||||||
AutoIptables string
|
AutoIptables string
|
||||||
|
ClashName = "Clash.Meta"
|
||||||
)
|
)
|
||||||
|
|
|
@ -60,6 +60,10 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
||||||
address = ""
|
address = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if addr == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package executor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/listener/tproxy"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -10,6 +9,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/listener/tproxy"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
|
@ -74,15 +75,17 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||||
defer mux.Unlock()
|
defer mux.Unlock()
|
||||||
|
|
||||||
updateUsers(cfg.Users)
|
updateUsers(cfg.Users)
|
||||||
|
updateHosts(cfg.Hosts)
|
||||||
updateProxies(cfg.Proxies, cfg.Providers)
|
updateProxies(cfg.Proxies, cfg.Providers)
|
||||||
updateRules(cfg.Rules, cfg.RuleProviders)
|
updateRules(cfg.Rules, cfg.RuleProviders)
|
||||||
updateHosts(cfg.Hosts)
|
|
||||||
updateProfile(cfg)
|
|
||||||
updateIPTables(cfg.DNS, cfg.General, cfg.Tun)
|
updateIPTables(cfg.DNS, cfg.General, cfg.Tun)
|
||||||
updateDNS(cfg.DNS, cfg.Tun)
|
updateDNS(cfg.DNS, cfg.Tun)
|
||||||
updateGeneral(cfg.General, cfg.Tun, force)
|
updateGeneral(cfg.General, cfg.Tun, force)
|
||||||
updateTun(cfg.Tun)
|
updateTun(cfg.Tun)
|
||||||
updateExperimental(cfg)
|
updateExperimental(cfg)
|
||||||
|
loadProvider(cfg.RuleProviders, cfg.Providers)
|
||||||
|
updateProfile(cfg)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneral() *config.General {
|
func GetGeneral() *config.General {
|
||||||
|
@ -175,6 +178,38 @@ func updateRules(rules []C.Rule, ruleProviders map[string]*provider.RuleProvider
|
||||||
tunnel.UpdateRules(rules, ruleProviders)
|
tunnel.UpdateRules(rules, ruleProviders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadProvider(ruleProviders map[string]*provider.RuleProvider, proxyProviders map[string]provider.ProxyProvider) {
|
||||||
|
load := func(pv provider.Provider) {
|
||||||
|
if pv.VehicleType() == provider.Compatible {
|
||||||
|
log.Infoln("Start initial compatible provider %s", pv.Name())
|
||||||
|
} else {
|
||||||
|
log.Infoln("Start initial provider %s", (pv).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := (pv).Initial(); err != nil {
|
||||||
|
switch pv.Type() {
|
||||||
|
case provider.Proxy:
|
||||||
|
{
|
||||||
|
log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err)
|
||||||
|
}
|
||||||
|
case provider.Rule:
|
||||||
|
{
|
||||||
|
log.Warnln("initial rule provider %s error: %v", (pv).Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, proxyProvider := range proxyProviders {
|
||||||
|
load(proxyProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ruleProvider := range ruleProviders {
|
||||||
|
load(*ruleProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
|
func updateGeneral(general *config.General, Tun *config.Tun, force bool) {
|
||||||
tunnel.SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
resolver.DisableIPv6 = !general.IPv6
|
resolver.DisableIPv6 = !general.IPv6
|
||||||
|
|
16
hub/route/script.go
Normal file
16
hub/route/script.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func scriptRouter() http.Handler {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Get("/", getScript)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func getScript(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
}
|
|
@ -72,6 +72,7 @@ func Start(addr string, secret string) {
|
||||||
r.Mount("/connections", connectionRouter())
|
r.Mount("/connections", connectionRouter())
|
||||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
|
r.Mount("/script", scriptRouter())
|
||||||
})
|
})
|
||||||
|
|
||||||
if uiPath != "" {
|
if uiPath != "" {
|
||||||
|
|
20
listener/inner/tcp.go
Normal file
20
listener/inner/tcp.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package inner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tcpIn chan<- C.ConnContext
|
||||||
|
|
||||||
|
func New(in chan<- C.ConnContext) {
|
||||||
|
tcpIn = in
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleTcp(dst string, host string) net.Conn {
|
||||||
|
conn1, conn2 := net.Pipe()
|
||||||
|
context := inbound.NewInner(conn2, dst, host)
|
||||||
|
tcpIn <- context
|
||||||
|
return conn1
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/listener/inner"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -124,6 +125,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
||||||
log.Errorln("Start SOCKS server error: %s", err.Error())
|
log.Errorln("Start SOCKS server error: %s", err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
inner.New(tcpIn)
|
||||||
|
|
||||||
addr := genAddr(bindAddress, port, allowLan)
|
addr := genAddr(bindAddress, port, allowLan)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
package dev
|
package dev
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TunDevice is cross-platform tun interface
|
// TunDevice is cross-platform tun interface
|
||||||
type TunDevice interface {
|
type TunDevice interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
@ -18,54 +10,3 @@ type TunDevice interface {
|
||||||
Read(buff []byte) (int, error)
|
Read(buff []byte) (int, error)
|
||||||
Write(buff []byte) (int, error)
|
Write(buff []byte) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetLinuxAutoRoute() {
|
|
||||||
log.Infoln("Tun adapter auto setting global route")
|
|
||||||
addLinuxSystemRoute("0")
|
|
||||||
//addLinuxSystemRoute("1")
|
|
||||||
//addLinuxSystemRoute("2/7")
|
|
||||||
//addLinuxSystemRoute("4/6")
|
|
||||||
//addLinuxSystemRoute("8/5")
|
|
||||||
//addLinuxSystemRoute("16/4")
|
|
||||||
//addLinuxSystemRoute("32/3")
|
|
||||||
//addLinuxSystemRoute("64/2")
|
|
||||||
//addLinuxSystemRoute("128.0/1")
|
|
||||||
//addLinuxSystemRoute("198.18.0/16")
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveLinuxAutoRoute() {
|
|
||||||
log.Infoln("Tun adapter removing global route")
|
|
||||||
delLinuxSystemRoute("0")
|
|
||||||
//delLinuxSystemRoute("1")
|
|
||||||
//delLinuxSystemRoute("2/7")
|
|
||||||
//delLinuxSystemRoute("4/6")
|
|
||||||
//delLinuxSystemRoute("8/5")
|
|
||||||
//delLinuxSystemRoute("16/4")
|
|
||||||
//delLinuxSystemRoute("32/3")
|
|
||||||
//delLinuxSystemRoute("64/2")
|
|
||||||
//delLinuxSystemRoute("128.0/1")
|
|
||||||
//delLinuxSystemRoute("198.18.0/16")
|
|
||||||
}
|
|
||||||
|
|
||||||
func addLinuxSystemRoute(net string) {
|
|
||||||
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd := exec.Command("route", "add", "-net", net, "meta")
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func delLinuxSystemRoute(net string) {
|
|
||||||
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd := exec.Command("route", "delete", "-net", net, "meta")
|
|
||||||
_ = cmd.Run()
|
|
||||||
//if err := cmd.Run(); err != nil {
|
|
||||||
// log.Errorln("[auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String())
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,11 +5,13 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -176,7 +178,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if autoRoute {
|
if autoRoute {
|
||||||
SetLinuxAutoRoute()
|
setAutoRoute(tunAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tun, nil
|
return tun, nil
|
||||||
|
@ -280,7 +282,7 @@ func (t *tunDarwin) IsClose() bool {
|
||||||
func (t *tunDarwin) Close() error {
|
func (t *tunDarwin) Close() error {
|
||||||
t.stopOnce.Do(func() {
|
t.stopOnce.Do(func() {
|
||||||
if t.autoRoute {
|
if t.autoRoute {
|
||||||
RemoveLinuxAutoRoute()
|
resetAutoRoute(t.tunAddress)
|
||||||
}
|
}
|
||||||
t.closed = true
|
t.closed = true
|
||||||
t.tunFile.Close()
|
t.tunFile.Close()
|
||||||
|
@ -480,15 +482,60 @@ func (t *tunDarwin) attachLinkLocal() error {
|
||||||
|
|
||||||
// GetAutoDetectInterface get ethernet interface
|
// GetAutoDetectInterface get ethernet interface
|
||||||
func GetAutoDetectInterface() (string, error) {
|
func GetAutoDetectInterface() (string, error) {
|
||||||
cmd := exec.Command("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n")
|
cmd := exec.Command("route", "-n", "get", "default")
|
||||||
var out bytes.Buffer
|
if result, err := cmd.Output(); err != nil {
|
||||||
cmd.Stdout = &out
|
return "", err
|
||||||
err := cmd.Run()
|
} else {
|
||||||
|
resultString := string(result)
|
||||||
|
reg, err := regexp.Compile("(interface:)(.*)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if out.Len() == 0 {
|
matchResult := reg.FindStringSubmatch(resultString)
|
||||||
return "", errors.New("interface not found by default route")
|
interfaceName := strings.TrimSpace(matchResult[len(matchResult)-1])
|
||||||
|
return interfaceName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAutoRoute(tunGateway string) {
|
||||||
|
addRoute("1", tunGateway)
|
||||||
|
addRoute("2/7", tunGateway)
|
||||||
|
addRoute("4/6", tunGateway)
|
||||||
|
addRoute("8/5", tunGateway)
|
||||||
|
addRoute("16/4", tunGateway)
|
||||||
|
addRoute("32/3", tunGateway)
|
||||||
|
addRoute("64/2", tunGateway)
|
||||||
|
addRoute("128.0/1", tunGateway)
|
||||||
|
addRoute("198.18.0/16", tunGateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetAutoRoute(tunGateway string) {
|
||||||
|
delRoute("1", tunGateway)
|
||||||
|
delRoute("2/7", tunGateway)
|
||||||
|
delRoute("4/6", tunGateway)
|
||||||
|
delRoute("8/5", tunGateway)
|
||||||
|
delRoute("16/4", tunGateway)
|
||||||
|
delRoute("32/3", tunGateway)
|
||||||
|
delRoute("64/2", tunGateway)
|
||||||
|
delRoute("128.0/1", tunGateway)
|
||||||
|
delRoute("198.18.0/16", tunGateway)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoute(net, name string) {
|
||||||
|
cmd := exec.Command("route", "add", "-net", net, name)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func delRoute(net, name string) {
|
||||||
|
cmd := exec.Command("route", "delete", "-net", net, name)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
||||||
}
|
}
|
||||||
return out.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -38,7 +39,7 @@ type tunLinux struct {
|
||||||
|
|
||||||
// OpenTunDevice return a TunDevice according a URL
|
// OpenTunDevice return a TunDevice according a URL
|
||||||
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||||
deviceURL, _ := url.Parse("dev://meta")
|
deviceURL, _ := url.Parse("dev://utun")
|
||||||
mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32)
|
mtu, _ := strconv.ParseInt(deviceURL.Query().Get("mtu"), 0, 32)
|
||||||
|
|
||||||
t := &tunLinux{
|
t := &tunLinux{
|
||||||
|
@ -62,8 +63,9 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if autoRoute {
|
if autoRoute {
|
||||||
SetLinuxAutoRoute()
|
addRoute(tunAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dev, nil
|
return dev, nil
|
||||||
case "fd":
|
case "fd":
|
||||||
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
|
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
|
||||||
|
@ -76,7 +78,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if autoRoute {
|
if autoRoute {
|
||||||
SetLinuxAutoRoute()
|
log.Warnln("linux unsupported automatic route")
|
||||||
}
|
}
|
||||||
return dev, nil
|
return dev, nil
|
||||||
}
|
}
|
||||||
|
@ -105,9 +107,6 @@ func (t *tunLinux) IsClose() bool {
|
||||||
|
|
||||||
func (t *tunLinux) Close() error {
|
func (t *tunLinux) Close() error {
|
||||||
t.stopOnce.Do(func() {
|
t.stopOnce.Do(func() {
|
||||||
if t.autoRoute {
|
|
||||||
RemoveLinuxAutoRoute()
|
|
||||||
}
|
|
||||||
t.closed = true
|
t.closed = true
|
||||||
t.tunFile.Close()
|
t.tunFile.Close()
|
||||||
})
|
})
|
||||||
|
@ -330,3 +329,21 @@ func GetAutoDetectInterface() (string, error) {
|
||||||
}
|
}
|
||||||
return out.String(), nil
|
return out.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addRoute(gateway string) {
|
||||||
|
cmd := exec.Command("route", "add", "default", "gw", gateway)
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Errorln("[auto route] Failed to add system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func delRoute(gateway string) {
|
||||||
|
cmd := exec.Command("ip", "route", "delete", "gw")
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Errorln("[auto route] Failed to delete system route: %s: %s , cmd: %s", err.Error(), stderr.String(), cmd.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ const nicID tcpip.NICID = 1
|
||||||
type gvisorAdapter struct {
|
type gvisorAdapter struct {
|
||||||
device dev.TunDevice
|
device dev.TunDevice
|
||||||
ipstack *stack.Stack
|
ipstack *stack.Stack
|
||||||
dnsServers []*DNSServer
|
dnsServer *DNSServer
|
||||||
udpIn chan<- *inbound.PacketAdapter
|
udpIn chan<- *inbound.PacketAdapter
|
||||||
|
|
||||||
stackName string
|
stackName string
|
||||||
|
@ -47,7 +47,7 @@ type gvisorAdapter struct {
|
||||||
writeHandle *channel.NotificationHandle
|
writeHandle *channel.NotificationHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
// GvisorAdapter create GvisorAdapter
|
// NewAdapter GvisorAdapter create GvisorAdapter
|
||||||
func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
|
func NewAdapter(device dev.TunDevice, conf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.TunAdapter, error) {
|
||||||
ipstack := stack.New(stack.Options{
|
ipstack := stack.New(stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
|
@ -132,7 +132,7 @@ func (t *gvisorAdapter) AutoRoute() bool {
|
||||||
|
|
||||||
// Close close the TunAdapter
|
// Close close the TunAdapter
|
||||||
func (t *gvisorAdapter) Close() {
|
func (t *gvisorAdapter) Close() {
|
||||||
t.StopAllDNSServer()
|
t.StopDNSServer()
|
||||||
if t.ipstack != nil {
|
if t.ipstack != nil {
|
||||||
t.ipstack.Close()
|
t.ipstack.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@ package gvisor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
Common "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
@ -23,15 +24,33 @@ var (
|
||||||
ipv6Zero = tcpip.Address(net.IPv6zero.To16())
|
ipv6Zero = tcpip.Address(net.IPv6zero.To16())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ListenerWrap struct {
|
||||||
|
net.Listener
|
||||||
|
listener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerWrap) Accept() (conn net.Conn, err error) {
|
||||||
|
conn, err = l.listener.Accept()
|
||||||
|
log.Debugln("[DNS] hijack tcp:%s", l.Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerWrap) Close() error {
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerWrap) Addr() net.Addr {
|
||||||
|
return l.listener.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
// DNSServer is DNS Server listening on tun devcice
|
// DNSServer is DNS Server listening on tun devcice
|
||||||
type DNSServer struct {
|
type DNSServer struct {
|
||||||
*dns.Server
|
dnsServers []*dns.Server
|
||||||
|
tcpListeners []net.Listener
|
||||||
resolver *dns.Resolver
|
resolver *dns.Resolver
|
||||||
|
|
||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
tcpListener net.Listener
|
udpEndpoints []*dnsEndpoint
|
||||||
udpEndpoint *dnsEndpoint
|
udpEndpointIDs []*stack.TransportEndpointID
|
||||||
udpEndpointID *stack.TransportEndpointID
|
|
||||||
tcpip.NICID
|
tcpip.NICID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +85,7 @@ func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pack
|
||||||
var msg D.Msg
|
var msg D.Msg
|
||||||
msg.Unpack(pkt.Data().AsRange().ToOwnedView())
|
msg.Unpack(pkt.Data().AsRange().ToOwnedView())
|
||||||
writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id}
|
writer := dnsResponseWriter{s: e.stack, pkt: pkt, id: id}
|
||||||
|
log.Debugln("[DNS] hijack udp:%s:%d", id.LocalAddress.String(), id.LocalPort)
|
||||||
go e.server.ServeDNS(&writer, &msg)
|
go e.server.ServeDNS(&writer, &msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,26 +149,66 @@ func (w *dnsResponseWriter) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDNSServer create a dns server on given netstack
|
// CreateDNSServer create a dns server on given netstack
|
||||||
func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, ip net.IP, port int, nicID tcpip.NICID) (*DNSServer, error) {
|
func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijack []net.Addr, nicID tcpip.NICID) (*DNSServer, error) {
|
||||||
var v4 bool
|
|
||||||
var err error
|
var err error
|
||||||
|
handler := dns.NewHandler(resolver, mapper)
|
||||||
|
serverIn := &dns.Server{}
|
||||||
|
serverIn.SetHandler(handler)
|
||||||
|
tcpDnsArr := make([]net.TCPAddr, 0, len(dnsHijack))
|
||||||
|
udpDnsArr := make([]net.UDPAddr, 0, len(dnsHijack))
|
||||||
|
for _, d := range dnsHijack {
|
||||||
|
switch d.(type) {
|
||||||
|
case *net.TCPAddr:
|
||||||
|
{
|
||||||
|
tcpDnsArr = append(tcpDnsArr, *d.(*net.TCPAddr))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case *net.UDPAddr:
|
||||||
|
{
|
||||||
|
udpDnsArr = append(udpDnsArr, *d.(*net.UDPAddr))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints, ids := hijackUdpDns(udpDnsArr, s, serverIn)
|
||||||
|
tcpListeners, dnsServers := hijackTcpDns(tcpDnsArr, s, serverIn)
|
||||||
|
server := &DNSServer{
|
||||||
|
resolver: resolver,
|
||||||
|
stack: s,
|
||||||
|
udpEndpoints: endpoints,
|
||||||
|
udpEndpointIDs: ids,
|
||||||
|
NICID: nicID,
|
||||||
|
tcpListeners: tcpListeners,
|
||||||
|
}
|
||||||
|
|
||||||
|
server.dnsServers = dnsServers
|
||||||
|
|
||||||
|
return server, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hijackUdpDns(dnsArr []net.UDPAddr, s *stack.Stack, serverIn *dns.Server) ([]*dnsEndpoint, []*stack.TransportEndpointID) {
|
||||||
|
endpoints := make([]*dnsEndpoint, len(dnsArr))
|
||||||
|
ids := make([]*stack.TransportEndpointID, len(dnsArr))
|
||||||
|
for i, dns := range dnsArr {
|
||||||
|
port := dns.Port
|
||||||
|
ip := dns.IP
|
||||||
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
||||||
var protocol tcpip.NetworkProtocolNumber
|
var protocol tcpip.NetworkProtocolNumber
|
||||||
if ip.To4() != nil {
|
if ip.To4() != nil {
|
||||||
v4 = true
|
|
||||||
address.Addr = tcpip.Address(ip.To4())
|
address.Addr = tcpip.Address(ip.To4())
|
||||||
protocol = ipv4.ProtocolNumber
|
protocol = ipv4.ProtocolNumber
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
v4 = false
|
|
||||||
address.Addr = tcpip.Address(ip.To16())
|
address.Addr = tcpip.Address(ip.To16())
|
||||||
protocol = ipv6.ProtocolNumber
|
protocol = ipv6.ProtocolNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolAddr := tcpip.ProtocolAddress{
|
protocolAddr := tcpip.ProtocolAddress{
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
AddressWithPrefix: address.Addr.WithPrefix(),
|
AddressWithPrefix: address.Addr.WithPrefix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints
|
// netstack will only reassemble IP fragments when its' dest ip address is registered in NIC.endpoints
|
||||||
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
|
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
|
||||||
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
|
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
|
||||||
|
@ -158,10 +218,6 @@ func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.Resolve
|
||||||
address.Addr = ""
|
address.Addr = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := dns.NewHandler(resolver, mapper)
|
|
||||||
serverIn := &dns.Server{}
|
|
||||||
serverIn.SetHandler(handler)
|
|
||||||
|
|
||||||
// UDP DNS
|
// UDP DNS
|
||||||
id := &stack.TransportEndpointID{
|
id := &stack.TransportEndpointID{
|
||||||
LocalAddress: address.Addr,
|
LocalAddress: address.Addr,
|
||||||
|
@ -190,44 +246,72 @@ func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.Resolve
|
||||||
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
|
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP DNS
|
ids[i] = id
|
||||||
|
endpoints[i] = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints, ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func hijackTcpDns(dnsArr []net.TCPAddr, s *stack.Stack, serverIn *dns.Server) ([]net.Listener, []*dns.Server) {
|
||||||
|
tcpListeners := make([]net.Listener, len(dnsArr))
|
||||||
|
dnsServers := make([]*dns.Server, len(dnsArr))
|
||||||
|
|
||||||
|
for i, dnsAddr := range dnsArr {
|
||||||
var tcpListener net.Listener
|
var tcpListener net.Listener
|
||||||
|
var v4 bool
|
||||||
|
var err error
|
||||||
|
port := dnsAddr.Port
|
||||||
|
ip := dnsAddr.IP
|
||||||
|
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
||||||
|
if ip.To4() != nil {
|
||||||
|
address.Addr = tcpip.Address(ip.To4())
|
||||||
|
v4 = true
|
||||||
|
} else {
|
||||||
|
address.Addr = tcpip.Address(ip.To16())
|
||||||
|
v4 = false
|
||||||
|
}
|
||||||
|
|
||||||
if v4 {
|
if v4 {
|
||||||
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
|
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
|
||||||
} else {
|
} else {
|
||||||
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
|
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can not listen on tun: %v", err)
|
log.Errorln("can not listen on tun: %v, hijack tcp[%s] failed", err, dnsAddr)
|
||||||
|
} else {
|
||||||
|
tcpListeners[i] = tcpListener
|
||||||
|
server := &D.Server{Listener: &ListenerWrap{
|
||||||
|
listener: tcpListener,
|
||||||
|
}, Handler: serverIn}
|
||||||
|
dnsServer := dns.Server{}
|
||||||
|
dnsServer.Server = server
|
||||||
|
go dnsServer.ActivateAndServe()
|
||||||
|
dnsServers[i] = &dnsServer
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &DNSServer{
|
|
||||||
Server: serverIn,
|
|
||||||
resolver: resolver,
|
|
||||||
stack: s,
|
|
||||||
tcpListener: tcpListener,
|
|
||||||
udpEndpoint: endpoint,
|
|
||||||
udpEndpointID: id,
|
|
||||||
NICID: nicID,
|
|
||||||
}
|
}
|
||||||
server.SetHandler(handler)
|
//
|
||||||
server.Server.Server = &D.Server{Listener: tcpListener, Handler: server}
|
//for _, listener := range tcpListeners {
|
||||||
|
// server := &D.Server{Listener: listener, Handler: serverIn}
|
||||||
|
//
|
||||||
|
// dnsServers = append(dnsServers, &dnsServer)
|
||||||
|
// go dnsServer.ActivateAndServe()
|
||||||
|
//}
|
||||||
|
|
||||||
go func() {
|
return tcpListeners, dnsServers
|
||||||
server.ActivateAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return server, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stop the DNS Server on tun
|
// Stop stop the DNS Server on tun
|
||||||
func (s *DNSServer) Stop() {
|
func (s *DNSServer) Stop() {
|
||||||
// shutdown TCP DNS Server
|
if s == nil {
|
||||||
s.Server.Shutdown()
|
return
|
||||||
// remove TCP endpoint from stack
|
|
||||||
if s.Listener != nil {
|
|
||||||
s.Listener.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s.udpEndpointIDs); i++ {
|
||||||
|
ep := s.udpEndpoints[i]
|
||||||
|
id := s.udpEndpointIDs[i]
|
||||||
// remove udp endpoint from stack
|
// remove udp endpoint from stack
|
||||||
s.stack.UnregisterTransportEndpoint(
|
s.stack.UnregisterTransportEndpoint(
|
||||||
[]tcpip.NetworkProtocolNumber{
|
[]tcpip.NetworkProtocolNumber{
|
||||||
|
@ -235,61 +319,106 @@ func (s *DNSServer) Stop() {
|
||||||
ipv6.ProtocolNumber,
|
ipv6.ProtocolNumber,
|
||||||
},
|
},
|
||||||
udp.ProtocolNumber,
|
udp.ProtocolNumber,
|
||||||
*s.udpEndpointID,
|
*id,
|
||||||
s.udpEndpoint,
|
ep,
|
||||||
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
|
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
|
||||||
s.NICID)
|
s.NICID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range s.dnsServers {
|
||||||
|
server.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, listener := range s.tcpListeners {
|
||||||
|
listener.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DnsHijack return the listening address of DNS Server
|
// DnsHijack return the listening address of DNS Server
|
||||||
func (t *gvisorAdapter) DnsHijack() []string {
|
func (t *gvisorAdapter) DnsHijack() []string {
|
||||||
results := make([]string, len(t.dnsServers))
|
dnsHijackArr := make([]string, len(t.dnsServer.udpEndpoints))
|
||||||
for i, dnsServer := range t.dnsServers {
|
for _, id := range t.dnsServer.udpEndpointIDs {
|
||||||
id := dnsServer.udpEndpointID
|
dnsHijackArr = append(dnsHijackArr, fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort))
|
||||||
results[i] = fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return dnsHijackArr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *gvisorAdapter) StopAllDNSServer() {
|
func (t *gvisorAdapter) StopDNSServer() {
|
||||||
for _, dnsServer := range t.dnsServers {
|
t.dnsServer.Stop()
|
||||||
dnsServer.Stop()
|
|
||||||
}
|
|
||||||
log.Debugln("tun DNS server stoped")
|
log.Debugln("tun DNS server stoped")
|
||||||
t.dnsServers = nil
|
t.dnsServer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReCreateDNSServer recreate the DNS Server on tun
|
// ReCreateDNSServer recreate the DNS Server on tun
|
||||||
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addrs []string) error {
|
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijackArr []string) error {
|
||||||
t.StopAllDNSServer()
|
t.StopDNSServer()
|
||||||
|
|
||||||
if resolver == nil {
|
if resolver == nil {
|
||||||
return fmt.Errorf("failed to create DNS server on tun: resolver not provided")
|
return fmt.Errorf("failed to create DNS server on tun: resolver not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(addrs) == 0 {
|
if len(dnsHijackArr) == 0 {
|
||||||
return fmt.Errorf("failed to create DNS server on tun: len(addrs) == 0")
|
return fmt.Errorf("failed to create DNS server on tun: len(addrs) == 0")
|
||||||
}
|
}
|
||||||
for _, addr := range addrs {
|
|
||||||
var err error
|
var err error
|
||||||
_, port, err := net.SplitHostPort(addr)
|
var addrs []net.Addr
|
||||||
if port == "0" || port == "" || err != nil {
|
for _, addr := range dnsHijackArr {
|
||||||
return nil
|
var (
|
||||||
}
|
addrType string
|
||||||
|
hostPort string
|
||||||
|
)
|
||||||
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
addrType, hostPort, err = Common.SplitNetworkType(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID)
|
var (
|
||||||
|
host, port string
|
||||||
|
hasPort bool
|
||||||
|
)
|
||||||
|
|
||||||
|
host, port, hasPort, err = Common.SplitHostPort(hostPort)
|
||||||
|
if !hasPort {
|
||||||
|
port = "53"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch addrType {
|
||||||
|
case "udp", "":
|
||||||
|
{
|
||||||
|
var udpDNS *net.UDPAddr
|
||||||
|
udpDNS, err = net.ResolveUDPAddr("udp", net.JoinHostPort(host, port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.dnsServers = append(t.dnsServers, server)
|
|
||||||
log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled())
|
addrs = append(addrs, udpDNS)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
case "tcp":
|
||||||
|
{
|
||||||
|
var tcpDNS *net.TCPAddr
|
||||||
|
tcpDNS, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs = append(addrs, tcpDNS)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unspported dns scheme:%s", addrType)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := CreateDNSServer(t.ipstack, resolver, mapper, addrs, nicID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.dnsServer = server
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -22,7 +22,7 @@ func HasNoResolve(params []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func findNetwork(params []string) C.NetWork {
|
func FindNetwork(params []string) C.NetWork {
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p == "tcp" {
|
if p == "tcp" {
|
||||||
return C.TCP
|
return C.TCP
|
||||||
|
@ -33,7 +33,7 @@ func findNetwork(params []string) C.NetWork {
|
||||||
return C.ALLNet
|
return C.ALLNet
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSourceIPs(params []string) []*net.IPNet {
|
func FindSourceIPs(params []string) []*net.IPNet {
|
||||||
var ips []*net.IPNet
|
var ips []*net.IPNet
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p == noResolve || len(p) < 7 {
|
if p == noResolve || len(p) < 7 {
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
53
rule/common/network_type.go
Normal file
53
rule/common/network_type.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkType struct {
|
||||||
|
network C.NetWork
|
||||||
|
adapter string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetworkType(network, adapter string) (*NetworkType, error) {
|
||||||
|
ntType := new(NetworkType)
|
||||||
|
ntType.adapter = adapter
|
||||||
|
switch strings.ToUpper(network) {
|
||||||
|
case "TCP":
|
||||||
|
ntType.network = C.TCP
|
||||||
|
break
|
||||||
|
case "UDP":
|
||||||
|
ntType.network = C.UDP
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported network type, only TCP/UDP")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ntType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkType) RuleType() C.RuleType {
|
||||||
|
return C.Network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkType) Match(metadata *C.Metadata) bool {
|
||||||
|
return n.network == metadata.NetWork
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkType) Adapter() string {
|
||||||
|
return n.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkType) Payload() string {
|
||||||
|
return n.network.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkType) ShouldResolveIP() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetworkType) RuleExtra() *C.RuleExtra {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -35,6 +35,7 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||||
|
if strings.TrimSpace(metadata.Process) == "" {
|
||||||
cached, hit := processCache.Get(key)
|
cached, hit := processCache.Get(key)
|
||||||
if !hit {
|
if !hit {
|
||||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||||
|
@ -54,6 +55,7 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Process = cached.(string)
|
metadata.Process = cached.(string)
|
||||||
|
}
|
||||||
|
|
||||||
return strings.EqualFold(metadata.Process, ps.process)
|
return strings.EqualFold(metadata.Process, ps.process)
|
||||||
}
|
}
|
58
rule/logic/and.go
Normal file
58
rule/logic/and.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package logic
|
||||||
|
|
||||||
|
import C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
|
type AND struct {
|
||||||
|
rules []C.Rule
|
||||||
|
payload string
|
||||||
|
adapter string
|
||||||
|
needIP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAND(payload string, adapter string) (*AND, error) {
|
||||||
|
and := &AND{payload: payload, adapter: adapter}
|
||||||
|
rules, err := parseRuleByPayload(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
and.rules = rules
|
||||||
|
for _, rule := range rules {
|
||||||
|
if rule.ShouldResolveIP() {
|
||||||
|
and.needIP = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return and, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (A *AND) RuleType() C.RuleType {
|
||||||
|
return C.AND
|
||||||
|
}
|
||||||
|
|
||||||
|
func (A *AND) Match(metadata *C.Metadata) bool {
|
||||||
|
for _, rule := range A.rules {
|
||||||
|
if !rule.Match(metadata) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (A *AND) Adapter() string {
|
||||||
|
return A.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (A *AND) Payload() string {
|
||||||
|
return A.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (A *AND) ShouldResolveIP() bool {
|
||||||
|
return A.needIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (A *AND) RuleExtra() *C.RuleExtra {
|
||||||
|
return nil
|
||||||
|
}
|
168
rule/logic/common.go
Normal file
168
rule/logic/common.go
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/common/collections"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
RC "github.com/Dreamacro/clash/rule/common"
|
||||||
|
"github.com/Dreamacro/clash/rule/provider"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRuleByPayload(payload string) ([]C.Rule, error) {
|
||||||
|
regex, err := regexp.Compile("\\(.*\\)")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if regex.MatchString(payload) {
|
||||||
|
subAllRanges, err := format(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rules := make([]C.Rule, 0, len(subAllRanges))
|
||||||
|
|
||||||
|
subRanges := findSubRuleRange(payload, subAllRanges)
|
||||||
|
for _, subRange := range subRanges {
|
||||||
|
subPayload := payload[subRange.start+1 : subRange.end]
|
||||||
|
|
||||||
|
rule, err := payloadToRule(subPayload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("payload format error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func containRange(r Range, preStart, preEnd int) bool {
|
||||||
|
return preStart < r.start && preEnd > r.end
|
||||||
|
}
|
||||||
|
|
||||||
|
func payloadToRule(subPayload string) (C.Rule, error) {
|
||||||
|
splitStr := strings.SplitN(subPayload, ",", 2)
|
||||||
|
tp := splitStr[0]
|
||||||
|
payload := splitStr[1]
|
||||||
|
if tp == "NOT" || tp == "OR" || tp == "AND" {
|
||||||
|
return parseRule(tp, payload, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
param := strings.Split(payload, ",")
|
||||||
|
return parseRule(tp, param[0], param[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRule(tp, payload string, params []string) (C.Rule, error) {
|
||||||
|
var (
|
||||||
|
parseErr error
|
||||||
|
parsed C.Rule
|
||||||
|
)
|
||||||
|
|
||||||
|
switch tp {
|
||||||
|
case "DOMAIN":
|
||||||
|
parsed = RC.NewDomain(payload, "", nil)
|
||||||
|
case "DOMAIN-SUFFIX":
|
||||||
|
parsed = RC.NewDomainSuffix(payload, "", nil)
|
||||||
|
case "DOMAIN-KEYWORD":
|
||||||
|
parsed = RC.NewDomainKeyword(payload, "", nil)
|
||||||
|
case "GEOSITE":
|
||||||
|
parsed, parseErr = RC.NewGEOSITE(payload, "", nil)
|
||||||
|
case "GEOIP":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RC.NewGEOIP(payload, "", noResolve, nil)
|
||||||
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRNoResolve(noResolve))
|
||||||
|
case "SRC-IP-CIDR":
|
||||||
|
parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||||
|
case "SRC-PORT":
|
||||||
|
parsed, parseErr = RC.NewPort(payload, "", true, nil)
|
||||||
|
case "DST-PORT":
|
||||||
|
parsed, parseErr = RC.NewPort(payload, "", false, nil)
|
||||||
|
case "PROCESS-NAME":
|
||||||
|
parsed, parseErr = RC.NewProcess(payload, "", nil)
|
||||||
|
case "RULE-SET":
|
||||||
|
parsed, parseErr = provider.NewRuleSet(payload, "", nil)
|
||||||
|
case "NOT":
|
||||||
|
parsed, parseErr = NewNOT(payload, "")
|
||||||
|
case "AND":
|
||||||
|
parsed, parseErr = NewAND(payload, "")
|
||||||
|
case "OR":
|
||||||
|
parsed, parseErr = NewOR(payload, "")
|
||||||
|
case "NETWORK":
|
||||||
|
parsed, parseErr = RC.NewNetworkType(payload, "")
|
||||||
|
default:
|
||||||
|
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, parseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Range struct {
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func format(payload string) ([]Range, error) {
|
||||||
|
stack := collections.NewStack()
|
||||||
|
num := 0
|
||||||
|
subRanges := make([]Range, 0)
|
||||||
|
for i, c := range payload {
|
||||||
|
if c == '(' {
|
||||||
|
sr := Range{
|
||||||
|
start: i,
|
||||||
|
index: num,
|
||||||
|
}
|
||||||
|
|
||||||
|
num++
|
||||||
|
stack.Push(sr)
|
||||||
|
} else if c == ')' {
|
||||||
|
sr := stack.Pop().(Range)
|
||||||
|
sr.end = i
|
||||||
|
subRanges = append(subRanges, sr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stack.Len() != 0 {
|
||||||
|
return nil, fmt.Errorf("format error is missing )")
|
||||||
|
}
|
||||||
|
|
||||||
|
sortResult := make([]Range, len(subRanges))
|
||||||
|
for _, sr := range subRanges {
|
||||||
|
sortResult[sr.index] = sr
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSubRuleRange(payload string, ruleRanges []Range) []Range {
|
||||||
|
payloadLen := len(payload)
|
||||||
|
subRuleRange := make([]Range, 0)
|
||||||
|
for _, rr := range ruleRanges {
|
||||||
|
if rr.start == 0 && rr.end == payloadLen-1 {
|
||||||
|
// 最大范围跳过
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
containInSub := false
|
||||||
|
for _, r := range subRuleRange {
|
||||||
|
if containRange(rr, r.start, r.end) {
|
||||||
|
// The subRuleRange contains a range of rr, which is the next level node of the tree
|
||||||
|
containInSub = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containInSub {
|
||||||
|
subRuleRange = append(subRuleRange, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subRuleRange
|
||||||
|
}
|
51
rule/logic/not.go
Normal file
51
rule/logic/not.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NOT struct {
|
||||||
|
rule C.Rule
|
||||||
|
payload string
|
||||||
|
adapter string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNOT(payload string, adapter string) (*NOT, error) {
|
||||||
|
not := &NOT{payload: payload, adapter: adapter}
|
||||||
|
rule, err := parseRuleByPayload(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rule) < 1 {
|
||||||
|
return nil, fmt.Errorf("the parsed rule is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
not.rule = rule[0]
|
||||||
|
return not, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (not *NOT) RuleType() C.RuleType {
|
||||||
|
return C.NOT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (not *NOT) Match(metadata *C.Metadata) bool {
|
||||||
|
return !not.rule.Match(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (not *NOT) Adapter() string {
|
||||||
|
return not.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (not *NOT) Payload() string {
|
||||||
|
return not.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (not *NOT) ShouldResolveIP() bool {
|
||||||
|
return not.rule.ShouldResolveIP()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (not *NOT) RuleExtra() *C.RuleExtra {
|
||||||
|
return nil
|
||||||
|
}
|
58
rule/logic/or.go
Normal file
58
rule/logic/or.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package logic
|
||||||
|
|
||||||
|
import C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
|
type OR struct {
|
||||||
|
rules []C.Rule
|
||||||
|
payload string
|
||||||
|
adapter string
|
||||||
|
needIP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (or *OR) RuleType() C.RuleType {
|
||||||
|
return C.OR
|
||||||
|
}
|
||||||
|
|
||||||
|
func (or *OR) Match(metadata *C.Metadata) bool {
|
||||||
|
for _, rule := range or.rules {
|
||||||
|
if rule.Match(metadata) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (or *OR) Adapter() string {
|
||||||
|
return or.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (or *OR) Payload() string {
|
||||||
|
return or.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (or *OR) ShouldResolveIP() bool {
|
||||||
|
return or.needIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (or *OR) RuleExtra() *C.RuleExtra {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOR(payload string, adapter string) (*OR, error) {
|
||||||
|
or := &OR{payload: payload, adapter: adapter}
|
||||||
|
rules, err := parseRuleByPayload(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
or.rules = rules
|
||||||
|
for _, rule := range rules {
|
||||||
|
if rule.ShouldResolveIP() {
|
||||||
|
or.needIP = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return or, nil
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
package rules
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
P "github.com/Dreamacro/clash/constant/provider"
|
RC "github.com/Dreamacro/clash/rule/common"
|
||||||
"time"
|
"github.com/Dreamacro/clash/rule/logic"
|
||||||
|
RP "github.com/Dreamacro/clash/rule/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
||||||
|
@ -16,81 +15,48 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
||||||
)
|
)
|
||||||
|
|
||||||
ruleExtra := &C.RuleExtra{
|
ruleExtra := &C.RuleExtra{
|
||||||
Network: findNetwork(params),
|
Network: RC.FindNetwork(params),
|
||||||
SourceIPs: findSourceIPs(params),
|
SourceIPs: RC.FindSourceIPs(params),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tp {
|
switch tp {
|
||||||
case "DOMAIN":
|
case "DOMAIN":
|
||||||
parsed = NewDomain(payload, target, ruleExtra)
|
parsed = RC.NewDomain(payload, target, ruleExtra)
|
||||||
case "DOMAIN-SUFFIX":
|
case "DOMAIN-SUFFIX":
|
||||||
parsed = NewDomainSuffix(payload, target, ruleExtra)
|
parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
|
||||||
case "DOMAIN-KEYWORD":
|
case "DOMAIN-KEYWORD":
|
||||||
parsed = NewDomainKeyword(payload, target, ruleExtra)
|
parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
|
||||||
case "GEOSITE":
|
case "GEOSITE":
|
||||||
parsed, parseErr = NewGEOSITE(payload, target, ruleExtra)
|
parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
|
||||||
case "GEOIP":
|
case "GEOIP":
|
||||||
noResolve := HasNoResolve(params)
|
noResolve := RC.HasNoResolve(params)
|
||||||
parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra)
|
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra)
|
||||||
case "IP-CIDR", "IP-CIDR6":
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
noResolve := HasNoResolve(params)
|
noResolve := RC.HasNoResolve(params)
|
||||||
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve))
|
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
|
||||||
case "SRC-IP-CIDR":
|
case "SRC-IP-CIDR":
|
||||||
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true))
|
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||||
case "SRC-PORT":
|
case "SRC-PORT":
|
||||||
parsed, parseErr = NewPort(payload, target, true, ruleExtra)
|
parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
|
||||||
case "DST-PORT":
|
case "DST-PORT":
|
||||||
parsed, parseErr = NewPort(payload, target, false, ruleExtra)
|
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
|
||||||
case "PROCESS-NAME":
|
case "PROCESS-NAME":
|
||||||
parsed, parseErr = NewProcess(payload, target, ruleExtra)
|
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
|
||||||
case "MATCH":
|
case "MATCH":
|
||||||
parsed = NewMatch(target, ruleExtra)
|
parsed = RC.NewMatch(target, ruleExtra)
|
||||||
case "RULE-SET":
|
case "RULE-SET":
|
||||||
parsed, parseErr = NewRuleSet(payload, target, ruleExtra)
|
parsed, parseErr = RP.NewRuleSet(payload, target, ruleExtra)
|
||||||
|
case "NETWORK":
|
||||||
|
parsed, parseErr = RC.NewNetworkType(payload, target)
|
||||||
|
case "AND":
|
||||||
|
parsed, parseErr = logic.NewAND(payload, target)
|
||||||
|
case "OR":
|
||||||
|
parsed, parseErr = logic.NewOR(payload, target)
|
||||||
|
case "NOT":
|
||||||
|
parsed, parseErr = logic.NewNOT(payload, target)
|
||||||
default:
|
default:
|
||||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed, parseErr
|
return parsed, parseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
type ruleProviderSchema struct {
|
|
||||||
Type string `provider:"type"`
|
|
||||||
Behavior string `provider:"behavior"`
|
|
||||||
Path string `provider:"path"`
|
|
||||||
URL string `provider:"url,omitempty"`
|
|
||||||
Interval int `provider:"interval,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) {
|
|
||||||
schema := &ruleProviderSchema{}
|
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
|
||||||
if err := decoder.Decode(mapping, schema); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var behavior P.RuleType
|
|
||||||
|
|
||||||
switch schema.Behavior {
|
|
||||||
case "domain":
|
|
||||||
behavior = P.Domain
|
|
||||||
case "ipcidr":
|
|
||||||
behavior = P.IPCIDR
|
|
||||||
case "classical":
|
|
||||||
behavior = P.Classical
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior)
|
|
||||||
}
|
|
||||||
|
|
||||||
path := C.Path.Resolve(schema.Path)
|
|
||||||
var vehicle P.Vehicle
|
|
||||||
switch schema.Type {
|
|
||||||
case "file":
|
|
||||||
vehicle = provider.NewFileVehicle(path)
|
|
||||||
case "http":
|
|
||||||
vehicle = provider.NewHTTPVehicle(schema.URL, path)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
90
rule/provider/parse.go
Normal file
90
rule/provider/parse.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
P "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
RC "github.com/Dreamacro/clash/rule/common"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ruleProviderSchema struct {
|
||||||
|
Type string `provider:"type"`
|
||||||
|
Behavior string `provider:"behavior"`
|
||||||
|
Path string `provider:"path"`
|
||||||
|
URL string `provider:"url,omitempty"`
|
||||||
|
Interval int `provider:"interval,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) {
|
||||||
|
schema := &ruleProviderSchema{}
|
||||||
|
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||||
|
if err := decoder.Decode(mapping, schema); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var behavior P.RuleType
|
||||||
|
|
||||||
|
switch schema.Behavior {
|
||||||
|
case "domain":
|
||||||
|
behavior = P.Domain
|
||||||
|
case "ipcidr":
|
||||||
|
behavior = P.IPCIDR
|
||||||
|
case "classical":
|
||||||
|
behavior = P.Classical
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := C.Path.Resolve(schema.Path)
|
||||||
|
var vehicle P.Vehicle
|
||||||
|
switch schema.Type {
|
||||||
|
case "file":
|
||||||
|
vehicle = provider.NewFileVehicle(path)
|
||||||
|
case "http":
|
||||||
|
vehicle = provider.NewHTTPVehicle(schema.URL, path)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
||||||
|
var (
|
||||||
|
parseErr error
|
||||||
|
parsed C.Rule
|
||||||
|
)
|
||||||
|
|
||||||
|
ruleExtra := &C.RuleExtra{
|
||||||
|
Network: RC.FindNetwork(params),
|
||||||
|
SourceIPs: RC.FindSourceIPs(params),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tp {
|
||||||
|
case "DOMAIN":
|
||||||
|
parsed = RC.NewDomain(payload, target, ruleExtra)
|
||||||
|
case "DOMAIN-SUFFIX":
|
||||||
|
parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
|
||||||
|
case "DOMAIN-KEYWORD":
|
||||||
|
parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
|
||||||
|
case "GEOSITE":
|
||||||
|
parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
|
||||||
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
|
||||||
|
case "SRC-IP-CIDR":
|
||||||
|
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||||
|
case "SRC-PORT":
|
||||||
|
parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
|
||||||
|
case "DST-PORT":
|
||||||
|
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
|
||||||
|
case "PROCESS-NAME":
|
||||||
|
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
|
||||||
|
default:
|
||||||
|
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, parseErr
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -75,6 +75,10 @@ func (rp *ruleSetProvider) Behavior() P.RuleType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
|
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
|
||||||
|
if rp.count == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
switch rp.behavior {
|
switch rp.behavior {
|
||||||
case P.Domain:
|
case P.Domain:
|
||||||
return rp.DomainRules.Search(metadata.Host) != nil
|
return rp.DomainRules.Search(metadata.Host) != nil
|
||||||
|
@ -201,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) {
|
||||||
return nil, errors.New("error rule type")
|
return nil, errors.New("error rule type")
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := ParseRule(ruleType, rule, "", params)
|
r, err := parseRule(ruleType, rule, "", params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -32,7 +32,7 @@ const (
|
||||||
ImageVmess = "v2fly/v2fly-core:latest"
|
ImageVmess = "v2fly/v2fly-core:latest"
|
||||||
ImageTrojan = "trojangfw/trojan:latest"
|
ImageTrojan = "trojangfw/trojan:latest"
|
||||||
ImageTrojanGo = "p4gefau1t/trojan-go:latest"
|
ImageTrojanGo = "p4gefau1t/trojan-go:latest"
|
||||||
ImageSnell = "icpz/snell-server:latest"
|
ImageSnell = "ghcr.io/icpz/snell-server:latest"
|
||||||
ImageXray = "teddysun/xray:latest"
|
ImageXray = "teddysun/xray:latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,42 @@ func TestClash_Snell(t *testing.T) {
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClash_Snellv3(t *testing.T) {
|
||||||
|
cfg := &container.Config{
|
||||||
|
Image: ImageSnell,
|
||||||
|
ExposedPorts: defaultExposedPorts,
|
||||||
|
Cmd: []string{"-c", "/config.conf"},
|
||||||
|
}
|
||||||
|
hostCfg := &container.HostConfig{
|
||||||
|
PortBindings: defaultPortBindings,
|
||||||
|
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell.conf"))},
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := startContainer(cfg, hostCfg, "snell")
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(t, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
cleanContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy, err := outbound.NewSnell(outbound.SnellOption{
|
||||||
|
Name: "snell",
|
||||||
|
Server: localIP.String(),
|
||||||
|
Port: 10002,
|
||||||
|
Psk: "password",
|
||||||
|
UDP: true,
|
||||||
|
Version: 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
assert.FailNow(t, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
testSuit(t, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
func Benchmark_Snell(b *testing.B) {
|
func Benchmark_Snell(b *testing.B) {
|
||||||
cfg := &container.Config{
|
cfg := &container.Config{
|
||||||
Image: ImageSnell,
|
Image: ImageSnell,
|
||||||
|
|
|
@ -5,6 +5,7 @@ package gun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
@ -173,7 +175,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
cn := tls.Client(pconn, cfg)
|
cn := tls.Client(pconn, cfg)
|
||||||
if err := cn.Handshake(); err != nil {
|
|
||||||
|
// fix tls handshake not timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := cn.HandshakeContext(ctx); err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||||
)
|
)
|
||||||
|
@ -15,13 +17,19 @@ import (
|
||||||
const (
|
const (
|
||||||
Version1 = 1
|
Version1 = 1
|
||||||
Version2 = 2
|
Version2 = 2
|
||||||
|
Version3 = 3
|
||||||
DefaultSnellVersion = Version1
|
DefaultSnellVersion = Version1
|
||||||
|
|
||||||
|
// max packet length
|
||||||
|
maxLength = 0x3FFF
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CommandPing byte = 0
|
CommandPing byte = 0
|
||||||
CommandConnect byte = 1
|
CommandConnect byte = 1
|
||||||
CommandConnectV2 byte = 5
|
CommandConnectV2 byte = 5
|
||||||
|
CommandUDP byte = 6
|
||||||
|
CommondUDPForward byte = 1
|
||||||
|
|
||||||
CommandTunnel byte = 0
|
CommandTunnel byte = 0
|
||||||
CommandPong byte = 1
|
CommandPong byte = 1
|
||||||
|
@ -100,6 +108,16 @@ func WriteHeader(conn net.Conn, host string, port uint, version int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WriteUDPHeader(conn net.Conn, version int) error {
|
||||||
|
if version < Version3 {
|
||||||
|
return errors.New("unsupport UDP version")
|
||||||
|
}
|
||||||
|
|
||||||
|
// version, command, clientID length
|
||||||
|
_, err := conn.Write([]byte{Version, CommandUDP, 0x00})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// HalfClose works only on version2
|
// HalfClose works only on version2
|
||||||
func HalfClose(conn net.Conn) error {
|
func HalfClose(conn net.Conn) error {
|
||||||
if _, err := conn.Write(endSignal); err != nil {
|
if _, err := conn.Write(endSignal); err != nil {
|
||||||
|
@ -114,10 +132,147 @@ func HalfClose(conn net.Conn) error {
|
||||||
|
|
||||||
func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
|
func StreamConn(conn net.Conn, psk []byte, version int) *Snell {
|
||||||
var cipher shadowaead.Cipher
|
var cipher shadowaead.Cipher
|
||||||
if version == Version2 {
|
if version != Version1 {
|
||||||
cipher = NewAES128GCM(psk)
|
cipher = NewAES128GCM(psk)
|
||||||
} else {
|
} else {
|
||||||
cipher = NewChacha20Poly1305(psk)
|
cipher = NewChacha20Poly1305(psk)
|
||||||
}
|
}
|
||||||
return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
|
return &Snell{Conn: shadowaead.NewConn(conn, cipher)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PacketConn(conn net.Conn) net.PacketConn {
|
||||||
|
return &packetConn{
|
||||||
|
Conn: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||||
|
buf := pool.GetBuffer()
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
|
// compose snell UDP address format (refer: icpz/snell-server-reversed)
|
||||||
|
// a brand new wheel to replace socks5 address format, well done Yachen
|
||||||
|
buf.WriteByte(CommondUDPForward)
|
||||||
|
switch socks5Addr[0] {
|
||||||
|
case socks5.AtypDomainName:
|
||||||
|
hostLen := socks5Addr[1]
|
||||||
|
buf.Write(socks5Addr[1 : 1+1+hostLen+2])
|
||||||
|
case socks5.AtypIPv4:
|
||||||
|
buf.Write([]byte{0x00, 0x04})
|
||||||
|
buf.Write(socks5Addr[1 : 1+net.IPv4len+2])
|
||||||
|
case socks5.AtypIPv6:
|
||||||
|
buf.Write([]byte{0x00, 0x06})
|
||||||
|
buf.Write(socks5Addr[1 : 1+net.IPv6len+2])
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Write(payload)
|
||||||
|
_, err := w.Write(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
|
||||||
|
if len(payload) <= maxLength {
|
||||||
|
return writePacket(w, socks5Addr, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := 0
|
||||||
|
total := len(payload)
|
||||||
|
for {
|
||||||
|
cursor := offset + maxLength
|
||||||
|
if cursor > total {
|
||||||
|
cursor = total
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := writePacket(w, socks5Addr, payload[offset:cursor])
|
||||||
|
if err != nil {
|
||||||
|
return offset + n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = cursor
|
||||||
|
if offset == total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, error) {
|
||||||
|
buf := pool.Get(pool.UDPBufferSize)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
|
||||||
|
n, err := r.Read(buf)
|
||||||
|
headLen := 1
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if n < headLen {
|
||||||
|
return nil, 0, errors.New("insufficient UDP length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse snell UDP response address format
|
||||||
|
switch buf[0] {
|
||||||
|
case 0x04:
|
||||||
|
headLen += net.IPv4len + 2
|
||||||
|
if n < headLen {
|
||||||
|
err = errors.New("insufficient UDP length")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf[0] = socks5.AtypIPv4
|
||||||
|
case 0x06:
|
||||||
|
headLen += net.IPv6len + 2
|
||||||
|
if n < headLen {
|
||||||
|
err = errors.New("insufficient UDP length")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf[0] = socks5.AtypIPv6
|
||||||
|
default:
|
||||||
|
err = errors.New("ip version invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := socks5.SplitAddr(buf[0:])
|
||||||
|
if addr == nil {
|
||||||
|
return nil, 0, errors.New("remote address invalid")
|
||||||
|
}
|
||||||
|
uAddr := addr.UDPAddr()
|
||||||
|
|
||||||
|
length := len(payload)
|
||||||
|
if n-headLen < length {
|
||||||
|
length = n - headLen
|
||||||
|
}
|
||||||
|
copy(payload[:], buf[headLen:headLen+length])
|
||||||
|
|
||||||
|
return uAddr, length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConn struct {
|
||||||
|
net.Conn
|
||||||
|
rMux sync.Mutex
|
||||||
|
wMux sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
pc.wMux.Lock()
|
||||||
|
defer pc.wMux.Unlock()
|
||||||
|
|
||||||
|
return WritePacket(pc, socks5.ParseAddr(addr.String()), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pc *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
pc.rMux.Lock()
|
||||||
|
defer pc.rMux.Unlock()
|
||||||
|
|
||||||
|
addr, n, err := ReadPacket(pc.Conn, b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, addr, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package trojan
|
package trojan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@ -12,6 +13,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
)
|
)
|
||||||
|
@ -68,7 +70,11 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
|
||||||
|
// fix tls handshake not timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package vmess
|
package vmess
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
|
@ -19,6 +22,10 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
err := tlsConn.Handshake()
|
|
||||||
|
// fix tls handshake not timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
err := tlsConn.HandshakeContext(ctx)
|
||||||
return tlsConn, err
|
return tlsConn, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package tunnel
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
R "github.com/Dreamacro/clash/rule/common"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -15,7 +16,6 @@ import (
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
icontext "github.com/Dreamacro/clash/context"
|
icontext "github.com/Dreamacro/clash/context"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rule"
|
|
||||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue