Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
7d04904109 | |||
a5acd3aa97 | |||
eea9a12560 | |||
0a4570b55c | |||
e995003c27 | |||
9edee2435c | |||
5d337b75f9 | |||
2188fad9c1 | |||
edc73f3f33 | |||
ade424cbb4 | |||
6e03773134 | |||
4ad0294655 | |||
43d3a0c8ea | |||
c045a4f2a7 | |||
20611eb8dc | |||
cb517cb529 | |||
50bb620aa1 | |||
c745ea63b2 |
43
Makefile
43
Makefile
@ -8,7 +8,7 @@ VERSION=beta-$(shell git rev-parse --short HEAD)
|
|||||||
else ifeq ($(BRANCH),)
|
else ifeq ($(BRANCH),)
|
||||||
VERSION=$(shell git describe --tags)
|
VERSION=$(shell git describe --tags)
|
||||||
else
|
else
|
||||||
VERSION=unknown
|
VERSION=$(shell git rev-parse --short HEAD)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
BUILDTIME=$(shell date -u)
|
BUILDTIME=$(shell date -u)
|
||||||
@ -17,13 +17,10 @@ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clas
|
|||||||
-w -s -buildid='
|
-w -s -buildid='
|
||||||
|
|
||||||
PLATFORM_LIST = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64v1 \
|
darwin-amd64 \
|
||||||
darwin-amd64v2 \
|
|
||||||
darwin-amd64v3 \
|
|
||||||
darwin-arm64 \
|
darwin-arm64 \
|
||||||
linux-amd64v1 \
|
linux-amd64-compatible \
|
||||||
linux-amd64v2 \
|
linux-amd64 \
|
||||||
linux-amd64v3 \
|
|
||||||
linux-armv5 \
|
linux-armv5 \
|
||||||
linux-armv6 \
|
linux-armv6 \
|
||||||
linux-armv7 \
|
linux-armv7 \
|
||||||
@ -41,43 +38,36 @@ PLATFORM_LIST = \
|
|||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64v1 \
|
windows-amd64-compatible \
|
||||||
windows-amd64v2 \
|
windows-amd64 \
|
||||||
windows-amd64v3 \
|
|
||||||
windows-arm64 \
|
windows-arm64 \
|
||||||
windows-arm32v7
|
windows-arm32v7
|
||||||
|
|
||||||
all:linux-amd64v3 linux-arm64\
|
all:linux-amd64 linux-arm64\
|
||||||
darwin-amd64v3 darwin-arm64\
|
darwin-amd64 darwin-arm64\
|
||||||
windows-amd64v3 windows-arm64\
|
windows-amd64 windows-arm64\
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64v3:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64v2:
|
darwin-amd64-compatible:
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
darwin-amd64v1:
|
|
||||||
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
darwin-arm64:
|
darwin-arm64:
|
||||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-386:
|
linux-386:
|
||||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-amd64v3:
|
linux-amd64:
|
||||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-amd64v2:
|
linux-amd64-compatible:
|
||||||
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-amd64v1:
|
|
||||||
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
linux-arm64:
|
linux-arm64:
|
||||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
@ -123,15 +113,12 @@ freebsd-arm64:
|
|||||||
windows-386:
|
windows-386:
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-amd64v3:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-amd64v2:
|
windows-amd64-compatible:
|
||||||
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
windows-amd64v1:
|
|
||||||
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
windows-arm64:
|
windows-arm64:
|
||||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||||
|
|
||||||
|
@ -10,11 +10,10 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowSocks struct {
|
type ShadowSocks struct {
|
||||||
|
@ -8,12 +8,11 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
|
||||||
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
"github.com/Dreamacro/clash/transport/ssr/obfs"
|
||||||
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
"github.com/Dreamacro/clash/transport/ssr/protocol"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowstream"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowSocksR struct {
|
type ShadowSocksR struct {
|
||||||
|
@ -51,10 +51,9 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
|||||||
var proxies []C.Proxy
|
var proxies []C.Proxy
|
||||||
for _, pd := range gb.providers {
|
for _, pd := range gb.providers {
|
||||||
if touch {
|
if touch {
|
||||||
proxies = append(proxies, pd.ProxiesWithTouch()...)
|
pd.Touch()
|
||||||
} else {
|
|
||||||
proxies = append(proxies, pd.Proxies()...)
|
|
||||||
}
|
}
|
||||||
|
proxies = append(proxies, pd.Proxies()...)
|
||||||
}
|
}
|
||||||
if len(proxies) == 0 {
|
if len(proxies) == 0 {
|
||||||
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||||
@ -63,13 +62,12 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pd := range gb.providers {
|
for _, pd := range gb.providers {
|
||||||
if pd.VehicleType() == types.Compatible {
|
if touch {
|
||||||
if touch {
|
pd.Touch()
|
||||||
gb.proxies.Store(pd.Name(), pd.ProxiesWithTouch())
|
}
|
||||||
} else {
|
|
||||||
gb.proxies.Store(pd.Name(), pd.Proxies())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if pd.VehicleType() == types.Compatible {
|
||||||
|
gb.proxies.Store(pd.Name(), pd.Proxies())
|
||||||
gb.versions.Store(pd.Name(), pd.Version())
|
gb.versions.Store(pd.Name(), pd.Version())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -80,11 +78,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
|||||||
newProxies []C.Proxy
|
newProxies []C.Proxy
|
||||||
)
|
)
|
||||||
|
|
||||||
if touch {
|
proxies = pd.Proxies()
|
||||||
proxies = pd.ProxiesWithTouch()
|
|
||||||
} else {
|
|
||||||
proxies = pd.Proxies()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range proxies {
|
for _, p := range proxies {
|
||||||
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
|
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
|
||||||
|
@ -75,9 +75,11 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
|||||||
providers = append(providers, pd)
|
providers = append(providers, pd)
|
||||||
providersMap[groupName] = pd
|
providersMap[groupName] = pd
|
||||||
} else {
|
} else {
|
||||||
if groupOption.URL == "" || groupOption.Interval == 0 {
|
if groupOption.URL == "" {
|
||||||
//return nil, errMissHealthCheck
|
|
||||||
groupOption.URL = "http://www.gstatic.com/generate_204"
|
groupOption.URL = "http://www.gstatic.com/generate_204"
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupOption.Interval == 0 {
|
||||||
groupOption.Interval = 300
|
groupOption.Interval = 300
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,42 +16,35 @@ var (
|
|||||||
dirMode os.FileMode = 0o755
|
dirMode os.FileMode = 0o755
|
||||||
)
|
)
|
||||||
|
|
||||||
type parser = func([]byte) (any, error)
|
type parser[V any] func([]byte) (V, error)
|
||||||
|
|
||||||
type fetcher struct {
|
type fetcher[V any] struct {
|
||||||
name string
|
name string
|
||||||
vehicle types.Vehicle
|
vehicle types.Vehicle
|
||||||
updatedAt *time.Time
|
updatedAt *time.Time
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
hash [16]byte
|
hash [16]byte
|
||||||
parser parser
|
parser parser[V]
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
onUpdate func(any)
|
onUpdate func(V)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) Name() string {
|
func (f *fetcher[V]) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) VehicleType() types.VehicleType {
|
func (f *fetcher[V]) VehicleType() types.VehicleType {
|
||||||
return f.vehicle.Type()
|
return f.vehicle.Type()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) Initial() (any, error) {
|
func (f *fetcher[V]) Initial() (V, error) {
|
||||||
var (
|
var (
|
||||||
buf []byte
|
buf []byte
|
||||||
err error
|
err error
|
||||||
isLocal bool
|
isLocal bool
|
||||||
)
|
)
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// pull proxies automatically
|
|
||||||
if f.ticker != nil {
|
|
||||||
go f.pullLoop()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||||
buf, err = os.ReadFile(f.vehicle.Path())
|
buf, err = os.ReadFile(f.vehicle.Path())
|
||||||
modTime := stat.ModTime()
|
modTime := stat.ModTime()
|
||||||
@ -60,7 +53,7 @@ func (f *fetcher) Initial() (any, error) {
|
|||||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
|
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
|
||||||
go f.update()
|
go f.Update()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -68,24 +61,24 @@ func (f *fetcher) Initial() (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies, err := f.parser(buf)
|
proxies, err := f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
return nil, err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse local file error, fallback to remote
|
// parse local file error, fallback to remote
|
||||||
buf, err = f.vehicle.Read()
|
buf, err = f.vehicle.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies, err = f.parser(buf)
|
proxies, err = f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocal = false
|
isLocal = false
|
||||||
@ -93,19 +86,24 @@ func (f *fetcher) Initial() (any, error) {
|
|||||||
|
|
||||||
if f.vehicle.Type() != types.File && !isLocal {
|
if f.vehicle.Type() != types.File && !isLocal {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return nil, err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.hash = md5.Sum(buf)
|
f.hash = md5.Sum(buf)
|
||||||
|
|
||||||
|
// pull proxies automatically
|
||||||
|
if f.ticker != nil {
|
||||||
|
go f.pullLoop()
|
||||||
|
}
|
||||||
|
|
||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) Update() (any, bool, error) {
|
func (f *fetcher[V]) Update() (V, bool, error) {
|
||||||
buf, err := f.vehicle.Read()
|
buf, err := f.vehicle.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@ -113,17 +111,17 @@ func (f *fetcher) Update() (any, bool, error) {
|
|||||||
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)
|
os.Chtimes(f.vehicle.Path(), now, now)
|
||||||
return nil, true, nil
|
return getZero[V](), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies, err := f.parser(buf)
|
proxies, err := f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != types.File {
|
if f.vehicle.Type() != types.File {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return nil, false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,22 +131,32 @@ func (f *fetcher) Update() (any, bool, error) {
|
|||||||
return proxies, false, nil
|
return proxies, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) Destroy() error {
|
func (f *fetcher[V]) Destroy() error {
|
||||||
if f.ticker != nil {
|
if f.ticker != nil {
|
||||||
f.done <- struct{}{}
|
f.done <- struct{}{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) pullLoop() {
|
func (f *fetcher[V]) pullLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-f.ticker.C:
|
case <-f.ticker.C:
|
||||||
same, err := f.update()
|
elm, same, err := f.Update()
|
||||||
if same || err != nil {
|
if err != nil {
|
||||||
|
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if same {
|
||||||
|
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("[Provider] %s's proxies update", f.Name())
|
||||||
|
if f.onUpdate != nil {
|
||||||
|
f.onUpdate(elm)
|
||||||
|
}
|
||||||
case <-f.done:
|
case <-f.done:
|
||||||
f.ticker.Stop()
|
f.ticker.Stop()
|
||||||
return
|
return
|
||||||
@ -156,26 +164,6 @@ func (f *fetcher) pullLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetcher) update() (same bool, err error) {
|
|
||||||
elm, same, err := f.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if same {
|
|
||||||
log.Debugln("[Provider] %s's proxies doesn't change", f.Name())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.onUpdate != nil {
|
|
||||||
f.onUpdate(elm)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infoln("[Provider] %s's proxies update", f.Name())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeWrite(path string, buf []byte) error {
|
func safeWrite(path string, buf []byte) error {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
@ -188,19 +176,23 @@ func safeWrite(path string, buf []byte) error {
|
|||||||
return os.WriteFile(path, buf, fileMode)
|
return os.WriteFile(path, buf, fileMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher {
|
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
|
||||||
var ticker *time.Ticker
|
var ticker *time.Ticker
|
||||||
if interval != 0 {
|
if interval != 0 {
|
||||||
ticker = time.NewTicker(interval)
|
ticker = time.NewTicker(interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &fetcher{
|
return &fetcher[V]{
|
||||||
name: name,
|
name: name,
|
||||||
ticker: ticker,
|
ticker: ticker,
|
||||||
vehicle: vehicle,
|
vehicle: vehicle,
|
||||||
parser: parser,
|
parser: parser,
|
||||||
done: make(chan struct{}, 1),
|
done: make(chan struct{}, 1),
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
interval: interval,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getZero[V any]() V {
|
||||||
|
var result V
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -32,9 +32,7 @@ func (hc *HealthCheck) process() {
|
|||||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
t := time.NewTicker(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
<-t.C
|
|
||||||
t.Stop()
|
|
||||||
hc.check()
|
hc.check()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/common/convert"
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -24,33 +25,32 @@ type ProxySchema struct {
|
|||||||
Proxies []map[string]any `yaml:"proxies"`
|
Proxies []map[string]any `yaml:"proxies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// for auto gc
|
// ProxySetProvider for auto gc
|
||||||
type ProxySetProvider struct {
|
type ProxySetProvider struct {
|
||||||
*proxySetProvider
|
*proxySetProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type proxySetProvider struct {
|
type proxySetProvider struct {
|
||||||
*fetcher
|
*fetcher[[]C.Proxy]
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
healthCheck *HealthCheck
|
healthCheck *HealthCheck
|
||||||
version uint
|
version uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Version() uint {
|
|
||||||
return pp.version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(map[string]any{
|
return json.Marshal(map[string]any{
|
||||||
"name": pp.Name(),
|
"name": pp.Name(),
|
||||||
"type": pp.Type().String(),
|
"type": pp.Type().String(),
|
||||||
"vehicleType": pp.VehicleType().String(),
|
"vehicleType": pp.VehicleType().String(),
|
||||||
"proxies": pp.Proxies(),
|
"proxies": pp.Proxies(),
|
||||||
//TODO maybe error because year value overflow
|
"updatedAt": pp.updatedAt,
|
||||||
"updatedAt": pp.updatedAt,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *proxySetProvider) Version() uint {
|
||||||
|
return pp.version
|
||||||
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Name() string {
|
func (pp *proxySetProvider) Name() string {
|
||||||
return pp.name
|
return pp.name
|
||||||
}
|
}
|
||||||
@ -72,12 +72,7 @@ func (pp *proxySetProvider) Initial() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pp.onUpdate(elm)
|
pp.onUpdate(elm)
|
||||||
if pp.healthCheck.auto() {
|
|
||||||
defer func() { go pp.healthCheck.process() }()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,52 +84,148 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
|
|||||||
return pp.proxies
|
return pp.proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
|
func (pp *proxySetProvider) Touch() {
|
||||||
pp.healthCheck.touch()
|
pp.healthCheck.touch()
|
||||||
return pp.Proxies()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||||
pp.proxies = proxies
|
pp.proxies = proxies
|
||||||
pp.healthCheck.setProxy(proxies)
|
pp.healthCheck.setProxy(proxies)
|
||||||
if pp.healthCheck.auto() {
|
if pp.healthCheck.auto() {
|
||||||
go pp.healthCheck.check()
|
defer func() { go pp.healthCheck.check() }()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopProxyProvider(pd *ProxySetProvider) {
|
func stopProxyProvider(pd *ProxySetProvider) {
|
||||||
pd.healthCheck.close()
|
pd.healthCheck.close()
|
||||||
pd.fetcher.Destroy()
|
_ = pd.fetcher.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||||
//filterReg, err := regexp.Compile(filter)
|
|
||||||
filterReg, err := regexp2.Compile(filter, 0)
|
filterReg, err := regexp2.Compile(filter, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate := func(elm any) {
|
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg), proxiesOnUpdate(pd))
|
||||||
ret := elm.([]C.Proxy)
|
pd.fetcher = fetcher
|
||||||
pd.setProxies(ret)
|
|
||||||
|
wrapper := &ProxySetProvider{pd}
|
||||||
|
runtime.SetFinalizer(wrapper, stopProxyProvider)
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompatibleProvider for auto gc
|
||||||
|
type CompatibleProvider struct {
|
||||||
|
*compatibleProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
type compatibleProvider struct {
|
||||||
|
name string
|
||||||
|
healthCheck *HealthCheck
|
||||||
|
proxies []C.Proxy
|
||||||
|
version uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]any{
|
||||||
|
"name": cp.Name(),
|
||||||
|
"type": cp.Type().String(),
|
||||||
|
"vehicleType": cp.VehicleType().String(),
|
||||||
|
"proxies": cp.Proxies(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Version() uint {
|
||||||
|
return cp.version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Name() string {
|
||||||
|
return cp.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) HealthCheck() {
|
||||||
|
cp.healthCheck.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Update() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Initial() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||||
|
return types.Compatible
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Type() types.ProviderType {
|
||||||
|
return types.Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||||
|
return cp.proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Touch() {
|
||||||
|
cp.healthCheck.touch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||||
|
pd.healthCheck.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return nil, errors.New("provider need one proxy at least")
|
||||||
|
}
|
||||||
|
|
||||||
|
if hc.auto() {
|
||||||
|
go hc.process()
|
||||||
|
}
|
||||||
|
|
||||||
|
pd := &compatibleProvider{
|
||||||
|
name: name,
|
||||||
|
proxies: proxies,
|
||||||
|
healthCheck: hc,
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := &CompatibleProvider{pd}
|
||||||
|
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||||
|
return func(elm []C.Proxy) {
|
||||||
|
pd.setProxies(elm)
|
||||||
if pd.version == math.MaxUint {
|
if pd.version == math.MaxUint {
|
||||||
pd.version = 0
|
pd.version = 0
|
||||||
} else {
|
} else {
|
||||||
pd.version++
|
pd.version++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
proxiesParseAndFilter := func(buf []byte) (any, error) {
|
func proxiesParseAndFilter(filter string, filterReg *regexp2.Regexp) parser[[]C.Proxy] {
|
||||||
|
return func(buf []byte) ([]C.Proxy, error) {
|
||||||
schema := &ProxySchema{}
|
schema := &ProxySchema{}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||||
return nil, err
|
proxies, err1 := convert.ConvertsV2Ray(buf)
|
||||||
|
if err1 != nil {
|
||||||
|
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
|
||||||
|
}
|
||||||
|
schema.Proxies = proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
if schema.Proxies == nil {
|
if schema.Proxies == nil {
|
||||||
@ -164,93 +255,4 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
|||||||
|
|
||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fetcher := newFetcher(name, interval, vehicle, proxiesParseAndFilter, onUpdate)
|
|
||||||
pd.fetcher = fetcher
|
|
||||||
|
|
||||||
wrapper := &ProxySetProvider{pd}
|
|
||||||
runtime.SetFinalizer(wrapper, stopProxyProvider)
|
|
||||||
return wrapper, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// for auto gc
|
|
||||||
type CompatibleProvider struct {
|
|
||||||
*compatibleProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
type compatibleProvider struct {
|
|
||||||
name string
|
|
||||||
healthCheck *HealthCheck
|
|
||||||
proxies []C.Proxy
|
|
||||||
version uint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Version() uint {
|
|
||||||
return cp.version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(map[string]any{
|
|
||||||
"name": cp.Name(),
|
|
||||||
"type": cp.Type().String(),
|
|
||||||
"vehicleType": cp.VehicleType().String(),
|
|
||||||
"proxies": cp.Proxies(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Name() string {
|
|
||||||
return cp.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) HealthCheck() {
|
|
||||||
cp.healthCheck.check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Update() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Initial() error {
|
|
||||||
if cp.healthCheck.auto() {
|
|
||||||
go cp.healthCheck.process()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
|
||||||
return types.Compatible
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Type() types.ProviderType {
|
|
||||||
return types.Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
|
||||||
return cp.proxies
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy {
|
|
||||||
cp.healthCheck.touch()
|
|
||||||
return cp.Proxies()
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopCompatibleProvider(pd *CompatibleProvider) {
|
|
||||||
pd.healthCheck.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
|
||||||
if len(proxies) == 0 {
|
|
||||||
return nil, errors.New("provider need one proxy at least")
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := &compatibleProvider{
|
|
||||||
name: name,
|
|
||||||
proxies: proxies,
|
|
||||||
healthCheck: hc,
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper := &CompatibleProvider{pd}
|
|
||||||
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
|
|
||||||
return wrapper, nil
|
|
||||||
}
|
}
|
||||||
|
344
common/convert/converter.go
Normal file
344
common/convert/converter.go
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var enc = base64.StdEncoding
|
||||||
|
|
||||||
|
func DecodeBase64(buf []byte) ([]byte, error) {
|
||||||
|
dBuf := make([]byte, enc.DecodedLen(len(buf)))
|
||||||
|
n, err := enc.Decode(dBuf, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dBuf[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBase64StringToString decode base64 string to string
|
||||||
|
func DecodeBase64StringToString(s string) (string, error) {
|
||||||
|
dBuf, err := enc.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(dBuf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
|
||||||
|
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||||
|
data, err := DecodeBase64(buf)
|
||||||
|
if err != nil {
|
||||||
|
data = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := strings.Split(string(data), "\n")
|
||||||
|
|
||||||
|
proxies := make([]map[string]any, 0, len(arr))
|
||||||
|
names := make(map[string]int, 200)
|
||||||
|
|
||||||
|
for _, line := range arr {
|
||||||
|
line = strings.TrimRight(line, " \r")
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme, body, found := strings.Cut(line, "://")
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme = strings.ToLower(scheme)
|
||||||
|
switch scheme {
|
||||||
|
case "trojan":
|
||||||
|
urlTrojan, err := url.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
q := urlTrojan.Query()
|
||||||
|
|
||||||
|
name := uniqueName(names, urlTrojan.Fragment)
|
||||||
|
trojan := make(map[string]any, 20)
|
||||||
|
|
||||||
|
trojan["name"] = name
|
||||||
|
trojan["type"] = scheme
|
||||||
|
trojan["server"] = urlTrojan.Hostname()
|
||||||
|
trojan["port"] = urlTrojan.Port()
|
||||||
|
trojan["password"] = urlTrojan.User.Username()
|
||||||
|
trojan["udp"] = true
|
||||||
|
trojan["skip-cert-verify"] = false
|
||||||
|
|
||||||
|
sni := q.Get("sni")
|
||||||
|
if sni != "" {
|
||||||
|
trojan["sni"] = sni
|
||||||
|
}
|
||||||
|
|
||||||
|
network := strings.ToLower(q.Get("type"))
|
||||||
|
if network != "" {
|
||||||
|
trojan["network"] = network
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == "ws" {
|
||||||
|
headers := make(map[string]any)
|
||||||
|
wsOpts := make(map[string]any)
|
||||||
|
|
||||||
|
headers["Host"] = RandHost()
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
|
||||||
|
wsOpts["path"] = q.Get("path")
|
||||||
|
wsOpts["headers"] = headers
|
||||||
|
|
||||||
|
trojan["ws-opts"] = wsOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies = append(proxies, trojan)
|
||||||
|
case "vmess":
|
||||||
|
dcBuf, err := enc.DecodeString(body)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonDc := json.NewDecoder(bytes.NewReader(dcBuf))
|
||||||
|
values := make(map[string]any, 20)
|
||||||
|
|
||||||
|
if jsonDc.Decode(&values) != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := uniqueName(names, values["ps"].(string))
|
||||||
|
vmess := make(map[string]any, 20)
|
||||||
|
|
||||||
|
vmess["name"] = name
|
||||||
|
vmess["type"] = scheme
|
||||||
|
vmess["server"] = values["add"]
|
||||||
|
vmess["port"] = values["port"]
|
||||||
|
vmess["uuid"] = values["id"]
|
||||||
|
vmess["alterId"] = values["aid"]
|
||||||
|
vmess["cipher"] = "auto"
|
||||||
|
vmess["udp"] = true
|
||||||
|
vmess["skip-cert-verify"] = false
|
||||||
|
|
||||||
|
host := values["host"]
|
||||||
|
network := strings.ToLower(values["net"].(string))
|
||||||
|
|
||||||
|
vmess["network"] = network
|
||||||
|
|
||||||
|
tls := strings.ToLower(values["tls"].(string))
|
||||||
|
if tls != "" && tls != "0" && tls != "null" {
|
||||||
|
if host != nil {
|
||||||
|
vmess["servername"] = host
|
||||||
|
}
|
||||||
|
vmess["tls"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == "ws" {
|
||||||
|
headers := make(map[string]any)
|
||||||
|
wsOpts := make(map[string]any)
|
||||||
|
|
||||||
|
headers["Host"] = RandHost()
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
|
||||||
|
if values["path"] != nil {
|
||||||
|
wsOpts["path"] = values["path"]
|
||||||
|
}
|
||||||
|
wsOpts["headers"] = headers
|
||||||
|
|
||||||
|
vmess["ws-opts"] = wsOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies = append(proxies, vmess)
|
||||||
|
case "ss":
|
||||||
|
urlSS, err := url.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := uniqueName(names, urlSS.Fragment)
|
||||||
|
port := urlSS.Port()
|
||||||
|
|
||||||
|
if port == "" {
|
||||||
|
dcBuf, err := enc.DecodeString(urlSS.Host)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSS, err = url.Parse("ss://" + string(dcBuf))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cipher = urlSS.User.Username()
|
||||||
|
password string
|
||||||
|
)
|
||||||
|
|
||||||
|
if password, found = urlSS.User.Password(); !found {
|
||||||
|
dcBuf, err := enc.DecodeString(cipher)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := make(map[string]any, 20)
|
||||||
|
|
||||||
|
ss["name"] = name
|
||||||
|
ss["type"] = scheme
|
||||||
|
ss["server"] = urlSS.Hostname()
|
||||||
|
ss["port"] = urlSS.Port()
|
||||||
|
ss["cipher"] = cipher
|
||||||
|
ss["password"] = password
|
||||||
|
ss["udp"] = true
|
||||||
|
|
||||||
|
proxies = append(proxies, ss)
|
||||||
|
case "ssr":
|
||||||
|
dcBuf, err := enc.DecodeString(body)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
|
||||||
|
|
||||||
|
before, after, ok := strings.Cut(string(dcBuf), "/?")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeArr := strings.Split(before, ":")
|
||||||
|
|
||||||
|
if len(beforeArr) != 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
host := beforeArr[0]
|
||||||
|
port := beforeArr[1]
|
||||||
|
protocol := beforeArr[2]
|
||||||
|
method := beforeArr[3]
|
||||||
|
obfs := beforeArr[4]
|
||||||
|
password := decodeUrlSafe(urlSafe(beforeArr[5]))
|
||||||
|
|
||||||
|
query, err := url.ParseQuery(urlSafe(after))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
remarks := decodeUrlSafe(query.Get("remarks"))
|
||||||
|
name := uniqueName(names, remarks)
|
||||||
|
|
||||||
|
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
|
||||||
|
protocolParam := query.Get("protoparam")
|
||||||
|
|
||||||
|
ssr := make(map[string]any, 20)
|
||||||
|
|
||||||
|
ssr["name"] = name
|
||||||
|
ssr["type"] = scheme
|
||||||
|
ssr["server"] = host
|
||||||
|
ssr["port"] = port
|
||||||
|
ssr["cipher"] = method
|
||||||
|
ssr["password"] = password
|
||||||
|
ssr["obfs"] = obfs
|
||||||
|
ssr["protocol"] = protocol
|
||||||
|
ssr["udp"] = true
|
||||||
|
|
||||||
|
if obfsParam != "" {
|
||||||
|
ssr["obfs-param"] = obfsParam
|
||||||
|
}
|
||||||
|
|
||||||
|
if protocolParam != "" {
|
||||||
|
ssr["protocol-param"] = protocolParam
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies = append(proxies, ssr)
|
||||||
|
case "vless":
|
||||||
|
urlVless, err := url.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
q := urlVless.Query()
|
||||||
|
|
||||||
|
name := uniqueName(names, urlVless.Fragment)
|
||||||
|
vless := make(map[string]any, 20)
|
||||||
|
|
||||||
|
vless["name"] = name
|
||||||
|
vless["type"] = scheme
|
||||||
|
vless["server"] = urlVless.Hostname()
|
||||||
|
vless["port"] = urlVless.Port()
|
||||||
|
vless["uuid"] = urlVless.User.Username()
|
||||||
|
vless["udp"] = true
|
||||||
|
vless["skip-cert-verify"] = false
|
||||||
|
|
||||||
|
sni := q.Get("sni")
|
||||||
|
if sni != "" {
|
||||||
|
vless["servername"] = sni
|
||||||
|
}
|
||||||
|
|
||||||
|
flow := strings.ToLower(q.Get("flow"))
|
||||||
|
if flow != "" {
|
||||||
|
vless["flow"] = flow
|
||||||
|
}
|
||||||
|
|
||||||
|
network := strings.ToLower(q.Get("type"))
|
||||||
|
if network != "" {
|
||||||
|
vless["network"] = network
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == "ws" {
|
||||||
|
headers := make(map[string]any)
|
||||||
|
wsOpts := make(map[string]any)
|
||||||
|
|
||||||
|
headers["Host"] = RandHost()
|
||||||
|
headers["User-Agent"] = RandUserAgent()
|
||||||
|
|
||||||
|
wsOpts["path"] = q.Get("path")
|
||||||
|
wsOpts["headers"] = headers
|
||||||
|
|
||||||
|
vless["ws-opts"] = wsOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies = append(proxies, vless)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(proxies) == 0 {
|
||||||
|
return nil, fmt.Errorf("convert v2ray subscribe error: format invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlSafe(data string) string {
|
||||||
|
return strings.ReplaceAll(strings.ReplaceAll(data, "+", "-"), "/", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUrlSafe(data string) string {
|
||||||
|
dcBuf, err := base64.URLEncoding.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(dcBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueName(names map[string]int, name string) string {
|
||||||
|
if index, ok := names[name]; ok {
|
||||||
|
index++
|
||||||
|
names[name] = index
|
||||||
|
name = name + "-" + fmt.Sprintf("%02d", index)
|
||||||
|
} else {
|
||||||
|
index = 0
|
||||||
|
names[name] = index
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
313
common/convert/util.go
Normal file
313
common/convert/util.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hostsSuffix = []string{
|
||||||
|
"-cdn.aliyuncs.com",
|
||||||
|
".alicdn.com",
|
||||||
|
".pan.baidu.com",
|
||||||
|
".tbcache.com",
|
||||||
|
".aliyuncdn.com",
|
||||||
|
".vod.miguvideo.com",
|
||||||
|
".cibntv.net",
|
||||||
|
".myqcloud.com",
|
||||||
|
".smtcdns.com",
|
||||||
|
".alikunlun.com",
|
||||||
|
".smtcdns.net",
|
||||||
|
".apcdns.net",
|
||||||
|
".cdn-go.cn",
|
||||||
|
".cdntip.com",
|
||||||
|
".cdntips.com",
|
||||||
|
".alidayu.com",
|
||||||
|
".alidns.com",
|
||||||
|
".cdngslb.com",
|
||||||
|
".mxhichina.com",
|
||||||
|
".alibabadns.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var userAgents = []string{
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3",
|
||||||
|
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hostsLen = len(hostsSuffix)
|
||||||
|
uaLen = len(userAgents)
|
||||||
|
)
|
||||||
|
|
||||||
|
func RandHost() string {
|
||||||
|
id, _ := uuid.NewV4()
|
||||||
|
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
||||||
|
base = strings.ReplaceAll(base, "-", "")
|
||||||
|
base = strings.ReplaceAll(base, "_", "")
|
||||||
|
buf := []byte(base)
|
||||||
|
prefix := string(buf[:3]) + "---"
|
||||||
|
prefix += string(buf[6:8]) + "-"
|
||||||
|
prefix += string(buf[len(buf)-8:])
|
||||||
|
|
||||||
|
return prefix + hostsSuffix[rand.Intn(hostsLen)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandUserAgent() string {
|
||||||
|
return userAgents[rand.Intn(uaLen)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetUserAgent(req *http.Request) {
|
||||||
|
userAgent := RandUserAgent()
|
||||||
|
req.Header.Set("User-Agent", userAgent)
|
||||||
|
}
|
52
component/geodata/init.go
Normal file
52
component/geodata/init.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package geodata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initFlag bool
|
||||||
|
|
||||||
|
func InitGeoSite() error {
|
||||||
|
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||||
|
log.Infoln("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())
|
||||||
|
}
|
||||||
|
log.Infoln("Download GeoSite.dat finish")
|
||||||
|
}
|
||||||
|
if !initFlag {
|
||||||
|
if err := Verify(C.GeositeName); err != nil {
|
||||||
|
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
|
||||||
|
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
||||||
|
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
||||||
|
}
|
||||||
|
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||||
|
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initFlag = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadGeoSite(path string) (err error) {
|
||||||
|
resp, err := http.Get(C.GeoSiteUrl)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(f, resp.Body)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
@ -16,8 +16,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
R "github.com/Dreamacro/clash/rule"
|
R "github.com/Dreamacro/clash/rules"
|
||||||
RP "github.com/Dreamacro/clash/rule/provider"
|
RP "github.com/Dreamacro/clash/rules/provider"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
@ -533,7 +533,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
|||||||
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
|
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
|
||||||
// parse rule provider
|
// parse rule provider
|
||||||
for name, mapping := range cfg.RuleProvider {
|
for name, mapping := range cfg.RuleProvider {
|
||||||
rp, err := RP.ParseRuleProvider(name, mapping)
|
rp, err := RP.ParseRuleProvider(name, mapping, R.ParseRule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -583,13 +583,6 @@ 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: %s", err)
|
|
||||||
}
|
|
||||||
initMode = false
|
|
||||||
}
|
|
||||||
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())
|
||||||
@ -730,7 +723,7 @@ func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, 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 len(countries) > 0 {
|
||||||
if err := initGeoSite(); err != nil {
|
if err := geodata.InitGeoSite(); err != nil {
|
||||||
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
|
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var initMode = true
|
|
||||||
|
|
||||||
func downloadMMDB(path string) (err error) {
|
func downloadMMDB(path string) (err error) {
|
||||||
resp, err := http.Get(C.MmdbUrl)
|
resp, err := http.Get(C.MmdbUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,45 +46,6 @@ func downloadGeoIP(path string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadGeoSite(path string) (err error) {
|
|
||||||
resp, err := http.Get(C.GeoSiteUrl)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = io.Copy(f, resp.Body)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func initGeoSite() error {
|
|
||||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
|
||||||
log.Infoln("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())
|
|
||||||
}
|
|
||||||
log.Infoln("Download GeoSite.dat finish")
|
|
||||||
}
|
|
||||||
if initMode {
|
|
||||||
if err := geodata.Verify(C.GeositeName); err != nil {
|
|
||||||
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
|
|
||||||
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
|
||||||
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
|
||||||
}
|
|
||||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
|
||||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initGeoIP() error {
|
func initGeoIP() error {
|
||||||
if C.GeodataMode {
|
if C.GeodataMode {
|
||||||
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
||||||
|
@ -66,9 +66,7 @@ type Provider interface {
|
|||||||
type ProxyProvider interface {
|
type ProxyProvider interface {
|
||||||
Provider
|
Provider
|
||||||
Proxies() []C.Proxy
|
Proxies() []C.Proxy
|
||||||
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
Touch()
|
||||||
// Commonly used in DialContext and DialPacketConn
|
|
||||||
ProxiesWithTouch() []C.Proxy
|
|
||||||
HealthCheck()
|
HealthCheck()
|
||||||
Version() uint
|
Version() uint
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,10 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
if c.proxyAdapter == "" {
|
if c.proxyAdapter != "" {
|
||||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
conn, err = dialContextExtra(ctx, c.proxyAdapter, network, ip, c.port, options...)
|
||||||
} else {
|
} else {
|
||||||
conn, err = dialContextWithProxyAdapter(ctx, c.proxyAdapter, network, ip, c.port, options...)
|
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -100,7 +100,7 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
|||||||
if proxyAdapter == "" {
|
if proxyAdapter == "" {
|
||||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||||
} else {
|
} else {
|
||||||
return dialContextWithProxyAdapter(ctx, proxyAdapter, "tcp", ip, port)
|
return dialContextExtra(ctx, proxyAdapter, "tcp", ip, port)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -167,7 +167,7 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
conn, err := dialContextWithProxyAdapter(context.Background(), dc.proxyAdapter, "udp", ip, port)
|
conn, err := dialContextExtra(context.Background(), dc.proxyAdapter, "udp", ip, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,11 +46,11 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip
|
|||||||
rr.A = ip.AsSlice()
|
rr.A = ip.AsSlice()
|
||||||
|
|
||||||
msg.Answer = []D.RR{rr}
|
msg.Answer = []D.RR{rr}
|
||||||
} else if ip.Is6() && q.Qtype == D.TypeAAAA {
|
} else if q.Qtype == D.TypeAAAA {
|
||||||
rr := &D.AAAA{}
|
rr := &D.AAAA{}
|
||||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
||||||
rr.AAAA = ip.AsSlice()
|
ip := ip.As16()
|
||||||
|
rr.AAAA = ip[:]
|
||||||
msg.Answer = []D.RR{rr}
|
msg.Answer = []D.RR{rr}
|
||||||
} else {
|
} else {
|
||||||
return next(ctx, r)
|
return next(ctx, r)
|
||||||
|
@ -143,10 +143,11 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
|
|||||||
return wpc.rAddr
|
return wpc.rAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
|
func dialContextExtra(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
|
||||||
adapter, ok := tunnel.Proxies()[adapterName]
|
adapter, ok := tunnel.Proxies()[adapterName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName)
|
opts = append(opts, dialer.WithInterface(adapterName))
|
||||||
|
adapter, _ = tunnel.Proxies()[tunnel.Direct.String()]
|
||||||
}
|
}
|
||||||
|
|
||||||
networkType := C.TCP
|
networkType := C.TCP
|
||||||
|
19
go.mod
19
go.mod
@ -3,7 +3,6 @@ module github.com/Dreamacro/clash
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8
|
|
||||||
github.com/dlclark/regexp2 v1.4.0
|
github.com/dlclark/regexp2 v1.4.0
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
@ -12,7 +11,7 @@ require (
|
|||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
|
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
|
||||||
github.com/lucas-clemente/quic-go v0.27.0
|
github.com/lucas-clemente/quic-go v0.27.0
|
||||||
github.com/miekg/dns v1.1.48
|
github.com/miekg/dns v1.1.49
|
||||||
github.com/oschwald/geoip2-golang v1.7.0
|
github.com/oschwald/geoip2-golang v1.7.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
@ -21,17 +20,17 @@ require (
|
|||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
go.uber.org/automaxprocs v1.5.1
|
go.uber.org/automaxprocs v1.5.1
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -56,7 +55,7 @@ require (
|
|||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/vishvananda/netlink v1.2.0-beta.0.20220404152918-5e915e014938 => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820
|
replace github.com/vishvananda/netlink v1.2.0-beta.0.20220404152918-5e915e014938 => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820
|
||||||
|
37
go.sum
37
go.sum
@ -8,8 +8,6 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
|
|||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g=
|
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y=
|
|
||||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc=
|
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc=
|
||||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
@ -126,8 +124,8 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ
|
|||||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
|
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||||
github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
@ -224,11 +222,11 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
@ -260,8 +258,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
||||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -273,8 +271,9 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||||
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -310,8 +309,8 @@ golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -347,8 +346,8 @@ golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 h1:SEYkJAIuYAsSAPkCffOiYLtq5brBDSI+L0mRjSsvSTY=
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
@ -389,11 +388,11 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150 h1:bspdBY1iCLtW6JXold8yhXHkAiE9UoWfmHShNkTc9JA=
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
|
||||||
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
package logic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAND(t *testing.T) {
|
|
||||||
and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
assert.Equal(t, "DIRECT", and.adapter)
|
|
||||||
assert.Equal(t, false, and.ShouldResolveIP())
|
|
||||||
assert.Equal(t, true, and.Match(&constant.Metadata{
|
|
||||||
Host: "baidu.com",
|
|
||||||
AddrType: constant.AtypDomainName,
|
|
||||||
NetWork: constant.TCP,
|
|
||||||
DstPort: "20000",
|
|
||||||
}))
|
|
||||||
|
|
||||||
and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
|
||||||
assert.NotEqual(t, nil, err)
|
|
||||||
|
|
||||||
and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNOT(t *testing.T) {
|
|
||||||
not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT")
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
assert.Equal(t, false, not.Match(&constant.Metadata{
|
|
||||||
DstPort: "6100",
|
|
||||||
}))
|
|
||||||
|
|
||||||
_, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT")
|
|
||||||
assert.NotEqual(t, nil, err)
|
|
||||||
|
|
||||||
_, err = NewNOT("(())", "DIRECT")
|
|
||||||
assert.NotEqual(t, nil, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOR(t *testing.T) {
|
|
||||||
or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
assert.Equal(t, true, or.Match(&constant.Metadata{
|
|
||||||
NetWork: constant.TCP,
|
|
||||||
}))
|
|
||||||
assert.Equal(t, false, or.ShouldResolveIP())
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package rules
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
RC "github.com/Dreamacro/clash/rule/common"
|
|
||||||
"github.com/Dreamacro/clash/rule/logic"
|
|
||||||
RP "github.com/Dreamacro/clash/rule/provider"
|
|
||||||
"github.com/Dreamacro/clash/rule/ruleparser"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
|
||||||
switch tp {
|
|
||||||
case "AND":
|
|
||||||
parsed, parseErr = logic.NewAND(payload, target)
|
|
||||||
case "OR":
|
|
||||||
parsed, parseErr = logic.NewOR(payload, target)
|
|
||||||
case "NOT":
|
|
||||||
parsed, parseErr = logic.NewNOT(payload, target)
|
|
||||||
case "RULE-SET":
|
|
||||||
noResolve := RC.HasNoResolve(params)
|
|
||||||
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve)
|
|
||||||
case "MATCH":
|
|
||||||
parsed = RC.NewMatch(target)
|
|
||||||
parseErr = nil
|
|
||||||
default:
|
|
||||||
parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, parseErr
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleExtra := &C.RuleExtra{
|
|
||||||
Network: RC.FindNetwork(params),
|
|
||||||
SourceIPs: RC.FindSourceIPs(params),
|
|
||||||
ProcessNames: RC.FindProcessName(params),
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.SetRuleExtra(ruleExtra)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -2,17 +2,13 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
|
_ "github.com/Dreamacro/clash/component/geodata/memconservative"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
|
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
_ "unsafe"
|
||||||
_ "github.com/Dreamacro/clash/component/geodata/memconservative"
|
|
||||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GEOSITE struct {
|
type GEOSITE struct {
|
||||||
@ -54,7 +50,7 @@ func (gs *GEOSITE) GetRecodeSize() int {
|
|||||||
|
|
||||||
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||||
if !initFlag {
|
if !initFlag {
|
||||||
if err := initGeoSite(); err != nil {
|
if err := geodata.InitGeoSite(); err != nil {
|
||||||
log.Errorln("can't initial GeoSite: %s", err)
|
log.Errorln("can't initial GeoSite: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -80,45 +76,3 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ C.Rule = (*GEOSITE)(nil)
|
var _ C.Rule = (*GEOSITE)(nil)
|
||||||
|
|
||||||
func downloadGeoSite(path string) (err error) {
|
|
||||||
resp, err := http.Get(C.GeoSiteUrl)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = io.Copy(f, resp.Body)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func initGeoSite() error {
|
|
||||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
|
||||||
log.Infoln("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())
|
|
||||||
}
|
|
||||||
log.Infoln("Download GeoSite.dat finish")
|
|
||||||
}
|
|
||||||
if !initFlag {
|
|
||||||
err := geodata.Verify(C.GeositeName)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
|
|
||||||
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
|
||||||
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
|
||||||
}
|
|
||||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
|
||||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
initFlag = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ package logic
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/rule/common"
|
"github.com/Dreamacro/clash/rules/common"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,9 +19,10 @@ func (A *AND) ShouldFindProcess() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAND(payload string, adapter string) (*AND, error) {
|
func NewAND(payload string, adapter string,
|
||||||
|
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*AND, error) {
|
||||||
and := &AND{Base: &common.Base{}, payload: payload, adapter: adapter}
|
and := &AND{Base: &common.Base{}, payload: payload, adapter: adapter}
|
||||||
rules, err := parseRuleByPayload(payload)
|
rules, err := parseRuleByPayload(payload, parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -4,14 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/common/collections"
|
"github.com/Dreamacro/clash/common/collections"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
RC "github.com/Dreamacro/clash/rule/common"
|
|
||||||
RP "github.com/Dreamacro/clash/rule/provider"
|
|
||||||
"github.com/Dreamacro/clash/rule/ruleparser"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseRuleByPayload(payload string) ([]C.Rule, error) {
|
func parseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ([]C.Rule, error) {
|
||||||
regex, err := regexp.Compile("\\(.*\\)")
|
regex, err := regexp.Compile("\\(.*\\)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -28,7 +26,7 @@ func parseRuleByPayload(payload string) ([]C.Rule, error) {
|
|||||||
for _, subRange := range subRanges {
|
for _, subRange := range subRanges {
|
||||||
subPayload := payload[subRange.start+1 : subRange.end]
|
subPayload := payload[subRange.start+1 : subRange.end]
|
||||||
|
|
||||||
rule, err := payloadToRule(subPayload)
|
rule, err := payloadToRule(subPayload, parseLogicSubRule(parseRule))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -46,7 +44,7 @@ func containRange(r Range, preStart, preEnd int) bool {
|
|||||||
return preStart < r.start && preEnd > r.end
|
return preStart < r.start && preEnd > r.end
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadToRule(subPayload string) (C.Rule, error) {
|
func payloadToRule(subPayload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (C.Rule, error) {
|
||||||
splitStr := strings.SplitN(subPayload, ",", 2)
|
splitStr := strings.SplitN(subPayload, ",", 2)
|
||||||
if len(splitStr) < 2 {
|
if len(splitStr) < 2 {
|
||||||
return nil, fmt.Errorf("[%s] format is error", subPayload)
|
return nil, fmt.Errorf("[%s] format is error", subPayload)
|
||||||
@ -55,40 +53,21 @@ func payloadToRule(subPayload string) (C.Rule, error) {
|
|||||||
tp := splitStr[0]
|
tp := splitStr[0]
|
||||||
payload := splitStr[1]
|
payload := splitStr[1]
|
||||||
if tp == "NOT" || tp == "OR" || tp == "AND" {
|
if tp == "NOT" || tp == "OR" || tp == "AND" {
|
||||||
return parseRule(tp, payload, nil)
|
return parseRule(tp, payload, "", nil)
|
||||||
}
|
}
|
||||||
param := strings.Split(payload, ",")
|
param := strings.Split(payload, ",")
|
||||||
return parseRule(tp, param[0], param[1:])
|
return parseRule(tp, param[0], "", param[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRule(tp, payload string, params []string) (parsed C.Rule, parseErr error) {
|
func parseLogicSubRule(parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||||
switch tp {
|
return func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||||
case "AND":
|
switch tp {
|
||||||
parsed, parseErr = NewAND(payload, "")
|
case "MATCH":
|
||||||
case "OR":
|
return nil, fmt.Errorf("unsupported rule type on logic rule")
|
||||||
parsed, parseErr = NewOR(payload, "")
|
default:
|
||||||
case "NOT":
|
return parseRule(tp, payload, target, params)
|
||||||
parsed, parseErr = NewNOT(payload, "")
|
}
|
||||||
case "RULE-SET":
|
|
||||||
noResolve := RC.HasNoResolve(params)
|
|
||||||
parsed, parseErr = RP.NewRuleSet(payload, "", noResolve)
|
|
||||||
default:
|
|
||||||
parsed, parseErr = ruleparser.ParseSameRule(tp, payload, "", params)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, parseErr
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleExtra := &C.RuleExtra{
|
|
||||||
Network: RC.FindNetwork(params),
|
|
||||||
SourceIPs: RC.FindSourceIPs(params),
|
|
||||||
ProcessNames: RC.FindProcessName(params),
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.SetRuleExtra(ruleExtra)
|
|
||||||
|
|
||||||
return parsed, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Range struct {
|
type Range struct {
|
108
rules/logic/logic_test.go
Normal file
108
rules/logic/logic_test.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/constant"
|
||||||
|
RC "github.com/Dreamacro/clash/rules/common"
|
||||||
|
RP "github.com/Dreamacro/clash/rules/provider"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseRule(tp, payload, target string, params []string) (parsed constant.Rule, parseErr error) {
|
||||||
|
switch tp {
|
||||||
|
case "DOMAIN":
|
||||||
|
parsed = RC.NewDomain(payload, target)
|
||||||
|
case "DOMAIN-SUFFIX":
|
||||||
|
parsed = RC.NewDomainSuffix(payload, target)
|
||||||
|
case "DOMAIN-KEYWORD":
|
||||||
|
parsed = RC.NewDomainKeyword(payload, target)
|
||||||
|
case "GEOSITE":
|
||||||
|
parsed, parseErr = RC.NewGEOSITE(payload, target)
|
||||||
|
case "GEOIP":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve)
|
||||||
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve))
|
||||||
|
case "SRC-IP-CIDR":
|
||||||
|
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||||
|
case "IP-SUFFIX":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve)
|
||||||
|
case "SRC-IP-SUFFIX":
|
||||||
|
parsed, parseErr = RC.NewIPSuffix(payload, target, true, true)
|
||||||
|
case "SRC-PORT":
|
||||||
|
parsed, parseErr = RC.NewPort(payload, target, true)
|
||||||
|
case "DST-PORT":
|
||||||
|
parsed, parseErr = RC.NewPort(payload, target, false)
|
||||||
|
case "PROCESS-NAME":
|
||||||
|
parsed, parseErr = RC.NewProcess(payload, target, true)
|
||||||
|
case "PROCESS-PATH":
|
||||||
|
parsed, parseErr = RC.NewProcess(payload, target, false)
|
||||||
|
case "NETWORK":
|
||||||
|
parsed, parseErr = RC.NewNetworkType(payload, target)
|
||||||
|
case "UID":
|
||||||
|
parsed, parseErr = RC.NewUid(payload, target)
|
||||||
|
case "IN-TYPE":
|
||||||
|
parsed, parseErr = RC.NewInType(payload, target)
|
||||||
|
case "AND":
|
||||||
|
parsed, parseErr = NewAND(payload, target, ParseRule)
|
||||||
|
case "OR":
|
||||||
|
parsed, parseErr = NewOR(payload, target, ParseRule)
|
||||||
|
case "NOT":
|
||||||
|
parsed, parseErr = NewNOT(payload, target, ParseRule)
|
||||||
|
case "RULE-SET":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule)
|
||||||
|
case "MATCH":
|
||||||
|
parsed = RC.NewMatch(target)
|
||||||
|
parseErr = nil
|
||||||
|
default:
|
||||||
|
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAND(t *testing.T) {
|
||||||
|
and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "DIRECT", and.adapter)
|
||||||
|
assert.Equal(t, false, and.ShouldResolveIP())
|
||||||
|
assert.Equal(t, true, and.Match(&constant.Metadata{
|
||||||
|
Host: "baidu.com",
|
||||||
|
AddrType: constant.AtypDomainName,
|
||||||
|
NetWork: constant.TCP,
|
||||||
|
DstPort: "20000",
|
||||||
|
}))
|
||||||
|
|
||||||
|
and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
|
||||||
|
and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNOT(t *testing.T) {
|
||||||
|
not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT", ParseRule)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, false, not.Match(&constant.Metadata{
|
||||||
|
DstPort: "6100",
|
||||||
|
}))
|
||||||
|
|
||||||
|
_, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT", ParseRule)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
|
||||||
|
_, err = NewNOT("(())", "DIRECT", ParseRule)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOR(t *testing.T) {
|
||||||
|
or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, true, or.Match(&constant.Metadata{
|
||||||
|
NetWork: constant.TCP,
|
||||||
|
}))
|
||||||
|
assert.Equal(t, false, or.ShouldResolveIP())
|
||||||
|
}
|
@ -3,7 +3,7 @@ package logic
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/rule/common"
|
"github.com/Dreamacro/clash/rules/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NOT struct {
|
type NOT struct {
|
||||||
@ -17,9 +17,9 @@ func (not *NOT) ShouldFindProcess() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNOT(payload string, adapter string) (*NOT, error) {
|
func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*NOT, error) {
|
||||||
not := &NOT{Base: &common.Base{}, adapter: adapter}
|
not := &NOT{Base: &common.Base{}, adapter: adapter}
|
||||||
rule, err := parseRuleByPayload(payload)
|
rule, err := parseRuleByPayload(payload, parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ package logic
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/rule/common"
|
"github.com/Dreamacro/clash/rules/common"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,9 +45,9 @@ func (or *OR) ShouldResolveIP() bool {
|
|||||||
return or.needIP
|
return or.needIP
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOR(payload string, adapter string) (*OR, error) {
|
func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*OR, error) {
|
||||||
or := &OR{Base: &common.Base{}, payload: payload, adapter: adapter}
|
or := &OR{Base: &common.Base{}, payload: payload, adapter: adapter}
|
||||||
rules, err := parseRuleByPayload(payload)
|
rules, err := parseRuleByPayload(payload, parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -1,12 +1,14 @@
|
|||||||
package ruleparser
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
RC "github.com/Dreamacro/clash/rule/common"
|
RC "github.com/Dreamacro/clash/rules/common"
|
||||||
|
"github.com/Dreamacro/clash/rules/logic"
|
||||||
|
RP "github.com/Dreamacro/clash/rules/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseSameRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||||
switch tp {
|
switch tp {
|
||||||
case "DOMAIN":
|
case "DOMAIN":
|
||||||
parsed = RC.NewDomain(payload, target)
|
parsed = RC.NewDomain(payload, target)
|
||||||
@ -43,8 +45,33 @@ func ParseSameRule(tp, payload, target string, params []string) (parsed C.Rule,
|
|||||||
parsed, parseErr = RC.NewUid(payload, target)
|
parsed, parseErr = RC.NewUid(payload, target)
|
||||||
case "IN-TYPE":
|
case "IN-TYPE":
|
||||||
parsed, parseErr = RC.NewInType(payload, target)
|
parsed, parseErr = RC.NewInType(payload, target)
|
||||||
|
case "AND":
|
||||||
|
parsed, parseErr = logic.NewAND(payload, target, ParseRule)
|
||||||
|
case "OR":
|
||||||
|
parsed, parseErr = logic.NewOR(payload, target, ParseRule)
|
||||||
|
case "NOT":
|
||||||
|
parsed, parseErr = logic.NewNOT(payload, target, ParseRule)
|
||||||
|
case "RULE-SET":
|
||||||
|
noResolve := RC.HasNoResolve(params)
|
||||||
|
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule)
|
||||||
|
case "MATCH":
|
||||||
|
parsed = RC.NewMatch(target)
|
||||||
|
parseErr = nil
|
||||||
default:
|
default:
|
||||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parseErr != nil {
|
||||||
|
return nil, parseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleExtra := &C.RuleExtra{
|
||||||
|
Network: RC.FindNetwork(params),
|
||||||
|
SourceIPs: RC.FindSourceIPs(params),
|
||||||
|
ProcessNames: RC.FindProcessName(params),
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.SetRuleExtra(ruleExtra)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
@ -9,6 +10,7 @@ type classicalStrategy struct {
|
|||||||
rules []C.Rule
|
rules []C.Rule
|
||||||
count int
|
count int
|
||||||
shouldResolveIP bool
|
shouldResolveIP bool
|
||||||
|
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *classicalStrategy) Match(metadata *C.Metadata) bool {
|
func (c *classicalStrategy) Match(metadata *C.Metadata) bool {
|
||||||
@ -34,7 +36,7 @@ func (c *classicalStrategy) OnUpdate(rules []string) {
|
|||||||
shouldResolveIP := false
|
shouldResolveIP := false
|
||||||
for _, rawRule := range rules {
|
for _, rawRule := range rules {
|
||||||
ruleType, rule, params := ruleParse(rawRule)
|
ruleType, rule, params := ruleParse(rawRule)
|
||||||
r, err := parseRule(ruleType, rule, "", params)
|
r, err := c.parse(ruleType, rule, "", params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("parse rule error:[%s]", err.Error())
|
log.Warnln("parse rule error:[%s]", err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -50,6 +52,13 @@ func (c *classicalStrategy) OnUpdate(rules []string) {
|
|||||||
c.count = len(classicalRules)
|
c.count = len(classicalRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClassicalStrategy() *classicalStrategy {
|
func NewClassicalStrategy(parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) *classicalStrategy {
|
||||||
return &classicalStrategy{rules: []C.Rule{}}
|
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||||
|
switch tp {
|
||||||
|
case "MATCH":
|
||||||
|
return nil, fmt.Errorf("unsupported rule type on rule-set")
|
||||||
|
default:
|
||||||
|
return parse(tp, payload, target, params)
|
||||||
|
}
|
||||||
|
}}
|
||||||
}
|
}
|
@ -6,8 +6,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/structure"
|
"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"
|
P "github.com/Dreamacro/clash/constant/provider"
|
||||||
RC "github.com/Dreamacro/clash/rule/common"
|
|
||||||
"github.com/Dreamacro/clash/rule/ruleparser"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,7 +17,7 @@ type ruleProviderSchema struct {
|
|||||||
Interval int `provider:"interval,omitempty"`
|
Interval int `provider:"interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) {
|
func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) {
|
||||||
schema := &ruleProviderSchema{}
|
schema := &ruleProviderSchema{}
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||||
if err := decoder.Decode(mapping, schema); err != nil {
|
if err := decoder.Decode(mapping, schema); err != nil {
|
||||||
@ -49,19 +47,5 @@ func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvi
|
|||||||
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil
|
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil
|
||||||
}
|
|
||||||
|
|
||||||
func parseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
|
||||||
parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params)
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, parseErr
|
|
||||||
}
|
|
||||||
ruleExtra := &C.RuleExtra{
|
|
||||||
Network: RC.FindNetwork(params),
|
|
||||||
SourceIPs: RC.FindSourceIPs(params),
|
|
||||||
}
|
|
||||||
parsed.SetRuleExtra(ruleExtra)
|
|
||||||
return parsed, parseErr
|
|
||||||
}
|
}
|
@ -99,7 +99,8 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle) P.RuleProvider {
|
func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle,
|
||||||
|
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) P.RuleProvider {
|
||||||
rp := &ruleSetProvider{
|
rp := &ruleSetProvider{
|
||||||
behavior: behavior,
|
behavior: behavior,
|
||||||
}
|
}
|
||||||
@ -112,7 +113,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration
|
|||||||
|
|
||||||
fetcher := newFetcher(name, interval, vehicle, rulesParse, onUpdate)
|
fetcher := newFetcher(name, interval, vehicle, rulesParse, onUpdate)
|
||||||
rp.fetcher = fetcher
|
rp.fetcher = fetcher
|
||||||
rp.strategy = newStrategy(behavior)
|
rp.strategy = newStrategy(behavior, parse)
|
||||||
|
|
||||||
wrapper := &RuleSetProvider{
|
wrapper := &RuleSetProvider{
|
||||||
rp,
|
rp,
|
||||||
@ -123,7 +124,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration
|
|||||||
return wrapper
|
return wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStrategy(behavior P.RuleType) ruleStrategy {
|
func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ruleStrategy {
|
||||||
switch behavior {
|
switch behavior {
|
||||||
case P.Domain:
|
case P.Domain:
|
||||||
strategy := NewDomainStrategy()
|
strategy := NewDomainStrategy()
|
||||||
@ -132,7 +133,7 @@ func newStrategy(behavior P.RuleType) ruleStrategy {
|
|||||||
strategy := NewIPCidrStrategy()
|
strategy := NewIPCidrStrategy()
|
||||||
return strategy
|
return strategy
|
||||||
case P.Classical:
|
case P.Classical:
|
||||||
strategy := NewClassicalStrategy()
|
strategy := NewClassicalStrategy(parse)
|
||||||
return strategy
|
return strategy
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
P "github.com/Dreamacro/clash/constant/provider"
|
P "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/rule/common"
|
"github.com/Dreamacro/clash/rules/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleSet struct {
|
type RuleSet struct {
|
||||||
@ -47,7 +47,7 @@ func (rs *RuleSet) getProviders() P.RuleProvider {
|
|||||||
return rs.ruleProvider
|
return rs.ruleProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool) (*RuleSet, error) {
|
func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*RuleSet, error) {
|
||||||
rp, ok := RuleProviders()[ruleProviderName]
|
rp, ok := RuleProviders()[ruleProviderName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("rule set %s not found", ruleProviderName)
|
return nil, fmt.Errorf("rule set %s not found", ruleProviderName)
|
36
test/go.mod
36
test/go.mod
@ -3,20 +3,18 @@ module clash-test
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/clash v1.7.2-0.20211108085948-bd2ea2b917aa
|
github.com/Dreamacro/clash v0.0.0
|
||||||
github.com/docker/docker v20.10.13+incompatible
|
github.com/docker/docker v20.10.16+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/miekg/dns v1.1.48
|
github.com/miekg/dns v1.1.48
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/Dreamacro/clash => ../
|
replace github.com/Dreamacro/clash => ../
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
|
||||||
github.com/containerd/containerd v1.6.1 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
@ -26,8 +24,9 @@ require (
|
|||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd // indirect
|
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/oschwald/geoip2-golang v1.7.0 // indirect
|
github.com/oschwald/geoip2-golang v1.7.0 // indirect
|
||||||
@ -39,22 +38,21 @@ require (
|
|||||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||||
golang.org/x/tools v0.1.9 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
|
||||||
google.golang.org/grpc v1.45.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.1.0 // indirect
|
gotest.tools/v3 v3.1.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20220422224113-2cca6b79d9f4 // indirect
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 // indirect
|
||||||
)
|
)
|
||||||
|
1350
test/go.sum
1350
test/go.sum
File diff suppressed because it is too large
Load Diff
5
transport/shadowsocks/README.md
Normal file
5
transport/shadowsocks/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
## Embedded go-shadowsocks2
|
||||||
|
|
||||||
|
from https://github.com/Dreamacro/go-shadowsocks2
|
||||||
|
|
||||||
|
origin https://github.com/riobard/go-shadowsocks2
|
164
transport/shadowsocks/core/cipher.go
Normal file
164
transport/shadowsocks/core/cipher.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cipher interface {
|
||||||
|
StreamConnCipher
|
||||||
|
PacketConnCipher
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamConnCipher interface {
|
||||||
|
StreamConn(net.Conn) net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConnCipher interface {
|
||||||
|
PacketConn(net.PacketConn) net.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
|
||||||
|
var ErrCipherNotSupported = errors.New("cipher not supported")
|
||||||
|
|
||||||
|
const (
|
||||||
|
aeadAes128Gcm = "AEAD_AES_128_GCM"
|
||||||
|
aeadAes192Gcm = "AEAD_AES_192_GCM"
|
||||||
|
aeadAes256Gcm = "AEAD_AES_256_GCM"
|
||||||
|
aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305"
|
||||||
|
aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List of AEAD ciphers: key size in bytes and constructor
|
||||||
|
var aeadList = map[string]struct {
|
||||||
|
KeySize int
|
||||||
|
New func([]byte) (shadowaead.Cipher, error)
|
||||||
|
}{
|
||||||
|
aeadAes128Gcm: {16, shadowaead.AESGCM},
|
||||||
|
aeadAes192Gcm: {24, shadowaead.AESGCM},
|
||||||
|
aeadAes256Gcm: {32, shadowaead.AESGCM},
|
||||||
|
aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305},
|
||||||
|
aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305},
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of stream ciphers: key size in bytes and constructor
|
||||||
|
var streamList = map[string]struct {
|
||||||
|
KeySize int
|
||||||
|
New func(key []byte) (shadowstream.Cipher, error)
|
||||||
|
}{
|
||||||
|
"RC4-MD5": {16, shadowstream.RC4MD5},
|
||||||
|
"AES-128-CTR": {16, shadowstream.AESCTR},
|
||||||
|
"AES-192-CTR": {24, shadowstream.AESCTR},
|
||||||
|
"AES-256-CTR": {32, shadowstream.AESCTR},
|
||||||
|
"AES-128-CFB": {16, shadowstream.AESCFB},
|
||||||
|
"AES-192-CFB": {24, shadowstream.AESCFB},
|
||||||
|
"AES-256-CFB": {32, shadowstream.AESCFB},
|
||||||
|
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
|
||||||
|
"XCHACHA20": {32, shadowstream.Xchacha20},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCipher returns a list of available cipher names sorted alphabetically.
|
||||||
|
func ListCipher() []string {
|
||||||
|
var l []string
|
||||||
|
for k := range aeadList {
|
||||||
|
l = append(l, k)
|
||||||
|
}
|
||||||
|
for k := range streamList {
|
||||||
|
l = append(l, k)
|
||||||
|
}
|
||||||
|
sort.Strings(l)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
|
||||||
|
func PickCipher(name string, key []byte, password string) (Cipher, error) {
|
||||||
|
name = strings.ToUpper(name)
|
||||||
|
|
||||||
|
switch name {
|
||||||
|
case "DUMMY":
|
||||||
|
return &dummy{}, nil
|
||||||
|
case "CHACHA20-IETF-POLY1305":
|
||||||
|
name = aeadChacha20Poly1305
|
||||||
|
case "XCHACHA20-IETF-POLY1305":
|
||||||
|
name = aeadXChacha20Poly1305
|
||||||
|
case "AES-128-GCM":
|
||||||
|
name = aeadAes128Gcm
|
||||||
|
case "AES-192-GCM":
|
||||||
|
name = aeadAes192Gcm
|
||||||
|
case "AES-256-GCM":
|
||||||
|
name = aeadAes256Gcm
|
||||||
|
}
|
||||||
|
|
||||||
|
if choice, ok := aeadList[name]; ok {
|
||||||
|
if len(key) == 0 {
|
||||||
|
key = Kdf(password, choice.KeySize)
|
||||||
|
}
|
||||||
|
if len(key) != choice.KeySize {
|
||||||
|
return nil, shadowaead.KeySizeError(choice.KeySize)
|
||||||
|
}
|
||||||
|
aead, err := choice.New(key)
|
||||||
|
return &AeadCipher{Cipher: aead, Key: key}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if choice, ok := streamList[name]; ok {
|
||||||
|
if len(key) == 0 {
|
||||||
|
key = Kdf(password, choice.KeySize)
|
||||||
|
}
|
||||||
|
if len(key) != choice.KeySize {
|
||||||
|
return nil, shadowstream.KeySizeError(choice.KeySize)
|
||||||
|
}
|
||||||
|
ciph, err := choice.New(key)
|
||||||
|
return &StreamCipher{Cipher: ciph, Key: key}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrCipherNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
type AeadCipher struct {
|
||||||
|
shadowaead.Cipher
|
||||||
|
|
||||||
|
Key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aead *AeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
|
||||||
|
func (aead *AeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
||||||
|
return shadowaead.NewPacketConn(c, aead)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamCipher struct {
|
||||||
|
shadowstream.Cipher
|
||||||
|
|
||||||
|
Key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
|
||||||
|
func (ciph *StreamCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
||||||
|
return shadowstream.NewPacketConn(c, ciph)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummy cipher does not encrypt
|
||||||
|
|
||||||
|
type dummy struct{}
|
||||||
|
|
||||||
|
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
|
||||||
|
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
|
||||||
|
|
||||||
|
// key-derivation function from original Shadowsocks
|
||||||
|
func Kdf(password string, keyLen int) []byte {
|
||||||
|
var b, prev []byte
|
||||||
|
h := md5.New()
|
||||||
|
for len(b) < keyLen {
|
||||||
|
h.Write(prev)
|
||||||
|
h.Write([]byte(password))
|
||||||
|
b = h.Sum(b)
|
||||||
|
prev = b[len(b)-h.Size():]
|
||||||
|
h.Reset()
|
||||||
|
}
|
||||||
|
return b[:keyLen]
|
||||||
|
}
|
94
transport/shadowsocks/shadowaead/cipher.go
Normal file
94
transport/shadowsocks/shadowaead/cipher.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package shadowaead
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/sha1"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cipher interface {
|
||||||
|
KeySize() int
|
||||||
|
SaltSize() int
|
||||||
|
Encrypter(salt []byte) (cipher.AEAD, error)
|
||||||
|
Decrypter(salt []byte) (cipher.AEAD, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySizeError int
|
||||||
|
|
||||||
|
func (e KeySizeError) Error() string {
|
||||||
|
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func hkdfSHA1(secret, salt, info, outkey []byte) {
|
||||||
|
r := hkdf.New(sha1.New, secret, salt, info)
|
||||||
|
if _, err := io.ReadFull(r, outkey); err != nil {
|
||||||
|
panic(err) // should never happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type metaCipher struct {
|
||||||
|
psk []byte
|
||||||
|
makeAEAD func(key []byte) (cipher.AEAD, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *metaCipher) KeySize() int { return len(a.psk) }
|
||||||
|
func (a *metaCipher) SaltSize() int {
|
||||||
|
if ks := a.KeySize(); ks > 16 {
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
||||||
|
subkey := make([]byte, a.KeySize())
|
||||||
|
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
||||||
|
return a.makeAEAD(subkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
||||||
|
subkey := make([]byte, a.KeySize())
|
||||||
|
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
||||||
|
return a.makeAEAD(subkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func aesGCM(key []byte) (cipher.AEAD, error) {
|
||||||
|
blk, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cipher.NewGCM(blk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
|
||||||
|
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
|
||||||
|
func AESGCM(psk []byte) (Cipher, error) {
|
||||||
|
switch l := len(psk); l {
|
||||||
|
case 16, 24, 32: // AES 128/196/256
|
||||||
|
default:
|
||||||
|
return nil, aes.KeySizeError(l)
|
||||||
|
}
|
||||||
|
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
||||||
|
// must be 32.
|
||||||
|
func Chacha20Poly1305(psk []byte) (Cipher, error) {
|
||||||
|
if len(psk) != chacha20poly1305.KeySize {
|
||||||
|
return nil, KeySizeError(chacha20poly1305.KeySize)
|
||||||
|
}
|
||||||
|
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
||||||
|
// must be 32.
|
||||||
|
func XChacha20Poly1305(psk []byte) (Cipher, error) {
|
||||||
|
if len(psk) != chacha20poly1305.KeySize {
|
||||||
|
return nil, KeySizeError(chacha20poly1305.KeySize)
|
||||||
|
}
|
||||||
|
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
|
||||||
|
}
|
95
transport/shadowsocks/shadowaead/packet.go
Normal file
95
transport/shadowsocks/shadowaead/packet.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package shadowaead
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
|
||||||
|
var ErrShortPacket = errors.New("short packet")
|
||||||
|
|
||||||
|
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
|
||||||
|
|
||||||
|
// Pack encrypts plaintext using Cipher with a randomly generated salt and
|
||||||
|
// returns a slice of dst containing the encrypted packet and any error occurred.
|
||||||
|
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
|
||||||
|
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
|
||||||
|
saltSize := ciph.SaltSize()
|
||||||
|
salt := dst[:saltSize]
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aead, err := ciph.Encrypter(salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
|
||||||
|
return nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
|
||||||
|
return dst[:saltSize+len(b)], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
|
||||||
|
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
|
||||||
|
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
|
||||||
|
saltSize := ciph.SaltSize()
|
||||||
|
if len(pkt) < saltSize {
|
||||||
|
return nil, ErrShortPacket
|
||||||
|
}
|
||||||
|
salt := pkt[:saltSize]
|
||||||
|
aead, err := ciph.Decrypter(salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pkt) < saltSize+aead.Overhead() {
|
||||||
|
return nil, ErrShortPacket
|
||||||
|
}
|
||||||
|
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
|
||||||
|
return nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
Cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxPacketSize = 64 * 1024
|
||||||
|
|
||||||
|
// NewPacketConn wraps a net.PacketConn with cipher
|
||||||
|
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
|
||||||
|
return &PacketConn{PacketConn: c, Cipher: ciph}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo encrypts b and write to addr using the embedded PacketConn.
|
||||||
|
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
buf := pool.Get(maxPacketSize)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
buf, err := Pack(buf, b, c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||||
|
return len(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom reads from the embedded PacketConn and decrypts into b.
|
||||||
|
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return n, addr, err
|
||||||
|
}
|
||||||
|
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
|
||||||
|
if err != nil {
|
||||||
|
return n, addr, err
|
||||||
|
}
|
||||||
|
copy(b, bb)
|
||||||
|
return len(bb), addr, err
|
||||||
|
}
|
285
transport/shadowsocks/shadowaead/stream.go
Normal file
285
transport/shadowsocks/shadowaead/stream.go
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
package shadowaead
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// payloadSizeMask is the maximum size of payload in bytes.
|
||||||
|
payloadSizeMask = 0x3FFF // 16*1024 - 1
|
||||||
|
bufSize = 17 * 1024 // >= 2+aead.Overhead()+payloadSizeMask+aead.Overhead()
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrZeroChunk = errors.New("zero chunk")
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
io.Writer
|
||||||
|
cipher.AEAD
|
||||||
|
nonce [32]byte // should be sufficient for most nonce sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter wraps an io.Writer with authenticated encryption.
|
||||||
|
func NewWriter(w io.Writer, aead cipher.AEAD) *Writer { return &Writer{Writer: w, AEAD: aead} }
|
||||||
|
|
||||||
|
// Write encrypts p and writes to the embedded io.Writer.
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
buf := pool.Get(bufSize)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
nonce := w.nonce[:w.NonceSize()]
|
||||||
|
tag := w.Overhead()
|
||||||
|
off := 2 + tag
|
||||||
|
|
||||||
|
// compatible with snell
|
||||||
|
if len(p) == 0 {
|
||||||
|
buf = buf[:off]
|
||||||
|
buf[0], buf[1] = byte(0), byte(0)
|
||||||
|
w.Seal(buf[:0], nonce, buf[:2], nil)
|
||||||
|
increment(nonce)
|
||||||
|
_, err = w.Writer.Write(buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for nr := 0; n < len(p) && err == nil; n += nr {
|
||||||
|
nr = payloadSizeMask
|
||||||
|
if n+nr > len(p) {
|
||||||
|
nr = len(p) - n
|
||||||
|
}
|
||||||
|
buf = buf[:off+nr+tag]
|
||||||
|
buf[0], buf[1] = byte(nr>>8), byte(nr) // big-endian payload size
|
||||||
|
w.Seal(buf[:0], nonce, buf[:2], nil)
|
||||||
|
increment(nonce)
|
||||||
|
w.Seal(buf[:off], nonce, p[n:n+nr], nil)
|
||||||
|
increment(nonce)
|
||||||
|
_, err = w.Writer.Write(buf)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom reads from the given io.Reader until EOF or error, encrypts and
|
||||||
|
// writes to the embedded io.Writer. Returns number of bytes read from r and
|
||||||
|
// any error encountered.
|
||||||
|
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
buf := pool.Get(bufSize)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
nonce := w.nonce[:w.NonceSize()]
|
||||||
|
tag := w.Overhead()
|
||||||
|
off := 2 + tag
|
||||||
|
for {
|
||||||
|
nr, er := r.Read(buf[off : off+payloadSizeMask])
|
||||||
|
n += int64(nr)
|
||||||
|
buf[0], buf[1] = byte(nr>>8), byte(nr)
|
||||||
|
w.Seal(buf[:0], nonce, buf[:2], nil)
|
||||||
|
increment(nonce)
|
||||||
|
w.Seal(buf[:off], nonce, buf[off:off+nr], nil)
|
||||||
|
increment(nonce)
|
||||||
|
if _, ew := w.Writer.Write(buf[:off+nr+tag]); ew != nil {
|
||||||
|
err = ew
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||||
|
err = er
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
io.Reader
|
||||||
|
cipher.AEAD
|
||||||
|
nonce [32]byte // should be sufficient for most nonce sizes
|
||||||
|
buf []byte // to be put back into bufPool
|
||||||
|
off int // offset to unconsumed part of buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader wraps an io.Reader with authenticated decryption.
|
||||||
|
func NewReader(r io.Reader, aead cipher.AEAD) *Reader { return &Reader{Reader: r, AEAD: aead} }
|
||||||
|
|
||||||
|
// Read and decrypt a record into p. len(p) >= max payload size + AEAD overhead.
|
||||||
|
func (r *Reader) read(p []byte) (int, error) {
|
||||||
|
nonce := r.nonce[:r.NonceSize()]
|
||||||
|
tag := r.Overhead()
|
||||||
|
|
||||||
|
// decrypt payload size
|
||||||
|
p = p[:2+tag]
|
||||||
|
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err := r.Open(p[:0], nonce, p, nil)
|
||||||
|
increment(nonce)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt payload
|
||||||
|
size := (int(p[0])<<8 + int(p[1])) & payloadSizeMask
|
||||||
|
if size == 0 {
|
||||||
|
return 0, ErrZeroChunk
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p[:size+tag]
|
||||||
|
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err = r.Open(p[:0], nonce, p, nil)
|
||||||
|
increment(nonce)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the embedded io.Reader, decrypts and writes to p.
|
||||||
|
func (r *Reader) Read(p []byte) (int, error) {
|
||||||
|
if r.buf == nil {
|
||||||
|
if len(p) >= payloadSizeMask+r.Overhead() {
|
||||||
|
return r.read(p)
|
||||||
|
}
|
||||||
|
b := pool.Get(bufSize)
|
||||||
|
n, err := r.read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
r.buf = b[:n]
|
||||||
|
r.off = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
n := copy(p, r.buf[r.off:])
|
||||||
|
r.off += n
|
||||||
|
if r.off == len(r.buf) {
|
||||||
|
pool.Put(r.buf[:cap(r.buf)])
|
||||||
|
r.buf = nil
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo reads from the embedded io.Reader, decrypts and writes to w until
|
||||||
|
// there's no more data to write or when an error occurs. Return number of
|
||||||
|
// bytes written to w and any error encountered.
|
||||||
|
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if r.buf == nil {
|
||||||
|
r.buf = pool.Get(bufSize)
|
||||||
|
r.off = len(r.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
for r.off < len(r.buf) {
|
||||||
|
nw, ew := w.Write(r.buf[r.off:])
|
||||||
|
r.off += nw
|
||||||
|
n += int64(nw)
|
||||||
|
if ew != nil {
|
||||||
|
if r.off == len(r.buf) {
|
||||||
|
pool.Put(r.buf[:cap(r.buf)])
|
||||||
|
r.buf = nil
|
||||||
|
}
|
||||||
|
err = ew
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nr, er := r.read(r.buf)
|
||||||
|
if er != nil {
|
||||||
|
if er != io.EOF {
|
||||||
|
err = er
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.buf = r.buf[:nr]
|
||||||
|
r.off = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
|
||||||
|
func increment(b []byte) {
|
||||||
|
for i := range b {
|
||||||
|
b[i]++
|
||||||
|
if b[i] != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
Cipher
|
||||||
|
r *Reader
|
||||||
|
w *Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn wraps a stream-oriented net.Conn with cipher.
|
||||||
|
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
|
||||||
|
|
||||||
|
func (c *Conn) initReader() error {
|
||||||
|
salt := make([]byte, c.SaltSize())
|
||||||
|
if _, err := io.ReadFull(c.Conn, salt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
aead, err := c.Decrypter(salt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.r = NewReader(c.Conn, aead)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Read(b []byte) (int, error) {
|
||||||
|
if c.r == nil {
|
||||||
|
if err := c.initReader(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.r.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
if c.r == nil {
|
||||||
|
if err := c.initReader(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.r.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) initWriter() error {
|
||||||
|
salt := make([]byte, c.SaltSize())
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aead, err := c.Encrypter(salt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.Conn.Write(salt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.w = NewWriter(c.Conn, aead)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Write(b []byte) (int, error) {
|
||||||
|
if c.w == nil {
|
||||||
|
if err := c.initWriter(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
if c.w == nil {
|
||||||
|
if err := c.initWriter(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.w.ReadFrom(r)
|
||||||
|
}
|
116
transport/shadowsocks/shadowstream/cipher.go
Normal file
116
transport/shadowsocks/shadowstream/cipher.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package shadowstream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rc4"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cipher generates a pair of stream ciphers for encryption and decryption.
|
||||||
|
type Cipher interface {
|
||||||
|
IVSize() int
|
||||||
|
Encrypter(iv []byte) cipher.Stream
|
||||||
|
Decrypter(iv []byte) cipher.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySizeError int
|
||||||
|
|
||||||
|
func (e KeySizeError) Error() string {
|
||||||
|
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
||||||
|
}
|
||||||
|
|
||||||
|
// CTR mode
|
||||||
|
type ctrStream struct{ cipher.Block }
|
||||||
|
|
||||||
|
func (b *ctrStream) IVSize() int { return b.BlockSize() }
|
||||||
|
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
|
||||||
|
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
|
||||||
|
|
||||||
|
func AESCTR(key []byte) (Cipher, error) {
|
||||||
|
blk, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ctrStream{blk}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CFB mode
|
||||||
|
type cfbStream struct{ cipher.Block }
|
||||||
|
|
||||||
|
func (b *cfbStream) IVSize() int { return b.BlockSize() }
|
||||||
|
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
|
||||||
|
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
|
||||||
|
|
||||||
|
func AESCFB(key []byte) (Cipher, error) {
|
||||||
|
blk, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &cfbStream{blk}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IETF-variant of chacha20
|
||||||
|
type chacha20ietfkey []byte
|
||||||
|
|
||||||
|
func (k chacha20ietfkey) IVSize() int { return chacha20.NonceSize }
|
||||||
|
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||||
|
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
|
||||||
|
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // should never happen
|
||||||
|
}
|
||||||
|
return ciph
|
||||||
|
}
|
||||||
|
|
||||||
|
func Chacha20IETF(key []byte) (Cipher, error) {
|
||||||
|
if len(key) != chacha20.KeySize {
|
||||||
|
return nil, KeySizeError(chacha20.KeySize)
|
||||||
|
}
|
||||||
|
return chacha20ietfkey(key), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type xchacha20key []byte
|
||||||
|
|
||||||
|
func (k xchacha20key) IVSize() int { return chacha20.NonceSizeX }
|
||||||
|
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||||
|
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
|
||||||
|
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // should never happen
|
||||||
|
}
|
||||||
|
return ciph
|
||||||
|
}
|
||||||
|
|
||||||
|
func Xchacha20(key []byte) (Cipher, error) {
|
||||||
|
if len(key) != chacha20.KeySize {
|
||||||
|
return nil, KeySizeError(chacha20.KeySize)
|
||||||
|
}
|
||||||
|
return xchacha20key(key), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rc4Md5Key []byte
|
||||||
|
|
||||||
|
func (k rc4Md5Key) IVSize() int {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write([]byte(k))
|
||||||
|
h.Write(iv)
|
||||||
|
rc4key := h.Sum(nil)
|
||||||
|
c, _ := rc4.NewCipher(rc4key)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream {
|
||||||
|
return k.Encrypter(iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RC4MD5(key []byte) (Cipher, error) {
|
||||||
|
return rc4Md5Key(key), nil
|
||||||
|
}
|
79
transport/shadowsocks/shadowstream/packet.go
Normal file
79
transport/shadowsocks/shadowstream/packet.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package shadowstream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
|
||||||
|
var ErrShortPacket = errors.New("short packet")
|
||||||
|
|
||||||
|
// Pack encrypts plaintext using stream cipher s and a random IV.
|
||||||
|
// Returns a slice of dst containing random IV and ciphertext.
|
||||||
|
// Ensure len(dst) >= s.IVSize() + len(plaintext).
|
||||||
|
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
|
||||||
|
if len(dst) < s.IVSize()+len(plaintext) {
|
||||||
|
return nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
iv := dst[:s.IVSize()]
|
||||||
|
_, err := rand.Read(iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
|
||||||
|
return dst[:len(iv)+len(plaintext)], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack decrypts pkt using stream cipher s.
|
||||||
|
// Returns a slice of dst containing decrypted plaintext.
|
||||||
|
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
|
||||||
|
if len(pkt) < s.IVSize() {
|
||||||
|
return nil, ErrShortPacket
|
||||||
|
}
|
||||||
|
if len(dst) < len(pkt)-s.IVSize() {
|
||||||
|
return nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
iv := pkt[:s.IVSize()]
|
||||||
|
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
|
||||||
|
return dst[:len(pkt)-len(iv)], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
Cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
|
||||||
|
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
|
||||||
|
return &PacketConn{PacketConn: c, Cipher: ciph}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxPacketSize = 64 * 1024
|
||||||
|
|
||||||
|
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
buf := pool.Get(maxPacketSize)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
buf, err := Pack(buf, b, c.Cipher)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||||
|
return len(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return n, addr, err
|
||||||
|
}
|
||||||
|
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
|
||||||
|
if err != nil {
|
||||||
|
return n, addr, err
|
||||||
|
}
|
||||||
|
copy(b, bb)
|
||||||
|
return len(bb), addr, err
|
||||||
|
}
|
197
transport/shadowsocks/shadowstream/stream.go
Normal file
197
transport/shadowsocks/shadowstream/stream.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package shadowstream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bufSize = 2048
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
io.Writer
|
||||||
|
cipher.Stream
|
||||||
|
buf [bufSize]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter wraps an io.Writer with stream cipher encryption.
|
||||||
|
func NewWriter(w io.Writer, s cipher.Stream) *Writer { return &Writer{Writer: w, Stream: s} }
|
||||||
|
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
buf := w.buf[:]
|
||||||
|
for nw := 0; n < len(p) && err == nil; n += nw {
|
||||||
|
end := n + len(buf)
|
||||||
|
if end > len(p) {
|
||||||
|
end = len(p)
|
||||||
|
}
|
||||||
|
w.XORKeyStream(buf, p[n:end])
|
||||||
|
nw, err = w.Writer.Write(buf[:end-n])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
buf := w.buf[:]
|
||||||
|
for {
|
||||||
|
nr, er := r.Read(buf)
|
||||||
|
n += int64(nr)
|
||||||
|
b := buf[:nr]
|
||||||
|
w.XORKeyStream(b, b)
|
||||||
|
if _, err = w.Writer.Write(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||||
|
err = er
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
io.Reader
|
||||||
|
cipher.Stream
|
||||||
|
buf [bufSize]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader wraps an io.Reader with stream cipher decryption.
|
||||||
|
func NewReader(r io.Reader, s cipher.Stream) *Reader { return &Reader{Reader: r, Stream: s} }
|
||||||
|
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.Reader.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
r.XORKeyStream(p, p[:n])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
buf := r.buf[:]
|
||||||
|
for {
|
||||||
|
nr, er := r.Reader.Read(buf)
|
||||||
|
if nr > 0 {
|
||||||
|
r.XORKeyStream(buf, buf[:nr])
|
||||||
|
nw, ew := w.Write(buf[:nr])
|
||||||
|
n += int64(nw)
|
||||||
|
if ew != nil {
|
||||||
|
err = ew
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut)
|
||||||
|
err = er
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Conn represents a Shadowsocks connection. It implements the net.Conn interface.
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
Cipher
|
||||||
|
r *Reader
|
||||||
|
w *Writer
|
||||||
|
readIV []byte
|
||||||
|
writeIV []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
|
||||||
|
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
|
||||||
|
|
||||||
|
func (c *Conn) initReader() error {
|
||||||
|
if c.r == nil {
|
||||||
|
iv, err := c.ObtainReadIV()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.r = NewReader(c.Conn, c.Decrypter(iv))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Read(b []byte) (int, error) {
|
||||||
|
if c.r == nil {
|
||||||
|
if err := c.initReader(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.r.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
if c.r == nil {
|
||||||
|
if err := c.initReader(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.r.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) initWriter() error {
|
||||||
|
if c.w == nil {
|
||||||
|
iv, err := c.ObtainWriteIV()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := c.Conn.Write(iv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.w = NewWriter(c.Conn, c.Encrypter(iv))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Write(b []byte) (int, error) {
|
||||||
|
if c.w == nil {
|
||||||
|
if err := c.initWriter(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
if c.w == nil {
|
||||||
|
if err := c.initWriter(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.w.ReadFrom(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ObtainWriteIV() ([]byte, error) {
|
||||||
|
if len(c.writeIV) == c.IVSize() {
|
||||||
|
return c.writeIV, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := make([]byte, c.IVSize())
|
||||||
|
|
||||||
|
if _, err := rand.Read(iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.writeIV = iv
|
||||||
|
|
||||||
|
return iv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ObtainReadIV() ([]byte, error) {
|
||||||
|
if len(c.readIV) == c.IVSize() {
|
||||||
|
return c.readIV, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := make([]byte, c.IVSize())
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(c.Conn, iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.readIV = iv
|
||||||
|
|
||||||
|
return iv, nil
|
||||||
|
}
|
@ -4,7 +4,8 @@ import (
|
|||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
@ -6,8 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/pool"
|
"github.com/Dreamacro/clash/component/pool"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
|
@ -9,9 +9,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/shadowaead"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -13,9 +13,8 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||||
|
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -12,8 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
|
@ -433,5 +433,5 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxies["REJECT"], nil, nil
|
return proxies["DIRECT"], nil, nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user