mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2024-11-15 13:41:23 +08:00
Merge branch 'Dev' into Meta
# Conflicts: # config/config.go
This commit is contained in:
commit
2f6f9ebc2e
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/config/cache*
|
||||
/output
|
||||
/.vscode
|
14
Makefile
14
Makefile
@ -1,6 +1,10 @@
|
||||
NAME=Clash.Meta
|
||||
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)
|
||||
AUTOIPTABLES=Enable
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
@ -141,4 +149,4 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||
|
||||
releases: $(gz_releases) $(zip_releases)
|
||||
clean:
|
||||
rm $(BINDIR)/*
|
||||
rm $(BINDIR)/*
|
||||
|
@ -20,3 +20,26 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
|
||||
|
||||
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"`
|
||||
Port int `proxy:"port"`
|
||||
Psk string `proxy:"psk"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Version int `proxy:"version,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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
psk := []byte(option.Psk)
|
||||
@ -106,7 +125,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
if option.Version == 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -115,6 +140,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Snell,
|
||||
udp: option.UDP,
|
||||
iface: option.Interface,
|
||||
},
|
||||
psk: psk,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
@ -21,6 +22,7 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter
|
||||
proxies = append(proxies, provider.Proxies()...)
|
||||
}
|
||||
}
|
||||
|
||||
var filterReg *regexp.Regexp = nil
|
||||
matchedProxies := []C.Proxy{}
|
||||
if len(filter) > 0 {
|
||||
@ -30,10 +32,18 @@ func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter
|
||||
matchedProxies = append(matchedProxies, p)
|
||||
}
|
||||
}
|
||||
//if no proxy matched, means bad filter, return all proxies
|
||||
|
||||
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.failedTimes.Store(1)
|
||||
} 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.failedTime.Store(-1)
|
||||
} else {
|
||||
f.failedTimes.Inc()
|
||||
failedCount := f.failedTimes.Load()
|
||||
failedCount := f.failedTimes.Inc()
|
||||
log.Warnln("%s failed count: %d", f.Name(), failedCount)
|
||||
if failedCount > 5 {
|
||||
log.Debugln("%s failed multiple times.", f.Name())
|
||||
if failedCount >= 5 {
|
||||
log.Warnln("because %s failed multiple times, active health check", f.Name())
|
||||
for _, proxyProvider := range f.providers {
|
||||
go proxyProvider.HealthCheck()
|
||||
}
|
||||
|
||||
f.failedTimes.Store(-1)
|
||||
f.failedTime.Store(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,10 +97,11 @@ func (f *Fallback) SupportUDP() bool {
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
all := make([]string, 0)
|
||||
for _, proxy := range f.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"type": f.Type().String(),
|
||||
"now": f.Now(),
|
||||
|
@ -150,10 +150,12 @@ func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
all := make([]string, 0)
|
||||
|
||||
for _, proxy := range lb.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"type": lb.Type().String(),
|
||||
"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) {
|
||||
var proxies []C.Proxy
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -69,10 +69,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
all := make([]string, 0)
|
||||
|
||||
for _, proxy := range r.rawProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"type": r.Type().String(),
|
||||
"all": all,
|
||||
|
@ -50,7 +50,8 @@ func (s *Selector) SupportUDP() bool {
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
all := make([]string, 0)
|
||||
|
||||
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
|
||||
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 {
|
||||
selected := providers[0].Proxies()[0].Name()
|
||||
return &Selector{
|
||||
Base: outbound.NewBase(outbound.BaseOption{
|
||||
Name: option.Name,
|
||||
@ -109,7 +109,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
|
||||
}),
|
||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||
providers: providers,
|
||||
selected: selected,
|
||||
selected: "COMPATIBLE",
|
||||
disableUDP: option.DisableUDP,
|
||||
filter: option.Filter,
|
||||
}
|
||||
|
@ -125,10 +125,12 @@ func (u *URLTest) SupportUDP() bool {
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
all := make([]string, 0)
|
||||
|
||||
for _, proxy := range u.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"type": u.Type().String(),
|
||||
"now": u.Now(),
|
||||
@ -147,14 +149,16 @@ func (u *URLTest) onDialFailed() {
|
||||
u.failedTimes.Store(-1)
|
||||
u.failedTime.Store(-1)
|
||||
} else {
|
||||
u.failedTimes.Inc()
|
||||
failedCount := u.failedTimes.Load()
|
||||
failedCount := u.failedTimes.Inc()
|
||||
log.Warnln("%s failed count: %d", u.Name(), failedCount)
|
||||
if failedCount > 5 {
|
||||
log.Debugln("%s failed multiple times.", u.Name())
|
||||
if failedCount >= 5 {
|
||||
log.Warnln("because %s failed multiple times, active health check", u.Name())
|
||||
for _, proxyProvider := range u.providers {
|
||||
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)
|
||||
if bytes.Equal(f.hash[:], hash[:]) {
|
||||
f.updatedAt = &now
|
||||
os.Chtimes(f.vehicle.Path(), now, now)
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,10 @@ func (pp *proxySetProvider) Initial() error {
|
||||
}
|
||||
|
||||
pp.onUpdate(elm)
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.process()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &proxySetProvider{
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
@ -190,6 +190,10 @@ func (cp *compatibleProvider) Update() error {
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Initial() error {
|
||||
if cp.healthCheck.auto() {
|
||||
go cp.healthCheck.process()
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &compatibleProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
|
@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -10,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
netHttp "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
@ -77,7 +77,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -97,3 +97,11 @@ func TestTrie_Boundary(t *testing.T) {
|
||||
assert.NotNil(t, tree.Insert("..dev", localIP))
|
||||
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
|
||||
type Node struct {
|
||||
Data interface{}
|
||||
children map[string]*Node
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func (n *Node) getChild(s string) *Node {
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
RP "github.com/Dreamacro/clash/rule/provider"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -24,7 +26,6 @@ import (
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
@ -209,7 +210,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Enable: false,
|
||||
Stack: "gvisor",
|
||||
DnsHijack: []string{"198.18.0.2:53"},
|
||||
AutoRoute: true,
|
||||
AutoRoute: false,
|
||||
},
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
@ -228,8 +229,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
"1.0.0.1",
|
||||
},
|
||||
NameServer: []string{
|
||||
"https://8.8.8.8/dns-query",
|
||||
"https://1.0.0.1/dns-query",
|
||||
"223.5.5.5",
|
||||
"119.29.29",
|
||||
},
|
||||
FakeIPFilter: []string{
|
||||
"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["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||
proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible())
|
||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||
|
||||
// parse proxy
|
||||
@ -396,13 +398,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
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
|
||||
for idx, mapping := range groupsConfig {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
for _, v := range proxyList {
|
||||
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
|
||||
for name, mapping := range cfg.RuleProvider {
|
||||
rp, err := R.ParseRuleProvider(name, mapping)
|
||||
rp, err := RP.ParseRuleProvider(name, mapping)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ruleProviders[name] = &rp
|
||||
R.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)
|
||||
}
|
||||
RP.SetRuleProvider(rp)
|
||||
}
|
||||
|
||||
var rules []C.Rule
|
||||
@ -536,24 +512,29 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
continue
|
||||
}
|
||||
|
||||
switch l := len(rule); {
|
||||
case l == 2:
|
||||
target = rule[1]
|
||||
case l == 3:
|
||||
if ruleName == "MATCH" {
|
||||
payload = ""
|
||||
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); {
|
||||
case l == 2:
|
||||
target = rule[1]
|
||||
params = rule[2:]
|
||||
break
|
||||
case l == 3:
|
||||
if ruleName == "MATCH" {
|
||||
payload = ""
|
||||
target = rule[1]
|
||||
params = rule[2:]
|
||||
break
|
||||
}
|
||||
payload = rule[1]
|
||||
target = rule[2]
|
||||
case l >= 4:
|
||||
payload = rule[1]
|
||||
target = rule[2]
|
||||
params = rule[3:]
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
}
|
||||
payload = rule[1]
|
||||
target = rule[2]
|
||||
case l >= 4:
|
||||
payload = rule[1]
|
||||
target = rule[2]
|
||||
params = rule[3:]
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||
}
|
||||
|
||||
if _, ok := proxies[target]; mode != T.Script && !ok {
|
||||
@ -562,6 +543,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
|
||||
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)
|
||||
if parseErr != nil {
|
||||
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) {
|
||||
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 {
|
||||
found := false
|
||||
|
@ -50,23 +50,6 @@ func initMMDB() error {
|
||||
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) {
|
||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
|
||||
if err != nil {
|
||||
@ -84,22 +67,9 @@ func downloadGeoSite(path string) (err error) {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
// initial GeoSite
|
||||
if err := initGeoSite(); err != nil {
|
||||
return fmt.Errorf("can't initial GeoSite: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
const (
|
||||
Direct AdapterType = iota
|
||||
Reject
|
||||
|
||||
Compatible
|
||||
Shadowsocks
|
||||
ShadowsocksR
|
||||
Snell
|
||||
@ -33,6 +33,7 @@ const (
|
||||
const (
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
DefaultUDPTimeout = DefaultTCPTimeout
|
||||
DefaultTLSTimeout = DefaultTCPTimeout
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
@ -128,7 +129,8 @@ func (at AdapterType) String() string {
|
||||
return "Direct"
|
||||
case Reject:
|
||||
return "Reject"
|
||||
|
||||
case Compatible:
|
||||
return "Compatible"
|
||||
case Shadowsocks:
|
||||
return "Shadowsocks"
|
||||
case ShadowsocksR:
|
||||
|
@ -24,6 +24,7 @@ const (
|
||||
REDIR
|
||||
TPROXY
|
||||
TUN
|
||||
INNER
|
||||
)
|
||||
|
||||
type NetWork int
|
||||
@ -59,6 +60,8 @@ func (t Type) String() string {
|
||||
return "TProxy"
|
||||
case TUN:
|
||||
return "Tun"
|
||||
case INNER:
|
||||
return "Inner"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@ -94,6 +97,10 @@ func (m *Metadata) SourceDetail() string {
|
||||
if m.Process != "" {
|
||||
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
|
||||
} else {
|
||||
if m.Type == INNER {
|
||||
return fmt.Sprintf("[Clash]")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s", m.SourceAddress())
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,12 @@ const (
|
||||
Process
|
||||
Script
|
||||
RuleSet
|
||||
Network
|
||||
Combination
|
||||
MATCH
|
||||
AND
|
||||
OR
|
||||
NOT
|
||||
)
|
||||
|
||||
type RuleType int
|
||||
@ -47,6 +52,14 @@ func (rt RuleType) String() string {
|
||||
return "Match"
|
||||
case RuleSet:
|
||||
return "RuleSet"
|
||||
case Network:
|
||||
return "Network"
|
||||
case AND:
|
||||
return "AND"
|
||||
case OR:
|
||||
return "OR"
|
||||
case NOT:
|
||||
return "NOT"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
@ -5,4 +5,5 @@ var (
|
||||
Version = "1.9.0"
|
||||
BuildTime = "unknown time"
|
||||
AutoIptables string
|
||||
ClashName = "Clash.Meta"
|
||||
)
|
||||
|
@ -60,6 +60,10 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
||||
address = ""
|
||||
}
|
||||
|
||||
if addr == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
|
@ -2,7 +2,6 @@ package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -10,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
@ -74,15 +75,17 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
defer mux.Unlock()
|
||||
|
||||
updateUsers(cfg.Users)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateProxies(cfg.Proxies, cfg.Providers)
|
||||
updateRules(cfg.Rules, cfg.RuleProviders)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateProfile(cfg)
|
||||
updateIPTables(cfg.DNS, cfg.General, cfg.Tun)
|
||||
updateDNS(cfg.DNS, cfg.Tun)
|
||||
updateGeneral(cfg.General, cfg.Tun, force)
|
||||
updateTun(cfg.Tun)
|
||||
updateExperimental(cfg)
|
||||
loadProvider(cfg.RuleProviders, cfg.Providers)
|
||||
updateProfile(cfg)
|
||||
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
@ -175,6 +178,38 @@ func updateRules(rules []C.Rule, ruleProviders map[string]*provider.RuleProvider
|
||||
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) {
|
||||
tunnel.SetMode(general.Mode)
|
||||
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("/providers/proxies", proxyProviderRouter())
|
||||
r.Mount("/providers/rules", ruleProviderRouter())
|
||||
r.Mount("/script", scriptRouter())
|
||||
})
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"net"
|
||||
"runtime"
|
||||
"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())
|
||||
}
|
||||
}()
|
||||
inner.New(tcpIn)
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
|
@ -1,13 +1,5 @@
|
||||
package dev
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// TunDevice is cross-platform tun interface
|
||||
type TunDevice interface {
|
||||
Name() string
|
||||
@ -18,54 +10,3 @@ type TunDevice interface {
|
||||
Read(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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
@ -176,7 +178,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
SetLinuxAutoRoute()
|
||||
setAutoRoute(tunAddress)
|
||||
}
|
||||
|
||||
return tun, nil
|
||||
@ -280,7 +282,7 @@ func (t *tunDarwin) IsClose() bool {
|
||||
func (t *tunDarwin) Close() error {
|
||||
t.stopOnce.Do(func() {
|
||||
if t.autoRoute {
|
||||
RemoveLinuxAutoRoute()
|
||||
resetAutoRoute(t.tunAddress)
|
||||
}
|
||||
t.closed = true
|
||||
t.tunFile.Close()
|
||||
@ -480,15 +482,60 @@ func (t *tunDarwin) attachLinkLocal() error {
|
||||
|
||||
// GetAutoDetectInterface get ethernet interface
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
cmd := exec.Command("bash", "-c", "netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
cmd := exec.Command("route", "-n", "get", "default")
|
||||
if result, err := cmd.Output(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
resultString := string(result)
|
||||
reg, err := regexp.Compile("(interface:)(.*)")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
matchResult := reg.FindStringSubmatch(resultString)
|
||||
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())
|
||||
}
|
||||
if out.Len() == 0 {
|
||||
return "", errors.New("interface not found by default route")
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -38,7 +39,7 @@ type tunLinux struct {
|
||||
|
||||
// OpenTunDevice return a TunDevice according a URL
|
||||
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)
|
||||
|
||||
t := &tunLinux{
|
||||
@ -62,8 +63,9 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
SetLinuxAutoRoute()
|
||||
addRoute(tunAddress)
|
||||
}
|
||||
|
||||
return dev, nil
|
||||
case "fd":
|
||||
fd, err := strconv.ParseInt(deviceURL.Host, 10, 32)
|
||||
@ -76,7 +78,7 @@ func OpenTunDevice(tunAddress string, autoRoute bool) (TunDevice, error) {
|
||||
return nil, err
|
||||
}
|
||||
if autoRoute {
|
||||
SetLinuxAutoRoute()
|
||||
log.Warnln("linux unsupported automatic route")
|
||||
}
|
||||
return dev, nil
|
||||
}
|
||||
@ -105,9 +107,6 @@ func (t *tunLinux) IsClose() bool {
|
||||
|
||||
func (t *tunLinux) Close() error {
|
||||
t.stopOnce.Do(func() {
|
||||
if t.autoRoute {
|
||||
RemoveLinuxAutoRoute()
|
||||
}
|
||||
t.closed = true
|
||||
t.tunFile.Close()
|
||||
})
|
||||
@ -330,3 +329,21 @@ func GetAutoDetectInterface() (string, error) {
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +34,10 @@ import (
|
||||
const nicID tcpip.NICID = 1
|
||||
|
||||
type gvisorAdapter struct {
|
||||
device dev.TunDevice
|
||||
ipstack *stack.Stack
|
||||
dnsServers []*DNSServer
|
||||
udpIn chan<- *inbound.PacketAdapter
|
||||
device dev.TunDevice
|
||||
ipstack *stack.Stack
|
||||
dnsServer *DNSServer
|
||||
udpIn chan<- *inbound.PacketAdapter
|
||||
|
||||
stackName string
|
||||
autoRoute bool
|
||||
@ -47,7 +47,7 @@ type gvisorAdapter struct {
|
||||
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) {
|
||||
ipstack := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||
@ -132,7 +132,7 @@ func (t *gvisorAdapter) AutoRoute() bool {
|
||||
|
||||
// Close close the TunAdapter
|
||||
func (t *gvisorAdapter) Close() {
|
||||
t.StopAllDNSServer()
|
||||
t.StopDNSServer()
|
||||
if t.ipstack != nil {
|
||||
t.ipstack.Close()
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ package gvisor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"net"
|
||||
|
||||
Common "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
D "github.com/miekg/dns"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
@ -23,15 +24,33 @@ var (
|
||||
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
|
||||
type DNSServer struct {
|
||||
*dns.Server
|
||||
resolver *dns.Resolver
|
||||
|
||||
stack *stack.Stack
|
||||
tcpListener net.Listener
|
||||
udpEndpoint *dnsEndpoint
|
||||
udpEndpointID *stack.TransportEndpointID
|
||||
dnsServers []*dns.Server
|
||||
tcpListeners []net.Listener
|
||||
resolver *dns.Resolver
|
||||
stack *stack.Stack
|
||||
udpEndpoints []*dnsEndpoint
|
||||
udpEndpointIDs []*stack.TransportEndpointID
|
||||
tcpip.NICID
|
||||
}
|
||||
|
||||
@ -66,6 +85,7 @@ func (e *dnsEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.Pack
|
||||
var msg D.Msg
|
||||
msg.Unpack(pkt.Data().AsRange().ToOwnedView())
|
||||
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)
|
||||
}
|
||||
|
||||
@ -129,167 +149,276 @@ func (w *dnsResponseWriter) Close() error {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var v4 bool
|
||||
func CreateDNSServer(s *stack.Stack, resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijack []net.Addr, nicID tcpip.NICID) (*DNSServer, error) {
|
||||
var err error
|
||||
|
||||
address := tcpip.FullAddress{NIC: nicID, Port: uint16(port)}
|
||||
var protocol tcpip.NetworkProtocolNumber
|
||||
if ip.To4() != nil {
|
||||
v4 = true
|
||||
address.Addr = tcpip.Address(ip.To4())
|
||||
protocol = ipv4.ProtocolNumber
|
||||
|
||||
} else {
|
||||
v4 = false
|
||||
address.Addr = tcpip.Address(ip.To16())
|
||||
protocol = ipv6.ProtocolNumber
|
||||
}
|
||||
protocolAddr := tcpip.ProtocolAddress{
|
||||
Protocol: protocol,
|
||||
AddressWithPrefix: address.Addr.WithPrefix(),
|
||||
}
|
||||
// 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 {
|
||||
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
|
||||
}
|
||||
|
||||
if address.Addr == ipv4Zero || address.Addr == ipv6Zero {
|
||||
address.Addr = ""
|
||||
}
|
||||
|
||||
handler := dns.NewHandler(resolver, mapper)
|
||||
serverIn := &dns.Server{}
|
||||
serverIn.SetHandler(handler)
|
||||
|
||||
// UDP DNS
|
||||
id := &stack.TransportEndpointID{
|
||||
LocalAddress: address.Addr,
|
||||
LocalPort: uint16(port),
|
||||
RemotePort: 0,
|
||||
RemoteAddress: "",
|
||||
}
|
||||
|
||||
// TransportEndpoint for DNS
|
||||
endpoint := &dnsEndpoint{
|
||||
stack: s,
|
||||
uniqueID: s.UniqueID(),
|
||||
server: serverIn,
|
||||
}
|
||||
|
||||
if tcpiperr := s.RegisterTransportEndpoint(
|
||||
[]tcpip.NetworkProtocolNumber{
|
||||
ipv4.ProtocolNumber,
|
||||
ipv6.ProtocolNumber,
|
||||
},
|
||||
udp.ProtocolNumber,
|
||||
*id,
|
||||
endpoint,
|
||||
ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect.
|
||||
nicID); tcpiperr != nil {
|
||||
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
|
||||
}
|
||||
|
||||
// TCP DNS
|
||||
var tcpListener net.Listener
|
||||
if v4 {
|
||||
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
|
||||
} else {
|
||||
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not listen on tun: %v", err)
|
||||
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{
|
||||
Server: serverIn,
|
||||
resolver: resolver,
|
||||
stack: s,
|
||||
tcpListener: tcpListener,
|
||||
udpEndpoint: endpoint,
|
||||
udpEndpointID: id,
|
||||
NICID: nicID,
|
||||
resolver: resolver,
|
||||
stack: s,
|
||||
udpEndpoints: endpoints,
|
||||
udpEndpointIDs: ids,
|
||||
NICID: nicID,
|
||||
tcpListeners: tcpListeners,
|
||||
}
|
||||
server.SetHandler(handler)
|
||||
server.Server.Server = &D.Server{Listener: tcpListener, Handler: server}
|
||||
|
||||
go func() {
|
||||
server.ActivateAndServe()
|
||||
}()
|
||||
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)}
|
||||
var protocol tcpip.NetworkProtocolNumber
|
||||
if ip.To4() != nil {
|
||||
address.Addr = tcpip.Address(ip.To4())
|
||||
protocol = ipv4.ProtocolNumber
|
||||
|
||||
} else {
|
||||
address.Addr = tcpip.Address(ip.To16())
|
||||
protocol = ipv6.ProtocolNumber
|
||||
}
|
||||
|
||||
protocolAddr := tcpip.ProtocolAddress{
|
||||
Protocol: protocol,
|
||||
AddressWithPrefix: address.Addr.WithPrefix(),
|
||||
}
|
||||
|
||||
// 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 {
|
||||
log.Errorln("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
|
||||
}
|
||||
|
||||
if address.Addr == ipv4Zero || address.Addr == ipv6Zero {
|
||||
address.Addr = ""
|
||||
}
|
||||
|
||||
// UDP DNS
|
||||
id := &stack.TransportEndpointID{
|
||||
LocalAddress: address.Addr,
|
||||
LocalPort: uint16(port),
|
||||
RemotePort: 0,
|
||||
RemoteAddress: "",
|
||||
}
|
||||
|
||||
// TransportEndpoint for DNS
|
||||
endpoint := &dnsEndpoint{
|
||||
stack: s,
|
||||
uniqueID: s.UniqueID(),
|
||||
server: serverIn,
|
||||
}
|
||||
|
||||
if tcpiperr := s.RegisterTransportEndpoint(
|
||||
[]tcpip.NetworkProtocolNumber{
|
||||
ipv4.ProtocolNumber,
|
||||
ipv6.ProtocolNumber,
|
||||
},
|
||||
udp.ProtocolNumber,
|
||||
*id,
|
||||
endpoint,
|
||||
ports.Flags{LoadBalanced: true}, // it's actually the SO_REUSEPORT. Not sure it take effect.
|
||||
nicID); tcpiperr != nil {
|
||||
log.Errorln("Unable to start UDP DNS on tun: %v", tcpiperr.String())
|
||||
}
|
||||
|
||||
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 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 {
|
||||
tcpListener, err = gonet.ListenTCP(s, address, ipv4.ProtocolNumber)
|
||||
} else {
|
||||
tcpListener, err = gonet.ListenTCP(s, address, ipv6.ProtocolNumber)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
//
|
||||
//for _, listener := range tcpListeners {
|
||||
// server := &D.Server{Listener: listener, Handler: serverIn}
|
||||
//
|
||||
// dnsServers = append(dnsServers, &dnsServer)
|
||||
// go dnsServer.ActivateAndServe()
|
||||
//}
|
||||
|
||||
return tcpListeners, dnsServers
|
||||
}
|
||||
|
||||
// Stop stop the DNS Server on tun
|
||||
func (s *DNSServer) Stop() {
|
||||
// shutdown TCP DNS Server
|
||||
s.Server.Shutdown()
|
||||
// remove TCP endpoint from stack
|
||||
if s.Listener != nil {
|
||||
s.Listener.Close()
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(s.udpEndpointIDs); i++ {
|
||||
ep := s.udpEndpoints[i]
|
||||
id := s.udpEndpointIDs[i]
|
||||
// remove udp endpoint from stack
|
||||
s.stack.UnregisterTransportEndpoint(
|
||||
[]tcpip.NetworkProtocolNumber{
|
||||
ipv4.ProtocolNumber,
|
||||
ipv6.ProtocolNumber,
|
||||
},
|
||||
udp.ProtocolNumber,
|
||||
*id,
|
||||
ep,
|
||||
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
|
||||
s.NICID)
|
||||
}
|
||||
|
||||
for _, server := range s.dnsServers {
|
||||
server.Shutdown()
|
||||
}
|
||||
|
||||
for _, listener := range s.tcpListeners {
|
||||
listener.Close()
|
||||
}
|
||||
// remove udp endpoint from stack
|
||||
s.stack.UnregisterTransportEndpoint(
|
||||
[]tcpip.NetworkProtocolNumber{
|
||||
ipv4.ProtocolNumber,
|
||||
ipv6.ProtocolNumber,
|
||||
},
|
||||
udp.ProtocolNumber,
|
||||
*s.udpEndpointID,
|
||||
s.udpEndpoint,
|
||||
ports.Flags{LoadBalanced: true}, // should match the RegisterTransportEndpoint
|
||||
s.NICID)
|
||||
}
|
||||
|
||||
// DnsHijack return the listening address of DNS Server
|
||||
func (t *gvisorAdapter) DnsHijack() []string {
|
||||
results := make([]string, len(t.dnsServers))
|
||||
for i, dnsServer := range t.dnsServers {
|
||||
id := dnsServer.udpEndpointID
|
||||
results[i] = fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort)
|
||||
dnsHijackArr := make([]string, len(t.dnsServer.udpEndpoints))
|
||||
for _, id := range t.dnsServer.udpEndpointIDs {
|
||||
dnsHijackArr = append(dnsHijackArr, fmt.Sprintf("%s:%d", id.LocalAddress.String(), id.LocalPort))
|
||||
}
|
||||
|
||||
return results
|
||||
return dnsHijackArr
|
||||
}
|
||||
|
||||
func (t *gvisorAdapter) StopAllDNSServer() {
|
||||
for _, dnsServer := range t.dnsServers {
|
||||
dnsServer.Stop()
|
||||
}
|
||||
func (t *gvisorAdapter) StopDNSServer() {
|
||||
t.dnsServer.Stop()
|
||||
log.Debugln("tun DNS server stoped")
|
||||
t.dnsServers = nil
|
||||
t.dnsServer = nil
|
||||
}
|
||||
|
||||
// ReCreateDNSServer recreate the DNS Server on tun
|
||||
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, addrs []string) error {
|
||||
t.StopAllDNSServer()
|
||||
func (t *gvisorAdapter) ReCreateDNSServer(resolver *dns.Resolver, mapper *dns.ResolverEnhancer, dnsHijackArr []string) error {
|
||||
t.StopDNSServer()
|
||||
|
||||
if resolver == nil {
|
||||
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")
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
var err error
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
if port == "0" || port == "" || err != nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
var addrs []net.Addr
|
||||
for _, addr := range dnsHijackArr {
|
||||
var (
|
||||
addrType string
|
||||
hostPort string
|
||||
)
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
addrType, hostPort, err = Common.SplitNetworkType(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := CreateDNSServer(t.ipstack, resolver, mapper, udpAddr.IP, udpAddr.Port, nicID)
|
||||
if err != nil {
|
||||
return err
|
||||
var (
|
||||
host, port string
|
||||
hasPort bool
|
||||
)
|
||||
|
||||
host, port, hasPort, err = Common.SplitHostPort(hostPort)
|
||||
if !hasPort {
|
||||
port = "53"
|
||||
}
|
||||
t.dnsServers = append(t.dnsServers, server)
|
||||
log.Infoln("Tun DNS server listening at: %s, fake ip enabled: %v", addr, mapper.FakeIPEnabled())
|
||||
|
||||
switch addrType {
|
||||
case "udp", "":
|
||||
{
|
||||
var udpDNS *net.UDPAddr
|
||||
udpDNS, err = net.ResolveUDPAddr("udp", net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -22,7 +22,7 @@ func HasNoResolve(params []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func findNetwork(params []string) C.NetWork {
|
||||
func FindNetwork(params []string) C.NetWork {
|
||||
for _, p := range params {
|
||||
if p == "tcp" {
|
||||
return C.TCP
|
||||
@ -33,7 +33,7 @@ func findNetwork(params []string) C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
func findSourceIPs(params []string) []*net.IPNet {
|
||||
func FindSourceIPs(params []string) []*net.IPNet {
|
||||
var ips []*net.IPNet
|
||||
for _, p := range params {
|
||||
if p == noResolve || len(p) < 7 {
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"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 (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -35,26 +35,28 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort)
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
processCache.Set(key, "")
|
||||
return false
|
||||
if strings.TrimSpace(metadata.Process) == "" {
|
||||
cached, hit := processCache.Get(key)
|
||||
if !hit {
|
||||
srcPort, err := strconv.Atoi(metadata.SrcPort)
|
||||
if err != nil {
|
||||
processCache.Set(key, "")
|
||||
return false
|
||||
}
|
||||
|
||||
name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
|
||||
if err != nil {
|
||||
log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, name)
|
||||
|
||||
cached = name
|
||||
}
|
||||
|
||||
name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort)
|
||||
if err != nil {
|
||||
log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error())
|
||||
}
|
||||
|
||||
processCache.Set(key, name)
|
||||
|
||||
cached = name
|
||||
metadata.Process = cached.(string)
|
||||
}
|
||||
|
||||
metadata.Process = cached.(string)
|
||||
|
||||
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 (
|
||||
"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"
|
||||
"time"
|
||||
RC "github.com/Dreamacro/clash/rule/common"
|
||||
"github.com/Dreamacro/clash/rule/logic"
|
||||
RP "github.com/Dreamacro/clash/rule/provider"
|
||||
)
|
||||
|
||||
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{
|
||||
Network: findNetwork(params),
|
||||
SourceIPs: findSourceIPs(params),
|
||||
Network: RC.FindNetwork(params),
|
||||
SourceIPs: RC.FindSourceIPs(params),
|
||||
}
|
||||
|
||||
switch tp {
|
||||
case "DOMAIN":
|
||||
parsed = NewDomain(payload, target, ruleExtra)
|
||||
parsed = RC.NewDomain(payload, target, ruleExtra)
|
||||
case "DOMAIN-SUFFIX":
|
||||
parsed = NewDomainSuffix(payload, target, ruleExtra)
|
||||
parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
|
||||
case "DOMAIN-KEYWORD":
|
||||
parsed = NewDomainKeyword(payload, target, ruleExtra)
|
||||
parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
|
||||
case "GEOSITE":
|
||||
parsed, parseErr = NewGEOSITE(payload, target, ruleExtra)
|
||||
parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
|
||||
case "GEOIP":
|
||||
noResolve := HasNoResolve(params)
|
||||
parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra)
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra)
|
||||
case "IP-CIDR", "IP-CIDR6":
|
||||
noResolve := HasNoResolve(params)
|
||||
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve))
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
|
||||
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":
|
||||
parsed, parseErr = NewPort(payload, target, true, ruleExtra)
|
||||
parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
|
||||
case "DST-PORT":
|
||||
parsed, parseErr = NewPort(payload, target, false, ruleExtra)
|
||||
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
|
||||
case "PROCESS-NAME":
|
||||
parsed, parseErr = NewProcess(payload, target, ruleExtra)
|
||||
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
|
||||
case "MATCH":
|
||||
parsed = NewMatch(target, ruleExtra)
|
||||
parsed = RC.NewMatch(target, ruleExtra)
|
||||
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:
|
||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||
}
|
||||
|
||||
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 (
|
||||
"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 (
|
||||
"encoding/json"
|
||||
@ -75,6 +75,10 @@ func (rp *ruleSetProvider) Behavior() P.RuleType {
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
|
||||
if rp.count == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch rp.behavior {
|
||||
case P.Domain:
|
||||
return rp.DomainRules.Search(metadata.Host) != nil
|
||||
@ -201,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) {
|
||||
return nil, errors.New("error rule type")
|
||||
}
|
||||
|
||||
r, err := ParseRule(ruleType, rule, "", params)
|
||||
r, err := parseRule(ruleType, rule, "", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -32,7 +32,7 @@ const (
|
||||
ImageVmess = "v2fly/v2fly-core:latest"
|
||||
ImageTrojan = "trojangfw/trojan:latest"
|
||||
ImageTrojanGo = "p4gefau1t/trojan-go:latest"
|
||||
ImageSnell = "icpz/snell-server:latest"
|
||||
ImageSnell = "ghcr.io/icpz/snell-server:latest"
|
||||
ImageXray = "teddysun/xray:latest"
|
||||
)
|
||||
|
||||
|
@ -120,6 +120,42 @@ func TestClash_Snell(t *testing.T) {
|
||||
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) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageSnell,
|
||||
|
@ -5,6 +5,7 @@ package gun
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
@ -17,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/net/http2"
|
||||
@ -173,7 +175,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport {
|
||||
}
|
||||
|
||||
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()
|
||||
return nil, err
|
||||
}
|
||||
|
@ -6,8 +6,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
||||
)
|
||||
@ -15,13 +17,19 @@ import (
|
||||
const (
|
||||
Version1 = 1
|
||||
Version2 = 2
|
||||
Version3 = 3
|
||||
DefaultSnellVersion = Version1
|
||||
|
||||
// max packet length
|
||||
maxLength = 0x3FFF
|
||||
)
|
||||
|
||||
const (
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
CommandConnectV2 byte = 5
|
||||
CommandPing byte = 0
|
||||
CommandConnect byte = 1
|
||||
CommandConnectV2 byte = 5
|
||||
CommandUDP byte = 6
|
||||
CommondUDPForward byte = 1
|
||||
|
||||
CommandTunnel byte = 0
|
||||
CommandPong byte = 1
|
||||
@ -100,6 +108,16 @@ func WriteHeader(conn net.Conn, host string, port uint, version int) error {
|
||||
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
|
||||
func HalfClose(conn net.Conn) error {
|
||||
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 {
|
||||
var cipher shadowaead.Cipher
|
||||
if version == Version2 {
|
||||
if version != Version1 {
|
||||
cipher = NewAES128GCM(psk)
|
||||
} else {
|
||||
cipher = NewChacha20Poly1305(psk)
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"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)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
package vmess
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type TLSConfig struct {
|
||||
@ -19,6 +22,10 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package tunnel
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
R "github.com/Dreamacro/clash/rule/common"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
@ -15,7 +16,6 @@ import (
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
icontext "github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
R "github.com/Dreamacro/clash/rule"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user