Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
190c930294 | |||
d578ca788c | |||
9602d42d7d | |||
55c7c4edb3 | |||
20cb4d0643 | |||
91b4176557 | |||
f03f33840e | |||
8ff7e180a4 | |||
3827e00b54 | |||
c52e689d0d | |||
8e959bd245 | |||
298ca42369 | |||
ed9b9ce3c5 | |||
1298d2f8b6 | |||
6e84f685ce | |||
1ad87cfec9 | |||
582c0763ba | |||
9e9f459c0e | |||
fa3e0c726e | |||
3b038310ab | |||
6709936a8f | |||
04e5d02ab9 | |||
1af39cb228 | |||
c95735f083 | |||
fa2e6be05d | |||
4092a7c84b | |||
58d299c737 | |||
11ddac2b5f | |||
1f95c74f1e | |||
c9616f70b7 | |||
a197fbd4b5 | |||
708b8beadf | |||
0e1601e5b6 | |||
c7355510a2 | |||
1faa172944 | |||
7a8c98cd90 | |||
13e907bbd0 | |||
39e7832676 | |||
9b999e72ce | |||
e1a61503e4 | |||
9272d02149 | |||
067c02aba1 | |||
d0268bb9a2 | |||
fb4872ff7f | |||
2044458df9 | |||
d6df026550 | |||
7858ca6cc5 | |||
ac36473d13 | |||
72fb153fe0 | |||
527a602eba | |||
a71fd3b4df | |||
2ebc0383b5 | |||
7431001ed6 | |||
149b4b5b43 | |||
c0eb9aac1c | |||
79469fc8d6 | |||
948700eed6 | |||
3ab82849d4 | |||
0f43a19fdb | |||
cc1c1340a3 | |||
fe25ae83df | |||
c787bbe0e5 | |||
7aff9aac82 |
2
.github/workflows/prerelease.yml
vendored
2
.github/workflows/prerelease.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
env:
|
||||
NAME: Clash.Meta
|
||||
BINDIR: bin
|
||||
run: make -j releases
|
||||
run: make -j$(($(nproc) + 1)) releases
|
||||
|
||||
- name: Delete current release assets
|
||||
uses: andreaswilli/delete-release-assets-action@v2.0.0
|
||||
|
64
.github/workflows/product.yaml
vendored
Normal file
64
.github/workflows/product.yaml
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Get latest go version
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Test
|
||||
run: |
|
||||
go test ./...
|
||||
|
||||
- name: Build
|
||||
if: success()
|
||||
env:
|
||||
NAME: Clash.Meta
|
||||
BINDIR: bin
|
||||
run: make -j$(($(nproc) + 1)) linux-amd64v2 windows-amd64v2 darwin-arm64 linux-amd64v3 windows-amd64v3 linux-arm64
|
||||
|
||||
- name: Delete current release assets
|
||||
uses: andreaswilli/delete-release-assets-action@v2.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: test
|
||||
deleteOnlyFromDrafts: false
|
||||
|
||||
- name: Tag Repo
|
||||
uses: richardsimko/update-tag@v1
|
||||
with:
|
||||
tag_name: test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Alpha
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag: test
|
||||
tag_name: test
|
||||
files: bin/*
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
env:
|
||||
NAME: Clash.Meta
|
||||
BINDIR: bin
|
||||
run: make -j releases
|
||||
run: make -j$(($(nproc) + 1)) releases
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
6
Makefile
6
Makefile
@ -47,9 +47,9 @@ WINDOWS_ARCH_LIST = \
|
||||
windows-arm64 \
|
||||
windows-arm32v7
|
||||
|
||||
all:linux-amd64 linux-arm64\
|
||||
darwin-amd64 darwin-arm64\
|
||||
windows-amd64 windows-arm64\
|
||||
all:linux-amd64v3 linux-arm64\
|
||||
darwin-amd64v3 darwin-arm64\
|
||||
windows-amd64v3 windows-arm64\
|
||||
|
||||
docker:
|
||||
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@ -11,10 +14,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
// NewHTTPS receive CONNECT request and return ConnContext
|
||||
func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTPCONNECT
|
||||
metadata.Type = C.HTTPS
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"github.com/gofrs/uuid"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -50,6 +51,10 @@ func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
return c, errors.New("no support")
|
||||
}
|
||||
|
||||
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
return nil, errors.New("no support")
|
||||
@ -128,7 +133,12 @@ func NewBase(opt BaseOption) *Base {
|
||||
|
||||
type conn struct {
|
||||
net.Conn
|
||||
chain C.Chain
|
||||
chain C.Chain
|
||||
actualRemoteDestination string
|
||||
}
|
||||
|
||||
func (c *conn) RemoteDestination() string {
|
||||
return c.actualRemoteDestination
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
@ -142,12 +152,17 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
return &conn{c, []string{a.Name()}}
|
||||
return &conn{c, []string{a.Name()}, parseRemoteDestination(a.Addr())}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
chain C.Chain
|
||||
chain C.Chain
|
||||
actualRemoteDestination string
|
||||
}
|
||||
|
||||
func (c *packetConn) RemoteDestination() string {
|
||||
return c.actualRemoteDestination
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
@ -161,5 +176,17 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
return &packetConn{pc, []string{a.Name()}}
|
||||
return &packetConn{pc, []string{a.Name()}, parseRemoteDestination(a.Addr())}
|
||||
}
|
||||
|
||||
func parseRemoteDestination(addr string) string {
|
||||
if dst, _, err := net.SplitHostPort(addr); err == nil {
|
||||
return dst
|
||||
} else {
|
||||
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
|
||||
return dst
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,19 @@ package outboundgroup
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Fallback struct {
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
testUrl string
|
||||
selected string
|
||||
}
|
||||
|
||||
func (f *Fallback) Now() string {
|
||||
@ -75,13 +79,40 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
|
||||
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||
proxies := f.GetProxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Alive() {
|
||||
al := proxies[0]
|
||||
for i := len(proxies) - 1; i > -1; i-- {
|
||||
proxy := proxies[i]
|
||||
if proxy.Name() == f.selected && proxy.Alive() {
|
||||
return proxy
|
||||
}
|
||||
if proxy.Alive() {
|
||||
al = proxy
|
||||
}
|
||||
}
|
||||
return al
|
||||
}
|
||||
|
||||
func (f *Fallback) Set(name string) error {
|
||||
var p C.Proxy
|
||||
for _, proxy := range f.GetProxies(false) {
|
||||
if proxy.Name() == name {
|
||||
p = proxy
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return proxies[0]
|
||||
if p == nil {
|
||||
return errors.New("proxy not exist")
|
||||
}
|
||||
|
||||
f.selected = name
|
||||
if !p.Alive() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
|
||||
defer cancel()
|
||||
_, _ = p.URLTest(ctx, f.testUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
||||
@ -97,5 +128,6 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
|
||||
providers,
|
||||
}),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -105,6 +107,34 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
return proxies
|
||||
}
|
||||
|
||||
func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
|
||||
var wg sync.WaitGroup
|
||||
var lock sync.Mutex
|
||||
mp := map[string]uint16{}
|
||||
proxies := gb.GetProxies(false)
|
||||
for _, proxy := range proxies {
|
||||
proxy := proxy
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
delay, err := proxy.URLTest(ctx, url)
|
||||
lock.Lock()
|
||||
if err == nil {
|
||||
mp[proxy.Name()] = delay
|
||||
}
|
||||
lock.Unlock()
|
||||
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(mp) == 0 {
|
||||
return mp, fmt.Errorf("get delay: all proxies timeout")
|
||||
} else {
|
||||
return mp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GroupBase) onDialFailed() {
|
||||
if gb.failedTesting.Load() {
|
||||
return
|
||||
@ -116,14 +146,14 @@ func (gb *GroupBase) onDialFailed() {
|
||||
|
||||
gb.failedTimes++
|
||||
if gb.failedTimes == 1 {
|
||||
log.Warnln("ProxyGroup: %s first failed", gb.Name())
|
||||
log.Debugln("ProxyGroup: %s first failed", gb.Name())
|
||||
gb.failedTime = time.Now()
|
||||
} else {
|
||||
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
|
||||
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
|
||||
if gb.failedTimes >= gb.maxFailedTimes() {
|
||||
gb.failedTesting.Store(true)
|
||||
log.Warnln("because %s failed multiple times, active health check", gb.Name())
|
||||
|
@ -151,29 +151,35 @@ func strategyConsistentHashing() strategyFn {
|
||||
|
||||
func strategyStickySessions() strategyFn {
|
||||
ttl := time.Minute * 10
|
||||
|
||||
c := cache.New[uint64, int](1 * time.Second)
|
||||
maxRetry := 5
|
||||
lruCache := cache.NewLRUCache[uint64, int](
|
||||
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
||||
cache.WithSize[uint64, int](1000))
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||
length := len(proxies)
|
||||
idx, expireTime := c.GetWithExpire(key)
|
||||
if expireTime.IsZero() {
|
||||
idx = int(jumpHash(key+uint64(time.Now().UnixMilli()), int32(length)))
|
||||
idx, has := lruCache.Get(key)
|
||||
if !has {
|
||||
idx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
nowIdx := (idx + 1) % length
|
||||
nowIdx := idx
|
||||
for i := 1; i < maxRetry; i++ {
|
||||
proxy := proxies[nowIdx]
|
||||
if proxy.Alive() {
|
||||
if nowIdx != idx {
|
||||
c.Put(key, idx, -1)
|
||||
c.Put(key, nowIdx, ttl)
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, nowIdx)
|
||||
}
|
||||
|
||||
return proxy
|
||||
} else {
|
||||
nowIdx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
|
||||
}
|
||||
}
|
||||
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, 0)
|
||||
return proxies[0]
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,9 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
providersMap[groupName] = pd
|
||||
} else {
|
||||
if groupOption.URL == "" || groupOption.Interval == 0 {
|
||||
return nil, errMissHealthCheck
|
||||
//return nil, errMissHealthCheck
|
||||
groupOption.URL = "http://www.gstatic.com/generate_204"
|
||||
groupOption.Interval = 300
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||
|
@ -51,3 +51,7 @@ func tcpKeepAlive(c net.Conn) {
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type SelectAble interface {
|
||||
Set(string) error
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ type fetcher struct {
|
||||
done chan struct{}
|
||||
hash [16]byte
|
||||
parser parser
|
||||
interval time.Duration
|
||||
onUpdate func(any)
|
||||
}
|
||||
|
||||
@ -56,6 +57,12 @@ func (f *fetcher) Initial() (any, error) {
|
||||
modTime := stat.ModTime()
|
||||
f.updatedAt = &modTime
|
||||
isLocal = true
|
||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||
defer func() {
|
||||
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
|
||||
go f.update()
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
buf, err = f.vehicle.Read()
|
||||
}
|
||||
@ -137,21 +144,11 @@ func (f *fetcher) pullLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-f.ticker.C:
|
||||
elm, same, err := f.Update()
|
||||
if err != nil {
|
||||
log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||
same, err := f.update()
|
||||
if same || err != nil {
|
||||
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:
|
||||
f.ticker.Stop()
|
||||
return
|
||||
@ -159,6 +156,26 @@ 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 {
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
@ -184,5 +201,6 @@ func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, pars
|
||||
parser: parser,
|
||||
done: make(chan struct{}, 1),
|
||||
onUpdate: onUpdate,
|
||||
interval: interval,
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func (pp *proxySetProvider) Initial() error {
|
||||
|
||||
pp.onUpdate(elm)
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.process()
|
||||
defer func() { go pp.healthCheck.process() }()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -2,16 +2,12 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
netHttp "github.com/Dreamacro/clash/component/http"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
netHttp "github.com/Dreamacro/clash/common/net"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type FileVehicle struct {
|
||||
@ -50,52 +46,16 @@ func (h *HTTPVehicle) Path() string {
|
||||
func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
uri, err := url.Parse(h.url)
|
||||
resp, err := netHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, uri.String(), nil)
|
||||
req.Header.Set("User-Agent", netHttp.UA)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user := uri.User; user != nil {
|
||||
password, _ := user.Password()
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
transport := &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
conn := inner.HandleTcp(address, uri.Hostname())
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
|
||||
client := http.Client{Transport: transport}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
package net
|
||||
|
||||
const (
|
||||
UA = "Clash"
|
||||
)
|
@ -15,6 +15,7 @@ var (
|
||||
dialMux sync.Mutex
|
||||
actualSingleDialContext = singleDialContext
|
||||
actualDualStackDialContext = dualStackDialContext
|
||||
tcpConcurrent = false
|
||||
DisableIPv6 = false
|
||||
)
|
||||
|
||||
@ -76,17 +77,22 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
|
||||
func SetDial(concurrent bool) {
|
||||
dialMux.Lock()
|
||||
tcpConcurrent = concurrent
|
||||
if concurrent {
|
||||
actualSingleDialContext = concurrentSingleDialContext
|
||||
actualDualStackDialContext = concurrentDualStackDialContext
|
||||
} else {
|
||||
actualSingleDialContext = singleDialContext
|
||||
actualDualStackDialContext = concurrentDualStackDialContext
|
||||
actualDualStackDialContext = dualStackDialContext
|
||||
}
|
||||
|
||||
dialMux.Unlock()
|
||||
}
|
||||
|
||||
func GetDial() bool {
|
||||
return tcpConcurrent
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
dialer := &net.Dialer{}
|
||||
if opt.interfaceName != "" {
|
||||
|
@ -30,7 +30,7 @@ func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*route
|
||||
return nil, fmt.Errorf("empty listname in rule: %s", siteWithAttr)
|
||||
}
|
||||
|
||||
domains, err := l.LoadSite(file, list)
|
||||
domains, err := l.LoadSiteByPath(file, list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -59,7 +59,7 @@ func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*route
|
||||
}
|
||||
|
||||
func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
return l.LoadIP(C.GeoipName, country)
|
||||
return l.LoadIPByPath(C.GeoipName, country)
|
||||
}
|
||||
|
||||
var loaders map[string]func() LoaderImplementation
|
||||
|
@ -5,8 +5,10 @@ import (
|
||||
)
|
||||
|
||||
type LoaderImplementation interface {
|
||||
LoadSite(filename, list string) ([]*router.Domain, error)
|
||||
LoadIP(filename, country string) ([]*router.CIDR, error)
|
||||
LoadSiteByPath(filename, list string) ([]*router.Domain, error)
|
||||
LoadSiteByBytes(geositeBytes []byte, list string) ([]*router.Domain, error)
|
||||
LoadIPByPath(filename, country string) ([]*router.CIDR, error)
|
||||
LoadIPByBytes(geoipBytes []byte, country string) ([]*router.CIDR, error)
|
||||
}
|
||||
|
||||
type Loader interface {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package memconservative
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
@ -13,7 +14,7 @@ type memConservativeLoader struct {
|
||||
geositecache GeoSiteCache
|
||||
}
|
||||
|
||||
func (m *memConservativeLoader) LoadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
func (m *memConservativeLoader) LoadIPByPath(filename, country string) ([]*router.CIDR, error) {
|
||||
defer runtime.GC()
|
||||
geoip, err := m.geoipcache.Unmarshal(filename, country)
|
||||
if err != nil {
|
||||
@ -22,7 +23,11 @@ func (m *memConservativeLoader) LoadIP(filename, country string) ([]*router.CIDR
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
|
||||
func (m *memConservativeLoader) LoadSite(filename, list string) ([]*router.Domain, error) {
|
||||
func (m *memConservativeLoader) LoadIPByBytes(geoipBytes []byte, country string) ([]*router.CIDR, error) {
|
||||
return nil, errors.New("memConservative do not support LoadIPByBytes")
|
||||
}
|
||||
|
||||
func (m *memConservativeLoader) LoadSiteByPath(filename, list string) ([]*router.Domain, error) {
|
||||
defer runtime.GC()
|
||||
geosite, err := m.geositecache.Unmarshal(filename, list)
|
||||
if err != nil {
|
||||
@ -31,6 +36,10 @@ func (m *memConservativeLoader) LoadSite(filename, list string) ([]*router.Domai
|
||||
return geosite.Domain, nil
|
||||
}
|
||||
|
||||
func (m *memConservativeLoader) LoadSiteByBytes(geositeBytes []byte, list string) ([]*router.Domain, error) {
|
||||
return nil, errors.New("memConservative do not support LoadSiteByBytes")
|
||||
}
|
||||
|
||||
func newMemConservativeLoader() geodata.LoaderImplementation {
|
||||
return &memConservativeLoader{make(map[string]*router.GeoIP), make(map[string]*router.GeoSite)}
|
||||
}
|
||||
|
@ -29,11 +29,7 @@ func ReadAsset(file string) ([]byte, error) {
|
||||
return ReadFile(C.Path.GetAssetLocation(file))
|
||||
}
|
||||
|
||||
func loadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
geoipBytes, err := ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error())
|
||||
}
|
||||
func loadIP(geoipBytes []byte, country string) ([]*router.CIDR, error) {
|
||||
var geoipList router.GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
@ -45,14 +41,10 @@ func loadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("country not found in %s%s%s", filename, ": ", country)
|
||||
return nil, fmt.Errorf("country %s not found", country)
|
||||
}
|
||||
|
||||
func loadSite(filename, list string) ([]*router.Domain, error) {
|
||||
geositeBytes, err := ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error())
|
||||
}
|
||||
func loadSite(geositeBytes []byte, list string) ([]*router.Domain, error) {
|
||||
var geositeList router.GeoSiteList
|
||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||
return nil, err
|
||||
@ -64,17 +56,33 @@ func loadSite(filename, list string) ([]*router.Domain, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("list not found in %s%s%s", filename, ": ", list)
|
||||
return nil, fmt.Errorf("list %s not found", list)
|
||||
}
|
||||
|
||||
type standardLoader struct{}
|
||||
|
||||
func (d standardLoader) LoadSite(filename, list string) ([]*router.Domain, error) {
|
||||
return loadSite(filename, list)
|
||||
func (d standardLoader) LoadSiteByPath(filename, list string) ([]*router.Domain, error) {
|
||||
geositeBytes, err := ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error())
|
||||
}
|
||||
return loadSite(geositeBytes, list)
|
||||
}
|
||||
|
||||
func (d standardLoader) LoadIP(filename, country string) ([]*router.CIDR, error) {
|
||||
return loadIP(filename, country)
|
||||
func (d standardLoader) LoadSiteByBytes(geositeBytes []byte, list string) ([]*router.Domain, error) {
|
||||
return loadSite(geositeBytes, list)
|
||||
}
|
||||
|
||||
func (d standardLoader) LoadIPByPath(filename, country string) ([]*router.CIDR, error) {
|
||||
geoipBytes, err := ReadAsset(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %s, base error: %s", filename, err.Error())
|
||||
}
|
||||
return loadIP(geoipBytes, country)
|
||||
}
|
||||
|
||||
func (d standardLoader) LoadIPByBytes(geoipBytes []byte, country string) ([]*router.CIDR, error) {
|
||||
return loadIP(geoipBytes, country)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -21,16 +21,16 @@ func SetLoader(newLoader string) {
|
||||
geoLoaderName = newLoader
|
||||
}
|
||||
|
||||
func Verify(name string) bool {
|
||||
func Verify(name string) error {
|
||||
switch name {
|
||||
case C.GeositeName:
|
||||
_, _, err := LoadGeoSiteMatcher("CN")
|
||||
return err == nil
|
||||
return err
|
||||
case C.GeoipName:
|
||||
_, _, err := LoadGeoIPMatcher("CN")
|
||||
return err == nil
|
||||
return err
|
||||
default:
|
||||
return false
|
||||
return fmt.Errorf("not support name")
|
||||
}
|
||||
}
|
||||
|
||||
|
64
component/http/http.go
Normal file
64
component/http/http.go
Normal file
@ -0,0 +1,64 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
URL "net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
UA = "Clash"
|
||||
)
|
||||
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||
method = strings.ToUpper(method)
|
||||
urlRes, err := URL.Parse(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, urlRes.String(), body)
|
||||
for k, v := range header {
|
||||
for _, v := range v {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := header["User-Agent"]; !ok {
|
||||
req.Header.Set("User-Agent", UA)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user := urlRes.User; user != nil {
|
||||
password, _ := user.Password()
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
transport := &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
log.Infoln(urlRes.String())
|
||||
conn := inner.HandleTcp(address, urlRes.Hostname())
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
|
||||
client := http.Client{Transport: transport}
|
||||
return client.Do(req)
|
||||
|
||||
}
|
69
component/js/function.go
Normal file
69
component/js/function.go
Normal file
@ -0,0 +1,69 @@
|
||||
//go:build !no_script
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
runtime *goja.Runtime
|
||||
}
|
||||
|
||||
func (c *Context) Resolve(host string, dnsType C.DnsType) []string {
|
||||
var ips []string
|
||||
var ipAddrs []netip.Addr
|
||||
var err error
|
||||
switch dnsType {
|
||||
case C.IPv4:
|
||||
ipAddrs, err = resolver.ResolveAllIPv4(host)
|
||||
case C.IPv6:
|
||||
ipAddrs, err = resolver.ResolveAllIPv6(host)
|
||||
case C.All:
|
||||
ipAddrs, err = resolver.ResolveAllIP(host)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorln("Script resolve %s failed, error: %v", host, err)
|
||||
return ips
|
||||
}
|
||||
|
||||
for _, addr := range ipAddrs {
|
||||
ips = append(ips, addr.String())
|
||||
}
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func newContext() require.ModuleLoader {
|
||||
return func(runtime *goja.Runtime, object *goja.Object) {
|
||||
ctx := Context{
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
o := object.Get("exports").(*goja.Object)
|
||||
o.Set("resolve", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
return runtime.ToValue([]string{})
|
||||
}
|
||||
|
||||
host := call.Argument(0).String()
|
||||
dnsType := C.IPv4
|
||||
if len(call.Arguments) == 2 {
|
||||
dnsType = int(call.Argument(1).ToInteger())
|
||||
}
|
||||
|
||||
ips := ctx.Resolve(host, C.DnsType(dnsType))
|
||||
return runtime.ToValue(ips)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func enable(rt *goja.Runtime) {
|
||||
rt.Set("context", require.Require(rt, "context"))
|
||||
}
|
60
component/js/js.go
Normal file
60
component/js/js.go
Normal file
@ -0,0 +1,60 @@
|
||||
//go:build !no_script
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/eventloop"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logPrinter := console.RequireWithPrinter(&JsLog{})
|
||||
require.RegisterNativeModule("console", logPrinter)
|
||||
contextFuncLoader := newContext()
|
||||
require.RegisterNativeModule("context", contextFuncLoader)
|
||||
}
|
||||
|
||||
func preSetting(rt *goja.Runtime) {
|
||||
registry := new(require.Registry)
|
||||
registry.Enable(rt)
|
||||
|
||||
console.Enable(rt)
|
||||
enable(rt)
|
||||
eventloop.EnableConsole(true)
|
||||
}
|
||||
|
||||
func getLoop() *eventloop.EventLoop {
|
||||
loop := eventloop.NewEventLoop(func(loop *eventloop.EventLoop) {
|
||||
loop.Run(func(runtime *goja.Runtime) {
|
||||
preSetting(runtime)
|
||||
})
|
||||
})
|
||||
|
||||
return loop
|
||||
}
|
||||
|
||||
func compiler(name, code string) (*goja.Program, error) {
|
||||
return goja.Compile(name, code, false)
|
||||
}
|
||||
|
||||
func run(loop *eventloop.EventLoop, program *goja.Program, args map[string]any, callback func(any, error)) {
|
||||
loop.Run(func(runtime *goja.Runtime) {
|
||||
for k, v := range args {
|
||||
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
||||
err := runtime.Set(k, v)
|
||||
if err != nil {
|
||||
log.Errorln("Args to script failed, %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
v, err := runtime.RunProgram(program)
|
||||
if v == nil {
|
||||
callback(nil, err)
|
||||
} else {
|
||||
callback(v.Export(), err)
|
||||
}
|
||||
})
|
||||
}
|
20
component/js/log.go
Normal file
20
component/js/log.go
Normal file
@ -0,0 +1,20 @@
|
||||
//go:build !no_script
|
||||
|
||||
package js
|
||||
|
||||
import "github.com/Dreamacro/clash/log"
|
||||
|
||||
type JsLog struct {
|
||||
}
|
||||
|
||||
func (j JsLog) Log(s string) {
|
||||
log.Infoln("[JS] %s", s)
|
||||
}
|
||||
|
||||
func (j JsLog) Warn(s string) {
|
||||
log.Warnln("[JS] %s", s)
|
||||
}
|
||||
|
||||
func (j JsLog) Error(s string) {
|
||||
log.Errorln("[JS] %s", s)
|
||||
}
|
34
component/js/script.go
Normal file
34
component/js/script.go
Normal file
@ -0,0 +1,34 @@
|
||||
//go:build !no_script
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var JS sync.Map
|
||||
var mux sync.Mutex
|
||||
|
||||
func NewJS(name, code string) error {
|
||||
program, err := compiler(name, code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := JS.Load(name); !ok {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
if _, ok := JS.Load(name); !ok {
|
||||
JS.Store(name, program)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Run(name string, args map[string]any, callback func(any, error)) {
|
||||
if value, ok := JS.Load(name); ok {
|
||||
run(getLoop(), value.(*goja.Program), args, callback)
|
||||
}
|
||||
}
|
12
component/js/script_no_script.go
Normal file
12
component/js/script_no_script.go
Normal file
@ -0,0 +1,12 @@
|
||||
//go:build no_script
|
||||
|
||||
package js
|
||||
|
||||
import "fmt"
|
||||
|
||||
func NewJS(name, code string) error {
|
||||
fmt.Errorf("unsupported script on the build")
|
||||
}
|
||||
|
||||
func Run(name string, args map[string]any, callback func(any, error)) {
|
||||
}
|
@ -6,13 +6,14 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidNetwork = errors.New("invalid network")
|
||||
ErrPlatformNotSupport = errors.New("not support on this platform")
|
||||
ErrNotFound = errors.New("process not found")
|
||||
|
||||
enableFindProcess = true
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,7 +21,11 @@ const (
|
||||
UDP = "udp"
|
||||
)
|
||||
|
||||
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) {
|
||||
func EnableFindProcess(e bool) {
|
||||
enableFindProcess = e
|
||||
}
|
||||
|
||||
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (int32, string, error) {
|
||||
return findProcessName(network, srcIP, srcPort)
|
||||
}
|
||||
|
||||
@ -33,10 +38,9 @@ func FindUid(network string, srcIP netip.Addr, srcPort int) (int32, error) {
|
||||
}
|
||||
|
||||
func ShouldFindProcess(metadata *C.Metadata) bool {
|
||||
if runtime.GOOS == "android" {
|
||||
return false
|
||||
}
|
||||
if metadata.Process != "" || metadata.ProcessPath != "" {
|
||||
if !enableFindProcess ||
|
||||
metadata.Process != "" ||
|
||||
metadata.ProcessPath != "" {
|
||||
return false
|
||||
}
|
||||
for _, ip := range localIPs {
|
||||
|
@ -21,7 +21,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32,
|
||||
return 0, 0, ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
func findProcessName(network string, ip netip.Addr, port int) (int32, string, error) {
|
||||
var spath string
|
||||
switch network {
|
||||
case TCP:
|
||||
@ -29,14 +29,14 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
case UDP:
|
||||
spath = "net.inet.udp.pcblist_n"
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
return -1, "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
isIPv4 := ip.Is4()
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
@ -81,10 +81,11 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
|
||||
// xsocket_n.so_last_pid
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
return getExecPathFromPID(pid)
|
||||
pp, err := getExecPathFromPID(pid)
|
||||
return -1, pp, err
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
return -1, "", ErrNotFound
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
|
@ -25,7 +25,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32,
|
||||
return 0, 0, ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
|
||||
once.Do(func() {
|
||||
if err := initSearcher(); err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
@ -35,7 +35,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
|
||||
})
|
||||
|
||||
if defaultSearcher == nil {
|
||||
return "", ErrPlatformNotSupport
|
||||
return -1, "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
var spath string
|
||||
@ -46,21 +46,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
|
||||
case UDP:
|
||||
spath = "net.inet.udp.pcblist"
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
return -1, "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
return getExecPathFromPID(pid)
|
||||
pp, err := getExecPathFromPID(pid)
|
||||
return -1, pp, err
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode"
|
||||
@ -32,12 +34,13 @@ const (
|
||||
pathProc = "/proc"
|
||||
)
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
|
||||
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return -1, "", err
|
||||
}
|
||||
return resolveProcessNameByProcSearch(inode, uid)
|
||||
pp, err := resolveProcessNameByProcSearch(inode, uid)
|
||||
return uid, pp, err
|
||||
}
|
||||
|
||||
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
|
||||
@ -195,8 +198,19 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
return os.Readlink(path.Join(processPath, "exe"))
|
||||
if runtime.GOOS == "android" {
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
cmdline, err := os.ReadFile(path.Join(processPath, "cmdline"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return splitCmdline(cmdline), nil
|
||||
}
|
||||
} else {
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
return os.Readlink(path.Join(processPath, "exe"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,6 +218,19 @@ func resolveProcessNameByProcSearch(inode, uid int32) (string, error) {
|
||||
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
|
||||
}
|
||||
|
||||
func splitCmdline(cmdline []byte) string {
|
||||
cmdline = bytes.Trim(cmdline, " ")
|
||||
|
||||
idx := bytes.IndexFunc(cmdline, func(r rune) bool {
|
||||
return unicode.IsControl(r) || unicode.IsSpace(r)
|
||||
})
|
||||
|
||||
if idx == -1 {
|
||||
return filepath.Base(string(cmdline))
|
||||
}
|
||||
return filepath.Base(string(cmdline[:idx]))
|
||||
}
|
||||
|
||||
func isPid(s string) bool {
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return !unicode.IsDigit(r)
|
||||
|
@ -4,8 +4,8 @@ package process
|
||||
|
||||
import "net/netip"
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
|
||||
return "", ErrPlatformNotSupport
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
|
||||
return -1, "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
|
||||
|
@ -62,7 +62,7 @@ func initWin32API() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (int32, string, error) {
|
||||
once.Do(func() {
|
||||
err := initWin32API()
|
||||
if err != nil {
|
||||
@ -86,21 +86,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
|
||||
fn = getExUDPTable
|
||||
class = udpTablePid
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
return -1, "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
buf, err := getTransportTable(fn, family, class)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
s := newSearcher(family == windows.AF_INET, network == TCP)
|
||||
|
||||
pid, err := s.Search(buf, ip, uint16(srcPort))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return -1, "", err
|
||||
}
|
||||
return getExecPathFromPID(pid)
|
||||
pp, err := getExecPathFromPID(pid)
|
||||
return -1, pp, err
|
||||
}
|
||||
|
||||
type searcher struct {
|
||||
|
@ -89,24 +89,39 @@ func ResolveIP(host string) (netip.Addr, error) {
|
||||
// ResolveIPv4ProxyServerHost proxies server host only
|
||||
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
|
||||
if ProxyServerHostResolver != nil {
|
||||
return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
|
||||
if ip, err := ResolveIPv4WithResolver(host, ProxyServerHostResolver); err != nil {
|
||||
return ResolveIPv4(host)
|
||||
} else {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return ResolveIPv4(host)
|
||||
}
|
||||
|
||||
// ResolveIPv6ProxyServerHost proxies server host only
|
||||
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
|
||||
if ProxyServerHostResolver != nil {
|
||||
return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
|
||||
if ip, err := ResolveIPv6WithResolver(host, ProxyServerHostResolver); err != nil {
|
||||
return ResolveIPv6(host)
|
||||
} else {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return ResolveIPv6(host)
|
||||
}
|
||||
|
||||
// ResolveProxyServerHost proxies server host only
|
||||
func ResolveProxyServerHost(host string) (netip.Addr, error) {
|
||||
if ProxyServerHostResolver != nil {
|
||||
return ResolveIPWithResolver(host, ProxyServerHostResolver)
|
||||
if ip, err := ResolveIPWithResolver(host, ProxyServerHostResolver); err != nil {
|
||||
return ResolveIP(host)
|
||||
} else {
|
||||
return ip, err
|
||||
}
|
||||
}
|
||||
|
||||
return ResolveIP(host)
|
||||
}
|
||||
|
||||
|
@ -47,12 +47,14 @@ type General struct {
|
||||
UnifiedDelay bool
|
||||
LogLevel log.LogLevel `json:"log-level"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Interface string `json:"-"`
|
||||
Interface string `json:"interface-name"`
|
||||
RoutingMark int `json:"-"`
|
||||
GeodataMode bool `json:"geodata-mode"`
|
||||
GeodataLoader string `json:"geodata-loader"`
|
||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||
EnableProcess bool `json:"enable-process"`
|
||||
Tun Tun `json:"tun"`
|
||||
Sniffing bool `json:"sniffing"`
|
||||
}
|
||||
|
||||
// Inbound config
|
||||
@ -113,6 +115,7 @@ type Tun struct {
|
||||
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
|
||||
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||
TunAddressPrefix netip.Prefix `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// IPTables config
|
||||
@ -205,6 +208,7 @@ type RawConfig struct {
|
||||
GeodataMode bool `yaml:"geodata-mode"`
|
||||
GeodataLoader string `yaml:"geodata-loader"`
|
||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||
EnableProcess bool `yaml:"enable-process" json:"enable-process"`
|
||||
|
||||
Sniffer RawSniffer `yaml:"sniffer"`
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
@ -215,11 +219,18 @@ type RawConfig struct {
|
||||
IPTables IPTables `yaml:"iptables"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
GeoXUrl RawGeoXUrl `yaml:"geox-url"`
|
||||
Proxy []map[string]any `yaml:"proxies"`
|
||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
}
|
||||
|
||||
type RawGeoXUrl struct {
|
||||
GeoIp string `yaml:"geoip" json:"geoip"`
|
||||
Mmdb string `yaml:"mmdb" json:"mmdb"`
|
||||
GeoSite string `yaml:"geosite" json:"geosite"`
|
||||
}
|
||||
|
||||
type RawSniffer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Sniffing []string `yaml:"sniffing" json:"sniffing"`
|
||||
@ -254,6 +265,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Proxy: []map[string]any{},
|
||||
ProxyGroup: []map[string]any{},
|
||||
TCPConcurrent: false,
|
||||
EnableProcess: true,
|
||||
Tun: RawTun{
|
||||
Enable: false,
|
||||
Device: "",
|
||||
@ -304,6 +316,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
},
|
||||
GeoXUrl: RawGeoXUrl{
|
||||
GeoIp: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat",
|
||||
Mmdb: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
||||
GeoSite: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat",
|
||||
},
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
||||
@ -327,12 +344,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.General = general
|
||||
|
||||
tunCfg, err := parseTun(rawCfg.Tun, config.General)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Tun = tunCfg
|
||||
|
||||
dialer.DefaultInterface.Store(config.General.Interface)
|
||||
|
||||
proxies, providers, err := parseProxies(rawCfg)
|
||||
@ -361,6 +372,12 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.DNS = dnsCfg
|
||||
|
||||
tunCfg, err := parseTun(rawCfg.Tun, config.General, dnsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Tun = tunCfg
|
||||
|
||||
config.Users = parseAuthentication(rawCfg.Authentication)
|
||||
|
||||
config.Sniffer, err = parseSniffer(rawCfg.Sniffer)
|
||||
@ -409,6 +426,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
GeodataMode: cfg.GeodataMode,
|
||||
GeodataLoader: cfg.GeodataLoader,
|
||||
TCPConcurrent: cfg.TCPConcurrent,
|
||||
EnableProcess: cfg.EnableProcess,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -865,7 +883,7 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
|
||||
return users
|
||||
}
|
||||
|
||||
func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
||||
func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
|
||||
if rawTun.Enable && rawTun.AutoDetectInterface {
|
||||
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
@ -892,6 +910,13 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
||||
dnsHijack = append(dnsHijack, addrPort)
|
||||
}
|
||||
|
||||
var tunAddressPrefix netip.Prefix
|
||||
if dnsCfg.FakeIPRange != nil {
|
||||
tunAddressPrefix = *dnsCfg.FakeIPRange.IPNet()
|
||||
} else {
|
||||
tunAddressPrefix = netip.MustParsePrefix("198.18.0.1/16")
|
||||
}
|
||||
|
||||
return &Tun{
|
||||
Enable: rawTun.Enable,
|
||||
Device: rawTun.Device,
|
||||
@ -899,6 +924,7 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
||||
DNSHijack: dnsHijack,
|
||||
AutoRoute: rawTun.AutoRoute,
|
||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||
TunAddressPrefix: tunAddressPrefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
var initMode = true
|
||||
|
||||
func downloadMMDB(path string) (err error) {
|
||||
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/geoip/release/Country.mmdb")
|
||||
resp, err := http.Get(C.MmdbUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -32,7 +32,7 @@ func downloadMMDB(path string) (err error) {
|
||||
}
|
||||
|
||||
func downloadGeoIP(path string) (err error) {
|
||||
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat")
|
||||
resp, err := http.Get(C.GeoIpUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -49,7 +49,7 @@ func downloadGeoIP(path string) (err error) {
|
||||
}
|
||||
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
resp, err := http.Get("https://raw.githubusercontents.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat")
|
||||
resp, err := http.Get(C.GeoSiteUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -74,8 +74,8 @@ func initGeoSite() error {
|
||||
log.Infoln("Download GeoSite.dat finish")
|
||||
}
|
||||
if initMode {
|
||||
if !geodata.Verify(C.GeositeName) {
|
||||
log.Warnln("GeoSite.dat invalid, remove and download")
|
||||
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())
|
||||
}
|
||||
@ -97,8 +97,8 @@ func initGeoIP() error {
|
||||
log.Infoln("Download GeoIP.dat finish")
|
||||
}
|
||||
|
||||
if !geodata.Verify(C.GeoipName) {
|
||||
log.Warnln("GeoIP.dat invalid, remove and download")
|
||||
if err := geodata.Verify(C.GeoipName); err != nil {
|
||||
log.Warnln("GeoIP.dat invalid, remove and download: %s", err)
|
||||
if err := os.Remove(C.Path.GeoIP()); err != nil {
|
||||
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
|
||||
}
|
||||
@ -159,6 +159,9 @@ func Init(dir string) error {
|
||||
if !C.GeodataMode {
|
||||
C.GeodataMode = rawCfg.GeodataMode
|
||||
}
|
||||
C.GeoIpUrl = rawCfg.GeoXUrl.GeoIp
|
||||
C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite
|
||||
C.MmdbUrl = rawCfg.GeoXUrl.Mmdb
|
||||
// initial GeoIP
|
||||
if err := initGeoIP(); err != nil {
|
||||
return fmt.Errorf("can't initial GeoIP: %w", err)
|
||||
|
80
config/updateGeo.go
Normal file
80
config/updateGeo.go
Normal file
@ -0,0 +1,80 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func UpdateGeoDatabases() error {
|
||||
defer runtime.GC()
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if C.GeodataMode {
|
||||
data, err := downloadForBytes(C.GeoIpUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download GeoIP database file: %w", err)
|
||||
}
|
||||
|
||||
if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil {
|
||||
return fmt.Errorf("invalid GeoIP database file: %s", err)
|
||||
}
|
||||
|
||||
if saveFile(data, C.Path.GeoIP()) != nil {
|
||||
return fmt.Errorf("can't save GeoIP database file: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
data, err := downloadForBytes(C.MmdbUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download MMDB database file: %w", err)
|
||||
}
|
||||
|
||||
instance, err := geoip2.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MMDB database file: %s", err)
|
||||
}
|
||||
_ = instance.Close()
|
||||
|
||||
if saveFile(data, C.Path.MMDB()) != nil {
|
||||
return fmt.Errorf("can't save MMDB database file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := downloadForBytes(C.GeoSiteUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download GeoSite database file: %w", err)
|
||||
}
|
||||
|
||||
if _, err = geoLoader.LoadSiteByBytes(data, "cn"); err != nil {
|
||||
return fmt.Errorf("invalid GeoSite database file: %s", err)
|
||||
}
|
||||
|
||||
if saveFile(data, C.Path.GeoSite()) != nil {
|
||||
return fmt.Errorf("can't save GeoSite database file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func saveFile(bytes []byte, path string) error {
|
||||
return ioutil.WriteFile(path, bytes, 0o644)
|
||||
}
|
@ -41,6 +41,7 @@ const (
|
||||
type Connection interface {
|
||||
Chains() Chain
|
||||
AppendToChains(adapter ProxyAdapter)
|
||||
RemoteDestination() string
|
||||
}
|
||||
|
||||
type Chain []string
|
||||
@ -107,6 +108,11 @@ type ProxyAdapter interface {
|
||||
Unwrap(metadata *Metadata) Proxy
|
||||
}
|
||||
|
||||
type Group interface {
|
||||
URLTest(ctx context.Context, url string) (mp map[string]uint16, err error)
|
||||
GetProxies(touch bool) []Proxy
|
||||
}
|
||||
|
||||
type DelayHistory struct {
|
||||
Time time.Time `json:"time"`
|
||||
Delay uint16 `json:"delay"`
|
||||
|
7
constant/features/no_doq.go
Normal file
7
constant/features/no_doq.go
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build no_doq
|
||||
|
||||
package features
|
||||
|
||||
func init() {
|
||||
TAGS = append(TAGS, "no_doq")
|
||||
}
|
7
constant/features/no_gvisor.go
Normal file
7
constant/features/no_gvisor.go
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package features
|
||||
|
||||
func init() {
|
||||
TAGS = append(TAGS, "no_gvisor")
|
||||
}
|
3
constant/features/tags.go
Normal file
3
constant/features/tags.go
Normal file
@ -0,0 +1,3 @@
|
||||
package features
|
||||
|
||||
var TAGS = make([]string, 0, 0)
|
@ -1,3 +1,8 @@
|
||||
package constant
|
||||
|
||||
var GeodataMode bool
|
||||
var (
|
||||
GeodataMode bool
|
||||
GeoIpUrl string
|
||||
MmdbUrl string
|
||||
GeoSiteUrl string
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ const (
|
||||
ALLNet
|
||||
|
||||
HTTP Type = iota
|
||||
HTTPCONNECT
|
||||
HTTPS
|
||||
SOCKS4
|
||||
SOCKS5
|
||||
REDIR
|
||||
@ -49,8 +49,8 @@ func (t Type) String() string {
|
||||
switch t {
|
||||
case HTTP:
|
||||
return "HTTP"
|
||||
case HTTPCONNECT:
|
||||
return "HTTP Connect"
|
||||
case HTTPS:
|
||||
return "HTTPS"
|
||||
case SOCKS4:
|
||||
return "Socks4"
|
||||
case SOCKS5:
|
||||
@ -68,6 +68,31 @@ func (t Type) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func ParseType(t string) (*Type, error) {
|
||||
var res Type
|
||||
switch t {
|
||||
case "HTTP":
|
||||
res = HTTP
|
||||
case "HTTPS":
|
||||
res = HTTPS
|
||||
case "SOCKS4":
|
||||
res = SOCKS4
|
||||
case "SOCKS5":
|
||||
res = SOCKS5
|
||||
case "REDIR":
|
||||
res = REDIR
|
||||
case "TPROXY":
|
||||
res = TPROXY
|
||||
case "TUN":
|
||||
res = TUN
|
||||
case "INNER":
|
||||
res = INNER
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %s", t)
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (t Type) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.String())
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
// Vehicle Type
|
||||
@ -65,10 +65,10 @@ type Provider interface {
|
||||
// ProxyProvider interface
|
||||
type ProxyProvider interface {
|
||||
Provider
|
||||
Proxies() []constant.Proxy
|
||||
Proxies() []C.Proxy
|
||||
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
||||
// Commonly used in DialContext and DialPacketConn
|
||||
ProxiesWithTouch() []constant.Proxy
|
||||
ProxiesWithTouch() []C.Proxy
|
||||
HealthCheck()
|
||||
Version() uint
|
||||
}
|
||||
@ -100,7 +100,7 @@ func (rt RuleType) String() string {
|
||||
type RuleProvider interface {
|
||||
Provider
|
||||
Behavior() RuleType
|
||||
Match(*constant.Metadata) bool
|
||||
Match(*C.Metadata) bool
|
||||
ShouldResolveIP() bool
|
||||
AsRule(adaptor string) constant.Rule
|
||||
AsRule(adaptor string) C.Rule
|
||||
}
|
||||
|
@ -9,13 +9,17 @@ const (
|
||||
GEOIP
|
||||
IPCIDR
|
||||
SrcIPCIDR
|
||||
IPSuffix
|
||||
SrcIPSuffix
|
||||
SrcPort
|
||||
DstPort
|
||||
Process
|
||||
Script
|
||||
ProcessPath
|
||||
RuleSet
|
||||
Network
|
||||
Uid
|
||||
INTYPE
|
||||
MATCH
|
||||
AND
|
||||
OR
|
||||
@ -40,6 +44,10 @@ func (rt RuleType) String() string {
|
||||
return "IPCIDR"
|
||||
case SrcIPCIDR:
|
||||
return "SrcIPCIDR"
|
||||
case IPSuffix:
|
||||
return "IPSuffix"
|
||||
case SrcIPSuffix:
|
||||
return "SrcIPSuffix"
|
||||
case SrcPort:
|
||||
return "SrcPort"
|
||||
case DstPort:
|
||||
@ -54,8 +62,12 @@ func (rt RuleType) String() string {
|
||||
return "RuleSet"
|
||||
case Network:
|
||||
return "Network"
|
||||
case Script:
|
||||
return "Script"
|
||||
case Uid:
|
||||
return "Uid"
|
||||
case INTYPE:
|
||||
return "InType"
|
||||
case AND:
|
||||
return "AND"
|
||||
case OR:
|
||||
|
27
constant/script.go
Normal file
27
constant/script.go
Normal file
@ -0,0 +1,27 @@
|
||||
package constant
|
||||
|
||||
type JSRuleMetadata struct {
|
||||
Type string `json:"type"`
|
||||
Network string `json:"network"`
|
||||
Host string `json:"host"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
DstIP string `json:"dstIP"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DstPort string `json:"dstPort"`
|
||||
Uid *int32 `json:"uid"`
|
||||
Process string `json:"process"`
|
||||
ProcessPath string `json:"processPath"`
|
||||
}
|
||||
|
||||
type DnsType int
|
||||
|
||||
const (
|
||||
IPv4 = 1 << iota
|
||||
IPv6
|
||||
All
|
||||
)
|
||||
|
||||
type JSFunction interface {
|
||||
//Resolve host to ip by Clash DNS
|
||||
Resolve(host string, resolveType DnsType) []string
|
||||
}
|
@ -82,12 +82,16 @@ func (r *Resolver) ResolveAllIP(host string) (ips []netip.Addr, err error) {
|
||||
|
||||
ips, err = r.resolveIP(host, D.TypeA)
|
||||
|
||||
ipv6s, open := <-ch
|
||||
if !open && err != nil {
|
||||
return nil, resolver.ErrIPNotFound
|
||||
select {
|
||||
case ipv6s, open := <-ch:
|
||||
if !open && err != nil {
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
ips = append(ips, ipv6s...)
|
||||
case <-time.After(1 * time.Millisecond):
|
||||
// wait ipv6 result
|
||||
}
|
||||
|
||||
ips = append(ips, ipv6s...)
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
|
10
go.mod
10
go.mod
@ -4,7 +4,9 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8
|
||||
github.com/dlclark/regexp2 v1.4.0
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91
|
||||
github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
@ -16,6 +18,7 @@ require (
|
||||
github.com/oschwald/geoip2-golang v1.7.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/vishvananda/netlink v1.2.0-beta.0.20220404152918-5e915e014938
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/atomic v1.9.0
|
||||
@ -37,9 +40,9 @@ require (
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
||||
@ -48,6 +51,7 @@ require (
|
||||
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
@ -57,4 +61,4 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
replace golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 => github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e
|
||||
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
@ -10,8 +10,8 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
|
||||
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/wintun-go v0.0.0-20220319102620-bbc5e6b2015e h1:GRfT5Lf8HP7RNczKIwTYLoCh1PPuIs/sY9hj+W+3deg=
|
||||
github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e/go.mod h1:ARUuShAtcziEJ/vnZ2hgoP+zc0J7Ukcca2S/NPDoQCc=
|
||||
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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
@ -20,11 +20,18 @@ github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitf
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||
github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41 h1:yRPjAkkuR/E/tsVG7QmhzEeEtD3P2yllxsT1/ftURb0=
|
||||
github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d h1:W1n4DvpzZGOISgp7wWNtraLcHtnmnTwBlJidqtMIuwQ=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
@ -42,6 +49,8 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||
@ -100,12 +109,14 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucas-clemente/quic-go v0.27.0 h1:v6WY87q9zD4dKASbG8hy/LpzAVNzEQzw8sEIeloJsc4=
|
||||
github.com/lucas-clemente/quic-go v0.27.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
@ -159,6 +170,8 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
@ -202,6 +215,9 @@ github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIz
|
||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -289,9 +305,11 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -313,6 +331,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -338,6 +357,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
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/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 h1:SEYkJAIuYAsSAPkCffOiYLtq5brBDSI+L0mRjSsvSTY=
|
||||
@ -369,8 +390,10 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
@ -2,6 +2,7 @@ package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/process"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"net/netip"
|
||||
"os"
|
||||
@ -85,7 +86,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
loadRuleProvider(cfg.RuleProviders)
|
||||
updateGeneral(cfg.General, force)
|
||||
updateIPTables(cfg)
|
||||
updateTun(cfg.Tun, cfg.DNS)
|
||||
updateTun(cfg.Tun)
|
||||
updateExperimental(cfg)
|
||||
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
@ -118,6 +119,9 @@ func GetGeneral() *config.General {
|
||||
IPv6: !resolver.DisableIPv6,
|
||||
GeodataLoader: G.LoaderName(),
|
||||
Tun: P.GetTunConf(),
|
||||
Interface: dialer.DefaultInterface.Load(),
|
||||
Sniffing: tunnel.IsSniffing(),
|
||||
TCPConcurrent: dialer.GetDial(),
|
||||
}
|
||||
|
||||
return general
|
||||
@ -244,12 +248,8 @@ func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func updateTun(tun *config.Tun, dns *config.DNS) {
|
||||
var tunAddressPrefix *netip.Prefix
|
||||
if dns.FakeIPRange != nil {
|
||||
tunAddressPrefix = dns.FakeIPRange.IPNet()
|
||||
}
|
||||
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
func updateTun(tun *config.Tun) {
|
||||
P.ReCreateTun(tun, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
}
|
||||
|
||||
func updateSniffer(sniffer *config.Sniffer) {
|
||||
@ -274,6 +274,7 @@ func updateSniffer(sniffer *config.Sniffer) {
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
log.SetLevel(general.LogLevel)
|
||||
process.EnableFindProcess(general.EnableProcess)
|
||||
tunnel.SetMode(general.Mode)
|
||||
dialer.DisableIPv6 = !general.IPv6
|
||||
if !dialer.DisableIPv6 {
|
||||
@ -353,7 +354,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
||||
continue
|
||||
}
|
||||
|
||||
selector, ok := outbound.ProxyAdapter.(*outboundgroup.Selector)
|
||||
selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@ -428,7 +429,7 @@ func updateIPTables(cfg *config.Config) {
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
P.Cleanup()
|
||||
P.Cleanup(false)
|
||||
tproxy.CleanupTProxyIPTables()
|
||||
resolver.StoreFakePoolState()
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
@ -16,26 +18,34 @@ import (
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var (
|
||||
updateGeoMux sync.Mutex
|
||||
updatingGeo = false
|
||||
)
|
||||
|
||||
func configRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getConfigs)
|
||||
r.Put("/", updateConfigs)
|
||||
r.Post("/geo", updateGeoDatabases)
|
||||
r.Patch("/", patchConfigs)
|
||||
return r
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
Tun *config.Tun `json:"tun"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
Tun *config.Tun `json:"tun"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Sniffing *bool `json:"sniffing"`
|
||||
TcpConcurrent *bool `json:"tcp-concurrent"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -67,6 +77,14 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
P.SetBindAddress(*general.BindAddress)
|
||||
}
|
||||
|
||||
if general.Sniffing != nil {
|
||||
tunnel.SetSniffing(*general.Sniffing)
|
||||
}
|
||||
|
||||
if general.TcpConcurrent != nil {
|
||||
dialer.SetDial(*general.TcpConcurrent)
|
||||
}
|
||||
|
||||
ports := P.GetPorts()
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
@ -138,3 +156,42 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
executor.ApplyConfig(cfg, force)
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
|
||||
updateGeoMux.Lock()
|
||||
|
||||
if updatingGeo {
|
||||
updateGeoMux.Unlock()
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("updating..."))
|
||||
return
|
||||
}
|
||||
|
||||
updatingGeo = true
|
||||
updateGeoMux.Unlock()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
updatingGeo = false
|
||||
}()
|
||||
|
||||
log.Warnln("[REST-API] updating GEO databases...")
|
||||
|
||||
if err := config.UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := executor.ParseWithPath(constant.Path.Config())
|
||||
if err != nil {
|
||||
log.Errorln("[REST-API] update GEO databases failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnln("[REST-API] update GEO databases successful, apply config...")
|
||||
|
||||
executor.ApplyConfig(cfg, false)
|
||||
}()
|
||||
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
79
hub/route/groups.go
Normal file
79
hub/route/groups.go
Normal file
@ -0,0 +1,79 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GroupRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getGroups)
|
||||
|
||||
r.Route("/{name}", func(r chi.Router) {
|
||||
r.Use(parseProxyName, findProxyByName)
|
||||
r.Get("/", getGroup)
|
||||
r.Get("/delay", getGroupDelay)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func getGroups(w http.ResponseWriter, r *http.Request) {
|
||||
var gs []C.Proxy
|
||||
for _, p := range tunnel.Proxies() {
|
||||
if _, ok := p.(*adapter.Proxy).ProxyAdapter.(C.Group); ok {
|
||||
gs = append(gs, p)
|
||||
}
|
||||
}
|
||||
render.JSON(w, r, render.M{
|
||||
"proxies": gs,
|
||||
})
|
||||
}
|
||||
|
||||
func getGroup(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
if _, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group); ok {
|
||||
render.JSON(w, r, proxy)
|
||||
return
|
||||
}
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
}
|
||||
|
||||
func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
group, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
dm, err := group.URLTest(ctx, url)
|
||||
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
render.JSON(w, r, dm)
|
||||
}
|
@ -83,7 +83,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
|
||||
selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector)
|
||||
selector, ok := proxy.ProxyAdapter.(outboundgroup.SelectAble)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("Must be a Selector"))
|
||||
|
@ -20,7 +20,7 @@ type Rule struct {
|
||||
Type string `json:"type"`
|
||||
Payload string `json:"payload"`
|
||||
Proxy string `json:"proxy"`
|
||||
Size int `json:"Size"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -68,6 +68,7 @@ func Start(addr string, secret string) {
|
||||
r.Get("/version", version)
|
||||
r.Mount("/configs", configRouter())
|
||||
r.Mount("/proxies", proxyRouter())
|
||||
r.Mount("/group", GroupRouter())
|
||||
r.Mount("/rules", ruleRouter())
|
||||
r.Mount("/connections", connectionRouter())
|
||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||
|
@ -2,11 +2,9 @@ package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
@ -25,10 +23,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
allowLan = false
|
||||
bindAddress = "*"
|
||||
lastTunConf *config.Tun
|
||||
lastTunAddressPrefix *netip.Prefix
|
||||
allowLan = false
|
||||
bindAddress = "*"
|
||||
lastTunConf *config.Tun
|
||||
|
||||
socksListener *socks.Listener
|
||||
socksUDPListener *socks.UDPListener
|
||||
@ -327,7 +324,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
||||
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||
}
|
||||
|
||||
func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
tunMux.Lock()
|
||||
defer tunMux.Unlock()
|
||||
|
||||
@ -335,35 +332,23 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorln("Start TUN listening error: %s", err.Error())
|
||||
Cleanup(false)
|
||||
}
|
||||
}()
|
||||
|
||||
if tunAddressPrefix == nil {
|
||||
tunAddressPrefix = lastTunAddressPrefix
|
||||
}
|
||||
|
||||
if !hasTunConfigChange(tunConf, tunAddressPrefix) {
|
||||
if !hasTunConfigChange(tunConf) {
|
||||
return
|
||||
}
|
||||
|
||||
if tunStackListener != nil {
|
||||
_ = tunStackListener.Close()
|
||||
tunStackListener = nil
|
||||
lastTunConf = nil
|
||||
lastTunAddressPrefix = nil
|
||||
}
|
||||
Cleanup(true)
|
||||
|
||||
if !tunConf.Enable {
|
||||
return
|
||||
}
|
||||
|
||||
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn)
|
||||
if err != nil {
|
||||
log.Warnln("Failed to start TUN interface: %s", err)
|
||||
}
|
||||
tunStackListener, err = tun.New(tunConf, tcpIn, udpIn)
|
||||
|
||||
lastTunConf = tunConf
|
||||
lastTunAddressPrefix = tunAddressPrefix
|
||||
}
|
||||
|
||||
// GetPorts return the ports of proxy servers
|
||||
@ -422,7 +407,7 @@ func genAddr(host string, port int, allowLan bool) string {
|
||||
return fmt.Sprintf("127.0.0.1:%d", port)
|
||||
}
|
||||
|
||||
func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) bool {
|
||||
func hasTunConfigChange(tunConf *config.Tun) bool {
|
||||
if lastTunConf == nil {
|
||||
return true
|
||||
}
|
||||
@ -448,29 +433,29 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
|
||||
if lastTunConf.Enable != tunConf.Enable ||
|
||||
lastTunConf.Device != tunConf.Device ||
|
||||
lastTunConf.Stack != tunConf.Stack ||
|
||||
lastTunConf.AutoRoute != tunConf.AutoRoute {
|
||||
lastTunConf.AutoRoute != tunConf.AutoRoute ||
|
||||
lastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface {
|
||||
return true
|
||||
}
|
||||
|
||||
if (tunAddressPrefix != nil && lastTunAddressPrefix == nil) || (tunAddressPrefix == nil && lastTunAddressPrefix != nil) {
|
||||
return true
|
||||
}
|
||||
|
||||
if tunAddressPrefix != nil && lastTunAddressPrefix != nil && *tunAddressPrefix != *lastTunAddressPrefix {
|
||||
if tunConf.TunAddressPrefix.String() != lastTunConf.TunAddressPrefix.String() {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func Cleanup() {
|
||||
func Cleanup(wait bool) {
|
||||
if tunStackListener != nil {
|
||||
_ = tunStackListener.Close()
|
||||
if runtime.GOOS == "android" {
|
||||
prefs := []int{9000, 9001, 9002, 9003, 9004}
|
||||
for _, pref := range prefs {
|
||||
_, _ = cmd.ExecCmd(fmt.Sprintf("ip rule del pref %d", pref))
|
||||
}
|
||||
commons.StopDefaultInterfaceChangeMonitor()
|
||||
|
||||
if wait {
|
||||
commons.WaitForTunClose(lastTunConf.Device)
|
||||
}
|
||||
|
||||
commons.CleanupRule()
|
||||
}
|
||||
tunStackListener = nil
|
||||
lastTunConf = nil
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
|
29
listener/tun/device/device_no_gvisor.go
Normal file
29
listener/tun/device/device_no_gvisor.go
Normal file
@ -0,0 +1,29 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package device
|
||||
|
||||
// Device is the interface that implemented by network layer devices (e.g. tun),
|
||||
// and easy to use as stack.LinkEndpoint.
|
||||
type Device interface {
|
||||
|
||||
// Name returns the current name of the device.
|
||||
Name() string
|
||||
|
||||
// Type returns the driver type of the device.
|
||||
Type() string
|
||||
|
||||
// Read packets from tun device
|
||||
Read(packet []byte) (int, error)
|
||||
|
||||
// Write packets to tun device
|
||||
Write(packet []byte) (int, error)
|
||||
|
||||
// Close stops and closes the device.
|
||||
Close() error
|
||||
|
||||
// UseEndpoint work for gVisor stack
|
||||
UseEndpoint() error
|
||||
|
||||
// UseIOBased work for other ip stack
|
||||
UseIOBased() error
|
||||
}
|
@ -4,24 +4,13 @@ package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type FD struct {
|
||||
stack.LinkEndpoint
|
||||
|
||||
fd int
|
||||
mtu uint32
|
||||
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func Open(name string, mtu uint32) (device.Device, error) {
|
||||
fd, err := strconv.Atoi(name)
|
||||
if err != nil {
|
||||
|
17
listener/tun/device/fdbased/fd_unix_gvisor.go
Normal file
17
listener/tun/device/fdbased/fd_unix_gvisor.go
Normal file
@ -0,0 +1,17 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"os"
|
||||
)
|
||||
|
||||
type FD struct {
|
||||
stack.LinkEndpoint
|
||||
|
||||
fd int
|
||||
mtu uint32
|
||||
|
||||
file *os.File
|
||||
}
|
14
listener/tun/device/fdbased/fd_unix_no_gvisor.go
Normal file
14
listener/tun/device/fdbased/fd_unix_no_gvisor.go
Normal file
@ -0,0 +1,14 @@
|
||||
//go:build no_gvisor
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type FD struct {
|
||||
fd int
|
||||
mtu uint32
|
||||
|
||||
file *os.File
|
||||
}
|
@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||
)
|
||||
|
||||
func open(fd int, mtu uint32) (device.Device, error) {
|
||||
@ -17,12 +16,7 @@ func open(fd int, mtu uint32) (device.Device, error) {
|
||||
}
|
||||
|
||||
func (f *FD) useEndpoint() error {
|
||||
ep, err := iobased.New(os.NewFile(uintptr(f.fd), f.Name()), f.mtu, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
f.LinkEndpoint = ep
|
||||
return nil
|
||||
return newEp(f)
|
||||
}
|
||||
|
||||
func (f *FD) useIOBased() error {
|
||||
|
19
listener/tun/device/fdbased/open_others_gvisor.go
Normal file
19
listener/tun/device/fdbased/open_others_gvisor.go
Normal file
@ -0,0 +1,19 @@
|
||||
//go:build !no_gvisor && !linux && !windows
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||
)
|
||||
|
||||
func newEp(f *FD) error {
|
||||
ep, err := iobased.New(os.NewFile(uintptr(f.fd), f.Name()), f.mtu, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
f.LinkEndpoint = ep
|
||||
return nil
|
||||
}
|
11
listener/tun/device/fdbased/open_others_no_gvisor.go
Normal file
11
listener/tun/device/fdbased/open_others_no_gvisor.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build no_gvisor && !linux && !windows
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func newEp(f *FD) error {
|
||||
return fmt.Errorf("unsupported gvisor on the build")
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
// Package iobased provides the implementation of io.ReadWriter
|
||||
// based data-link layer endpoints.
|
||||
package iobased
|
||||
|
1
listener/tun/device/iobased/iobased.go
Normal file
1
listener/tun/device/iobased/iobased.go
Normal file
@ -0,0 +1 @@
|
||||
package iobased
|
233
listener/tun/device/tun/driver/dll_windows.go
Normal file
233
listener/tun/device/tun/driver/dll_windows.go
Normal file
@ -0,0 +1,233 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
||||
)
|
||||
|
||||
//go:linkname modwintun golang.zx2c4.com/wintun.modwintun
|
||||
|
||||
//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter
|
||||
|
||||
//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter
|
||||
|
||||
//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter
|
||||
|
||||
//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver
|
||||
|
||||
//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID
|
||||
|
||||
//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion
|
||||
|
||||
//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket
|
||||
|
||||
//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession
|
||||
|
||||
//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent
|
||||
|
||||
//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket
|
||||
|
||||
//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket
|
||||
|
||||
//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket
|
||||
|
||||
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
|
||||
|
||||
var (
|
||||
modwintun *lazyDLL
|
||||
procWintunCreateAdapter *lazyProc
|
||||
procWintunOpenAdapter *lazyProc
|
||||
procWintunCloseAdapter *lazyProc
|
||||
procWintunDeleteDriver *lazyProc
|
||||
procWintunGetAdapterLUID *lazyProc
|
||||
procWintunGetRunningDriverVersion *lazyProc
|
||||
procWintunAllocateSendPacket *lazyProc
|
||||
procWintunEndSession *lazyProc
|
||||
procWintunGetReadWaitEvent *lazyProc
|
||||
procWintunReceivePacket *lazyProc
|
||||
procWintunReleaseReceivePacket *lazyProc
|
||||
procWintunSendPacket *lazyProc
|
||||
procWintunStartSession *lazyProc
|
||||
)
|
||||
|
||||
type loggerLevel int
|
||||
|
||||
const (
|
||||
logInfo loggerLevel = iota
|
||||
logWarn
|
||||
logErr
|
||||
)
|
||||
|
||||
func init() {
|
||||
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
||||
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
||||
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
||||
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
|
||||
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
|
||||
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
|
||||
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
|
||||
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
|
||||
procWintunEndSession = modwintun.NewProc("WintunEndSession")
|
||||
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
|
||||
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
|
||||
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
||||
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
||||
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
||||
}
|
||||
|
||||
func InitWintun() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("init wintun.dll error: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = modwintun.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
procWintunCreateAdapter.Addr()
|
||||
procWintunOpenAdapter.Addr()
|
||||
procWintunCloseAdapter.Addr()
|
||||
procWintunDeleteDriver.Addr()
|
||||
procWintunGetAdapterLUID.Addr()
|
||||
procWintunGetRunningDriverVersion.Addr()
|
||||
procWintunAllocateSendPacket.Addr()
|
||||
procWintunEndSession.Addr()
|
||||
procWintunGetReadWaitEvent.Addr()
|
||||
procWintunReceivePacket.Addr()
|
||||
procWintunReleaseReceivePacket.Addr()
|
||||
procWintunSendPacket.Addr()
|
||||
procWintunStartSession.Addr()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
||||
}
|
||||
|
||||
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
|
||||
switch level {
|
||||
case logInfo:
|
||||
log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
case logWarn:
|
||||
log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
case logErr:
|
||||
log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
default:
|
||||
log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func setupLogger(dll *lazyDLL) {
|
||||
var callback uintptr
|
||||
if runtime.GOARCH == "386" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int {
|
||||
return logMessage(level, 0, msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "arm" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int {
|
||||
return logMessage(level, 0, msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
||||
callback = windows.NewCallback(logMessage)
|
||||
}
|
||||
_, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
||||
return &lazyProc{dll: d, Name: name}
|
||||
}
|
||||
|
||||
type lazyProc struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
dll *lazyDLL
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Find() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.addr != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.dll.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err)
|
||||
}
|
||||
addr, err := p.nameToAddr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting %s address: %w", p.Name, err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) Addr() uintptr {
|
||||
err := p.Find()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Load() error {
|
||||
return p.dll.Load()
|
||||
}
|
||||
|
||||
type lazyDLL struct {
|
||||
Name string
|
||||
Base windows.Handle
|
||||
mu sync.Mutex
|
||||
module *memmod.Module
|
||||
onLoad func(d *lazyDLL)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) Load() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.module != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
module, err := memmod.LoadLibrary(dllContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load library: %w", err)
|
||||
}
|
||||
d.Base = windows.Handle(module.BaseAddr())
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||
if d.onLoad != nil {
|
||||
d.onLoad(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||
return p.dll.module.ProcAddressByName(p.Name)
|
||||
}
|
13
listener/tun/device/tun/driver/dll_windows_386.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_386.go
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed x86/wintun.dll
|
||||
var dllContent []byte
|
13
listener/tun/device/tun/driver/dll_windows_amd64.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_amd64.go
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed amd64/wintun.dll
|
||||
var dllContent []byte
|
13
listener/tun/device/tun/driver/dll_windows_arm.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_arm.go
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm/wintun.dll
|
||||
var dllContent []byte
|
13
listener/tun/device/tun/driver/dll_windows_arm64.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_arm64.go
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm64/wintun.dll
|
||||
var dllContent []byte
|
5
listener/tun/device/tun/wintun/package_info.go → listener/tun/device/tun/driver/package_info.go
Executable file → Normal file
5
listener/tun/device/tun/wintun/package_info.go → listener/tun/device/tun/driver/package_info.go
Executable file → Normal file
@ -1,11 +1,10 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Modified from: https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
||||
// https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
package driver
|
@ -3,28 +3,15 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type TUN struct {
|
||||
*iobased.Endpoint
|
||||
|
||||
nt *tun.NativeTun
|
||||
mtu uint32
|
||||
name string
|
||||
offset int
|
||||
|
||||
cache []byte
|
||||
}
|
||||
|
||||
func Open(name string, mtu uint32) (_ device.Device, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -43,11 +30,11 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
|
||||
forcedMTU = int(t.mtu)
|
||||
}
|
||||
|
||||
nt, err := tun.CreateTUN(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||
nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||
|
||||
// retry if abnormal exit on Windows at last time
|
||||
if err != nil && runtime.GOOS == "windows" && errors.Is(err, os.ErrExist) {
|
||||
nt, err = tun.CreateTUN(t.name, forcedMTU)
|
||||
// retry if abnormal exit at last time on Windows
|
||||
if err != nil && runtime.GOOS == "windows" && os.IsExist(err) {
|
||||
nt, err = newDevice(t.name, forcedMTU)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -92,11 +79,7 @@ func (t *TUN) Write(packet []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
defer func(ep *iobased.Endpoint) {
|
||||
if ep != nil {
|
||||
ep.Close()
|
||||
}
|
||||
}(t.Endpoint)
|
||||
defer closeIO(t)
|
||||
return t.nt.Close()
|
||||
}
|
||||
|
||||
@ -106,12 +89,7 @@ func (t *TUN) Name() string {
|
||||
}
|
||||
|
||||
func (t *TUN) UseEndpoint() error {
|
||||
ep, err := iobased.New(t, t.mtu, t.offset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
t.Endpoint = ep
|
||||
return nil
|
||||
return newEq(t)
|
||||
}
|
||||
|
||||
func (t *TUN) UseIOBased() error {
|
||||
|
34
listener/tun/device/tun/tun_wireguard_gvisor.go
Normal file
34
listener/tun/device/tun/tun_wireguard_gvisor.go
Normal file
@ -0,0 +1,34 @@
|
||||
//go:build !linux && !no_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/listener/tun/device/iobased"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type TUN struct {
|
||||
*iobased.Endpoint
|
||||
nt *tun.NativeTun
|
||||
mtu uint32
|
||||
name string
|
||||
offset int
|
||||
|
||||
cache []byte
|
||||
}
|
||||
|
||||
func closeIO(t *TUN) {
|
||||
if t.Endpoint != nil {
|
||||
t.Endpoint.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func newEq(t *TUN) error {
|
||||
ep, err := iobased.New(t, t.mtu, t.offset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
t.Endpoint = ep
|
||||
return nil
|
||||
}
|
24
listener/tun/device/tun/tun_wireguard_no_gvisor.go
Normal file
24
listener/tun/device/tun/tun_wireguard_no_gvisor.go
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build !linux && no_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
type TUN struct {
|
||||
nt *tun.NativeTun
|
||||
mtu uint32
|
||||
name string
|
||||
offset int
|
||||
|
||||
cache []byte
|
||||
}
|
||||
|
||||
func closeIO(t *TUN) {
|
||||
|
||||
}
|
||||
|
||||
func newEq(t *TUN) error {
|
||||
return nil
|
||||
}
|
@ -2,7 +2,13 @@
|
||||
|
||||
package tun
|
||||
|
||||
import "golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
const (
|
||||
offset = 4 /* 4 bytes TUN_PI */
|
||||
defaultMTU = 1500
|
||||
)
|
||||
|
||||
func newDevice(name string, mtu int) (tun.Device, error) {
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/listener/tun/device/tun/driver"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
@ -20,3 +22,11 @@ func init() {
|
||||
func (t *TUN) LUID() uint64 {
|
||||
return t.nt.LUID()
|
||||
}
|
||||
|
||||
func newDevice(name string, mtu int) (nt tun.Device, err error) {
|
||||
if err = driver.InitWintun(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
||||
|
@ -1,94 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.zx2c4.com/wintun/embed_dll"
|
||||
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
||||
}
|
||||
|
||||
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
||||
return &lazyProc{dll: d, Name: name}
|
||||
}
|
||||
|
||||
type lazyProc struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
dll *lazyDLL
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Find() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.addr != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.dll.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading %v DLL: %w", p.dll.Name, err)
|
||||
}
|
||||
addr, err := p.nameToAddr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting %v address: %w", p.Name, err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) Addr() uintptr {
|
||||
err := p.Find()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p.addr
|
||||
}
|
||||
|
||||
type lazyDLL struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
module *memmod.Module
|
||||
onLoad func(d *lazyDLL)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) Load() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.module != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
module, err := memmod.LoadLibrary(embed_dll.DDlContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load library: %w", err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||
if d.onLoad != nil {
|
||||
d.onLoad(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||
return p.dll.module.ProcAddressByName(p.Name)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package embed_dll
|
||||
|
||||
// Copyright 2020 MeshStep Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed x86/wintun.dll
|
||||
var DDlContent []byte
|
@ -1,21 +0,0 @@
|
||||
package embed_dll
|
||||
|
||||
// Copyright 2020 MeshStep Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed amd64/wintun.dll
|
||||
var DDlContent []byte
|
@ -1,21 +0,0 @@
|
||||
package embed_dll
|
||||
|
||||
// Copyright 2020 MeshStep Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm/wintun.dll
|
||||
var DDlContent []byte
|
@ -1,21 +0,0 @@
|
||||
package embed_dll
|
||||
|
||||
// Copyright 2020 MeshStep Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed arm64/wintun.dll
|
||||
var DDlContent []byte
|
@ -1,8 +0,0 @@
|
||||
module golang.zx2c4.com/wintun
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
)
|
@ -1,17 +0,0 @@
|
||||
github.com/MetaCubeX/Clash.Meta v1.9.1 h1:jHZhVRBxFuaCRBN9vxB/FL5R16wY4kIgNqjszdXPeLs=
|
||||
github.com/MetaCubeX/Clash.Meta v1.9.1/go.mod h1:/I4cSh+PcgmtS5SEnFp8RANL6aVRd3i9YOult+mKLhU=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf h1:Fm4IcnUL803i92qDlmB0obyHmosDrxZWxJL3gIeNqOw=
|
||||
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
@ -1,90 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
handle uintptr
|
||||
}
|
||||
|
||||
const (
|
||||
PacketSizeMax = 0xffff // Maximum packet size
|
||||
RingCapacityMin = 0x20000 // Minimum ring capacity (128 kiB)
|
||||
RingCapacityMax = 0x4000000 // Maximum ring capacity (64 MiB)
|
||||
)
|
||||
|
||||
// Packet with data
|
||||
type Packet struct {
|
||||
Next *Packet // Pointer to next packet in queue
|
||||
Size uint32 // Size of packet (max WINTUN_MAX_IP_PACKET_SIZE)
|
||||
Data *[PacketSizeMax]byte // Pointer to layer 3 IPv4 or IPv6 packet
|
||||
}
|
||||
|
||||
var (
|
||||
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
|
||||
procWintunEndSession = modwintun.NewProc("WintunEndSession")
|
||||
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
|
||||
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
|
||||
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
||||
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
||||
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
||||
)
|
||||
|
||||
func (wintun *Adapter) StartSession(capacity uint32) (session Session, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procWintunStartSession.Addr(), 2, uintptr(wintun.handle), uintptr(capacity), 0)
|
||||
if r0 == 0 {
|
||||
err = e1
|
||||
} else {
|
||||
session = Session{r0}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (session Session) End() {
|
||||
syscall.Syscall(procWintunEndSession.Addr(), 1, session.handle, 0, 0)
|
||||
session.handle = 0
|
||||
}
|
||||
|
||||
func (session Session) ReadWaitEvent() (handle windows.Handle) {
|
||||
r0, _, _ := syscall.Syscall(procWintunGetReadWaitEvent.Addr(), 1, session.handle, 0, 0)
|
||||
handle = windows.Handle(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func (session Session) ReceivePacket() (packet []byte, err error) {
|
||||
var packetSize uint32
|
||||
r0, _, e1 := syscall.Syscall(procWintunReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packetSize)), 0)
|
||||
if r0 == 0 {
|
||||
err = e1
|
||||
return
|
||||
}
|
||||
packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize)
|
||||
return
|
||||
}
|
||||
|
||||
func (session Session) ReleaseReceivePacket(packet []byte) {
|
||||
syscall.Syscall(procWintunReleaseReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0)
|
||||
}
|
||||
|
||||
func (session Session) AllocateSendPacket(packetSize int) (packet []byte, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procWintunAllocateSendPacket.Addr(), 2, session.handle, uintptr(packetSize), 0)
|
||||
if r0 == 0 {
|
||||
err = e1
|
||||
return
|
||||
}
|
||||
packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize)
|
||||
return
|
||||
}
|
||||
|
||||
func (session Session) SendPacket(packet []byte) {
|
||||
syscall.Syscall(procWintunSendPacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0)
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type loggerLevel int
|
||||
|
||||
const (
|
||||
logInfo loggerLevel = iota
|
||||
logWarn
|
||||
logErr
|
||||
)
|
||||
|
||||
const AdapterNameMax = 128
|
||||
|
||||
type Adapter struct {
|
||||
handle uintptr
|
||||
}
|
||||
|
||||
var (
|
||||
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
||||
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
||||
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
||||
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
|
||||
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
|
||||
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
|
||||
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
|
||||
)
|
||||
|
||||
type TimestampedWriter interface {
|
||||
WriteWithTimestamp(p []byte, ts int64) (n int, err error)
|
||||
}
|
||||
|
||||
func logMessage(level loggerLevel, timestamp uint64, msg *uint16) int {
|
||||
if tw, ok := log.Default().Writer().(TimestampedWriter); ok {
|
||||
tw.WriteWithTimestamp([]byte(log.Default().Prefix()+windows.UTF16PtrToString(msg)), (int64(timestamp)-116444736000000000)*100)
|
||||
} else {
|
||||
log.Println(windows.UTF16PtrToString(msg))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func setupLogger(dll *lazyDLL) {
|
||||
var callback uintptr
|
||||
if runtime.GOARCH == "386" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, timestampLow, timestampHigh uint32, msg *uint16) int {
|
||||
return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "arm" {
|
||||
callback = windows.NewCallback(func(level loggerLevel, _, timestampLow, timestampHigh uint32, msg *uint16) int {
|
||||
return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
|
||||
})
|
||||
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
||||
callback = windows.NewCallback(logMessage)
|
||||
}
|
||||
syscall.Syscall(dll.NewProc("WintunSetLogger").Addr(), 1, callback, 0, 0)
|
||||
}
|
||||
|
||||
func closeAdapter(wintun *Adapter) {
|
||||
syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0)
|
||||
}
|
||||
|
||||
// CreateAdapter creates a Wintun adapter. name is the cosmetic name of the adapter.
|
||||
// tunnelType represents the type of adapter and should be "Wintun". requestedGUID is
|
||||
// the GUID of the created network adapter, which then influences NLA generation
|
||||
// deterministically. If it is set to nil, the GUID is chosen by the system at random,
|
||||
// and hence a new NLA entry is created for each new adapter.
|
||||
func CreateAdapter(name string, tunnelType string, requestedGUID *windows.GUID) (wintun *Adapter, err error) {
|
||||
var name16 *uint16
|
||||
name16, err = windows.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var tunnelType16 *uint16
|
||||
tunnelType16, err = windows.UTF16PtrFromString(tunnelType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall(procWintunCreateAdapter.Addr(), 3, uintptr(unsafe.Pointer(name16)), uintptr(unsafe.Pointer(tunnelType16)), uintptr(unsafe.Pointer(requestedGUID)))
|
||||
if r0 == 0 {
|
||||
err = e1
|
||||
return
|
||||
}
|
||||
wintun = &Adapter{handle: r0}
|
||||
runtime.SetFinalizer(wintun, closeAdapter)
|
||||
return
|
||||
}
|
||||
|
||||
// OpenAdapter opens an existing Wintun adapter by name.
|
||||
func OpenAdapter(name string) (wintun *Adapter, err error) {
|
||||
var name16 *uint16
|
||||
name16, err = windows.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall(procWintunOpenAdapter.Addr(), 1, uintptr(unsafe.Pointer(name16)), 0, 0)
|
||||
if r0 == 0 {
|
||||
err = e1
|
||||
return
|
||||
}
|
||||
wintun = &Adapter{handle: r0}
|
||||
runtime.SetFinalizer(wintun, closeAdapter)
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes a Wintun adapter.
|
||||
func (wintun *Adapter) Close() (err error) {
|
||||
runtime.SetFinalizer(wintun, nil)
|
||||
r1, _, e1 := syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0)
|
||||
if r1 == 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Uninstall removes the driver from the system if no drivers are currently in use.
|
||||
func Uninstall() (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procWintunDeleteDriver.Addr(), 0, 0, 0, 0)
|
||||
if r1 == 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunningVersion returns the version of the loaded driver.
|
||||
func RunningVersion() (version uint32, err error) {
|
||||
r0, _, e1 := syscall.Syscall(procWintunGetRunningDriverVersion.Addr(), 0, 0, 0, 0)
|
||||
version = uint32(r0)
|
||||
if version == 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LUID returns the LUID of the adapter.
|
||||
func (wintun *Adapter) LUID() (luid uint64) {
|
||||
syscall.Syscall(procWintunGetAdapterLUID.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&luid)), 0)
|
||||
return
|
||||
}
|
90
listener/tun/ipstack/commons/auto_linux.go
Normal file
90
listener/tun/ipstack/commons/auto_linux.go
Normal file
@ -0,0 +1,90 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/vishvananda/netlink"
|
||||
"go.uber.org/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
monitorStarted = atomic.NewBool(false)
|
||||
monitorStop = make(chan struct{}, 2)
|
||||
)
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
go func() {
|
||||
if monitorStarted.Load() {
|
||||
return
|
||||
}
|
||||
monitorStarted.Store(true)
|
||||
|
||||
done := make(chan struct{})
|
||||
ch := make(chan netlink.RouteUpdate, 2)
|
||||
err := netlink.RouteSubscribe(ch, done)
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] auto detect interface fail: %s", err)
|
||||
return
|
||||
}
|
||||
log.Debugln("[TUN] start auto detect interface monitor")
|
||||
defer func() {
|
||||
close(done)
|
||||
monitorStarted.Store(false)
|
||||
log.Debugln("[TUN] stop auto detect interface monitor")
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-monitorStop:
|
||||
default:
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-monitorStop:
|
||||
return
|
||||
case <-ch:
|
||||
}
|
||||
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
t := time.NewTicker(2 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case ch <- <-ch:
|
||||
break
|
||||
case <-t.C:
|
||||
interfaceName, err = GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Debugln("[TUN] detect interface: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
iface.FlushCache()
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
if monitorStarted.Load() {
|
||||
monitorStop <- struct{}{}
|
||||
}
|
||||
}
|
67
listener/tun/ipstack/commons/auto_others.go
Normal file
67
listener/tun/ipstack/commons/auto_others.go
Normal file
@ -0,0 +1,67 @@
|
||||
//go:build !linux
|
||||
|
||||
package commons
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"go.uber.org/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
monitorDuration = 3 * time.Second
|
||||
monitorStarted = atomic.NewBool(false)
|
||||
monitorStop = make(chan struct{}, 2)
|
||||
)
|
||||
|
||||
func StartDefaultInterfaceChangeMonitor() {
|
||||
go func() {
|
||||
if monitorStarted.Load() {
|
||||
return
|
||||
}
|
||||
monitorStarted.Store(true)
|
||||
t := time.NewTicker(monitorDuration)
|
||||
log.Debugln("[TUN] start auto detect interface monitor")
|
||||
defer func() {
|
||||
monitorStarted.Store(false)
|
||||
t.Stop()
|
||||
log.Debugln("[TUN] stop auto detect interface monitor")
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-monitorStop:
|
||||
default:
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
iface.FlushCache()
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
case <-monitorStop:
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StopDefaultInterfaceChangeMonitor() {
|
||||
if monitorStarted.Load() {
|
||||
monitorStop <- struct{}{}
|
||||
}
|
||||
}
|
@ -2,16 +2,13 @@ package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
|
||||
defaultInterfaceMonitorDuration = 3 * time.Second
|
||||
defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
|
||||
)
|
||||
|
||||
func ipv4MaskString(bits int) string {
|
||||
@ -23,26 +20,27 @@ func ipv4MaskString(bits int) string {
|
||||
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
||||
}
|
||||
|
||||
func DefaultInterfaceChangeMonitor() {
|
||||
t := time.NewTicker(defaultInterfaceMonitorDuration)
|
||||
func WaitForTunClose(deviceName string) {
|
||||
t := time.NewTicker(600 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
log.Debugln("[TUN] waiting for device close")
|
||||
for {
|
||||
<-t.C
|
||||
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor exited, cause: %v", err)
|
||||
continue
|
||||
break
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
if interfaceName == old {
|
||||
continue
|
||||
found := false
|
||||
for i := len(interfaces) - 1; i > -1; i-- {
|
||||
if interfaces[i].Name == deviceName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dialer.DefaultInterface.Store(interfaceName)
|
||||
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,86 +2,139 @@ package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/vishvananda/netlink"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (ifn string, err error) {
|
||||
cmdRes, err := cmd.ExecCmd("ip route get 1.1.1.1 uid 4294967295")
|
||||
|
||||
sps := strings.Split(cmdRes, " ")
|
||||
if len(sps) > 4 {
|
||||
ifn = sps[4]
|
||||
routes, err := netlink.RouteGetWithOptions(
|
||||
net.ParseIP("1.1.1.1"),
|
||||
&netlink.RouteGetOptions{
|
||||
Uid: &netlink.UID{Uid: 4294967295},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if ifn == "" {
|
||||
err = fmt.Errorf("interface not found")
|
||||
for _, route := range routes {
|
||||
if lk, err := netlink.LinkByIndex(route.LinkIndex); err == nil {
|
||||
return lk.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return "", fmt.Errorf("interface not found")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute, autoDetectInterface bool) error {
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
var (
|
||||
interfaceName = dev.Name()
|
||||
ip = addr.Masked().Addr().Next()
|
||||
)
|
||||
|
||||
_, err := cmd.ExecCmd(fmt.Sprintf("ip addr add %s dev %s", ip.String(), interfaceName))
|
||||
metaLink, err := netlink.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cmd.ExecCmd(fmt.Sprintf("ip link set %s up", interfaceName))
|
||||
naddr, err := netlink.ParseAddr(addr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = execRouterCmd("add", addr.Masked().String(), interfaceName, ip.String(), "main"); err != nil {
|
||||
if err = netlink.AddrAdd(metaLink, naddr); err != nil && err.Error() != "file exists" {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(metaLink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
err = configInterfaceRouting(interfaceName, addr, autoDetectInterface)
|
||||
err = configInterfaceRouting(metaLink.Attrs().Index, interfaceName, ip)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func configInterfaceRouting(interfaceName string, addr netip.Prefix, autoDetectInterface bool) error {
|
||||
linkIP := addr.Masked().Addr().Next()
|
||||
func configInterfaceRouting(index int, interfaceName string, ip netip.Addr) error {
|
||||
const tableId = 1981801
|
||||
var pref = 9000
|
||||
|
||||
for _, route := range defaultRoutes {
|
||||
if err := execRouterCmd("add", route, interfaceName, linkIP.String(), strconv.Itoa(tableId)); err != nil {
|
||||
_, ipn, err := net.ParseCIDR(route)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: index,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
Protocol: 2,
|
||||
Src: ip.AsSlice(),
|
||||
Dst: ipn,
|
||||
Table: tableId,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
execAddRuleCmd(fmt.Sprintf("lookup main pref 9000"))
|
||||
execAddRuleCmd(fmt.Sprintf("from 0.0.0.0 iif lo uidrange 0-4294967294 lookup %d pref 9001", tableId))
|
||||
execAddRuleCmd(fmt.Sprintf("from %s iif lo uidrange 0-4294967294 lookup %d pref 9002", linkIP, tableId))
|
||||
execAddRuleCmd(fmt.Sprintf("from all iif %s lookup main suppress_prefixlength 0 pref 9003", interfaceName))
|
||||
execAddRuleCmd(fmt.Sprintf("not from all iif lo lookup %d pref 9004", tableId))
|
||||
|
||||
if autoDetectInterface {
|
||||
go DefaultInterfaceChangeMonitor()
|
||||
logIfErr := func(e error) {
|
||||
if e != nil {
|
||||
log.Warnln("[TOUTE] config route rule: %s", e)
|
||||
}
|
||||
}
|
||||
|
||||
var r *netlink.Rule
|
||||
r = netlink.NewRule()
|
||||
r.Table = 254
|
||||
r.Priority = pref
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
r = netlink.NewRule()
|
||||
_, nl, _ := net.ParseCIDR("0.0.0.0/32")
|
||||
r.Table = tableId
|
||||
r.Priority = pref
|
||||
r.Src = nl
|
||||
r.IifName = "lo"
|
||||
r.UID = netlink.NewRuleUIDRange(0, 4294967294)
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
_, nl, _ = net.ParseCIDR(ip.String())
|
||||
r.Priority = pref
|
||||
r.Src = nl
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
r = netlink.NewRule()
|
||||
r.Table = 254
|
||||
r.Priority = pref
|
||||
r.IifName = interfaceName
|
||||
r.SuppressPrefixlen = 0
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
pref += 10
|
||||
|
||||
r = netlink.NewRule()
|
||||
r.Table = tableId
|
||||
r.Priority = pref
|
||||
r.IifName = "lo"
|
||||
r.SuppressPrefixlen = 0
|
||||
r.Invert = true
|
||||
logIfErr(netlink.RuleAdd(r))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func execAddRuleCmd(rule string) {
|
||||
_, err := cmd.ExecCmd("ip rule add " + rule)
|
||||
if err != nil {
|
||||
log.Warnln("%s", err)
|
||||
func CleanupRule() {
|
||||
r := netlink.NewRule()
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Priority = 9000 + i*10
|
||||
err := netlink.RuleDel(r)
|
||||
if err != nil {
|
||||
log.Warnln("[TOUTE] cleanup route rule: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func execRouterCmd(action, route, interfaceName, linkIP, table string) error {
|
||||
cmdStr := fmt.Sprintf("ip route %s %s dev %s proto kernel scope link src %s table %s", action, route, interfaceName, linkIP, table)
|
||||
|
||||
_, err := cmd.ExecCmd(cmdStr)
|
||||
return err
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ func GetAutoDetectInterface() (string, error) {
|
||||
return cmd.ExecCmd("bash -c route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute, autoDetectInterface bool) error {
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
if !addr.Addr().Is4() {
|
||||
return fmt.Errorf("supported ipv4 only")
|
||||
}
|
||||
@ -37,12 +37,12 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int,
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
err = configInterfaceRouting(interfaceName, addr, autoDetectInterface)
|
||||
err = configInterfaceRouting(interfaceName, addr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func configInterfaceRouting(interfaceName string, addr netip.Prefix, autoDetectInterface bool) error {
|
||||
func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
|
||||
var (
|
||||
routes = append(defaultRoutes, addr.String())
|
||||
gateway = addr.Masked().Addr().Next()
|
||||
@ -54,10 +54,6 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix, autoDetectI
|
||||
}
|
||||
}
|
||||
|
||||
if autoDetectInterface {
|
||||
go DefaultInterfaceChangeMonitor()
|
||||
}
|
||||
|
||||
return execRouterCmd("add", "-inet6", "2000::/3", interfaceName)
|
||||
}
|
||||
|
||||
@ -65,3 +61,5 @@ func execRouterCmd(action, inet, route string, interfaceName string) error {
|
||||
_, err := cmd.ExecCmd(fmt.Sprintf("route %s %s %s -interface %s", action, inet, route, interfaceName))
|
||||
return err
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
||||
|
@ -4,58 +4,86 @@ package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/common/cmd"
|
||||
"github.com/Dreamacro/clash/listener/tun/device"
|
||||
"github.com/vishvananda/netlink"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Dst == nil {
|
||||
lk, err := netlink.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if lk.Type() == "tuntap" {
|
||||
continue
|
||||
}
|
||||
|
||||
return lk.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("interface not found")
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute, autoDetectInterface bool) error {
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
var (
|
||||
interfaceName = dev.Name()
|
||||
ip = addr.Masked().Addr().Next()
|
||||
)
|
||||
|
||||
if _, err := cmd.ExecCmd(fmt.Sprintf("ip addr add %s dev %s", ip.String(), interfaceName)); err != nil {
|
||||
metaLink, err := netlink.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := cmd.ExecCmd(fmt.Sprintf("ip link set %s up", interfaceName)); err != nil {
|
||||
nlAddr, err := netlink.ParseAddr(addr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := execRouterCmd("add", addr.Masked().String(), interfaceName, ip.String(), "main"); err != nil {
|
||||
if err = netlink.AddrAdd(metaLink, nlAddr); err != nil && err.Error() != "file exists" {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(metaLink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
_ = configInterfaceRouting(interfaceName, addr, autoDetectInterface)
|
||||
_ = configInterfaceRouting(metaLink.Attrs().Index, ip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configInterfaceRouting(interfaceName string, addr netip.Prefix, autoDetectInterface bool) error {
|
||||
linkIP := addr.Masked().Addr().Next()
|
||||
|
||||
func configInterfaceRouting(index int, ip netip.Addr) error {
|
||||
for _, route := range defaultRoutes {
|
||||
if err := execRouterCmd("add", route, interfaceName, linkIP.String(), "main"); err != nil {
|
||||
_, ipn, err := net.ParseCIDR(route)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: index,
|
||||
Scope: netlink.SCOPE_LINK,
|
||||
Protocol: 2,
|
||||
Src: ip.AsSlice(),
|
||||
Dst: ipn,
|
||||
Table: 254,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if autoDetectInterface {
|
||||
go DefaultInterfaceChangeMonitor()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func execRouterCmd(action, route, interfaceName, linkIP, table string) error {
|
||||
cmdStr := fmt.Sprintf("ip route %s %s dev %s proto kernel scope link src %s table %s", action, route, interfaceName, linkIP, table)
|
||||
|
||||
_, err := cmd.ExecCmd(cmdStr)
|
||||
return err
|
||||
}
|
||||
func CleanupRule() {}
|
||||
|
@ -14,6 +14,8 @@ func GetAutoDetectInterface() (string, error) {
|
||||
return "", fmt.Errorf("can not auto detect interface-name on this OS: %s, you must be detecting interface-name by manual", runtime.GOOS)
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool, bool) error {
|
||||
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
||||
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
||||
|
@ -27,7 +27,7 @@ func GetAutoDetectInterface() (string, error) {
|
||||
return getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET6))
|
||||
}
|
||||
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute, autoDetectInterface bool) error {
|
||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
||||
retryOnFailure := services.StartedAtBoot()
|
||||
tryTimes := 0
|
||||
var err error
|
||||
@ -205,10 +205,6 @@ startOver:
|
||||
|
||||
wintunInterfaceName = dev.Name()
|
||||
|
||||
if autoDetectInterface {
|
||||
go DefaultInterfaceChangeMonitor()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -273,3 +269,5 @@ func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, erro
|
||||
|
||||
return "", errors.New("ethernet interface not found")
|
||||
}
|
||||
|
||||
func CleanupRule() {}
|
||||
|
@ -1,3 +1,5 @@
|
||||
//go:build !no_gvisor
|
||||
|
||||
package adapter
|
||||
|
||||
import (
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user