Compare commits
30 Commits
android-op
...
Alpha
Author | SHA1 | Date | |
---|---|---|---|
8c3557e96b | |||
228990472d | |||
09e7866a5c | |||
665ba7f9f1 | |||
ee3038d5e4 | |||
885ee7a820 | |||
ef303b11f2 | |||
a82ce85707 | |||
5bfe7ba169 | |||
ceac5bfaa4 | |||
b0638cfc49 | |||
96220aa8ea | |||
8ff476a3a1 | |||
261b6e8dce | |||
2b9141e0e5 | |||
55255faa52 | |||
d42e3f74ad | |||
81a8a63861 | |||
c3a61e2db5 | |||
bffe47a974 | |||
4314b37d04 | |||
cf93f69f40 | |||
55f626424f | |||
431d52f250 | |||
c1f24d8f0e | |||
fc5a3cf80c | |||
e1e999180a | |||
8755618910 | |||
aede97571f | |||
01bc84db02 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: Create a report to help us improve
|
description: Create a report to help us improve
|
||||||
title: "[Bug] "
|
title: "[Bug] "
|
||||||
|
labels: ["bug"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: ensure
|
id: ensure
|
||||||
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Clash.Meta Community Support
|
||||||
|
url: https://github.com/MetaCubeX/Clash.Meta/discussions
|
||||||
|
about: Please ask and answer questions about Clash.Meta here.
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
description: Suggest an idea for this project
|
description: Suggest an idea for this project
|
||||||
title: "[Feature] "
|
title: "[Feature] "
|
||||||
|
labels: ["enhancement"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: ensure
|
id: ensure
|
||||||
|
12
.github/rename-go120.sh
vendored
Normal file
12
.github/rename-go120.sh
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
FILENAMES=$(ls)
|
||||||
|
for FILENAME in $FILENAMES
|
||||||
|
do
|
||||||
|
if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then
|
||||||
|
mv $FILENAME ${FILENAME}-go120
|
||||||
|
elif [[ $FILENAME =~ ".exe" ]];then
|
||||||
|
mv $FILENAME ${FILENAME%.*}-go120.exe
|
||||||
|
else echo "skip $FILENAME"
|
||||||
|
fi
|
||||||
|
done
|
18
.github/workflows/android-branch-auto-sync.yml
vendored
18
.github/workflows/android-branch-auto-sync.yml
vendored
@ -49,3 +49,21 @@ jobs:
|
|||||||
- name: Push changes
|
- name: Push changes
|
||||||
run: |
|
run: |
|
||||||
git push origin android-real --force
|
git push origin android-real --force
|
||||||
|
|
||||||
|
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
||||||
|
trigger-CMFA-update:
|
||||||
|
needs: update-dependencies
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: tibdex/github-app-token@v1
|
||||||
|
id: generate-token
|
||||||
|
with:
|
||||||
|
app_id: ${{ secrets.MAINTAINER_APPID }}
|
||||||
|
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Trigger update-dependencies
|
||||||
|
run: |
|
||||||
|
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
|
||||||
|
-H "Accept: application/vnd.github.everest-preview+json" \
|
||||||
|
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
|
||||||
|
-d '{"event_type": "core-updated"}'
|
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@ -69,6 +69,12 @@ jobs:
|
|||||||
target: "darwin-amd64 darwin-arm64 android-arm64",
|
target: "darwin-amd64 darwin-arm64 android-arm64",
|
||||||
id: "9",
|
id: "9",
|
||||||
}
|
}
|
||||||
|
# only for test
|
||||||
|
- { type: "WithoutCGO-GO120", target: "linux-amd64 linux-amd64-compatible",id: "1" }
|
||||||
|
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
|
||||||
|
- { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" }
|
||||||
|
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
|
||||||
|
- { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" }
|
||||||
- { type: "WithCGO", target: "windows/*", id: "1" }
|
- { type: "WithCGO", target: "windows/*", id: "1" }
|
||||||
- { type: "WithCGO", target: "linux/386", id: "2" }
|
- { type: "WithCGO", target: "linux/386", id: "2" }
|
||||||
- { type: "WithCGO", target: "linux/amd64", id: "3" }
|
- { type: "WithCGO", target: "linux/amd64", id: "3" }
|
||||||
@ -126,18 +132,26 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.21"
|
go-version: "1.21"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.20"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
|
if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
|
||||||
run: |
|
run: |
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
- name: Build WithoutCGO
|
- name: Build WithoutCGO
|
||||||
if: ${{ matrix.job.type=='WithoutCGO' }}
|
if: ${{ matrix.job.type!='WithCGO' }}
|
||||||
env:
|
env:
|
||||||
NAME: Clash.Meta
|
NAME: Clash.Meta
|
||||||
BINDIR: bin
|
BINDIR: bin
|
||||||
@ -185,6 +199,17 @@ jobs:
|
|||||||
ls -la
|
ls -la
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
- name: Rename
|
||||||
|
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
|
||||||
|
run: |
|
||||||
|
cd bin
|
||||||
|
ls -la
|
||||||
|
cp ../.github/rename-go120.sh ./
|
||||||
|
bash ./rename-go120.sh
|
||||||
|
rm ./rename-go120.sh
|
||||||
|
ls -la
|
||||||
|
cd ..
|
||||||
|
|
||||||
- name: Zip
|
- name: Zip
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
run: |
|
run: |
|
||||||
|
28
.github/workflows/cmfa-update-deps-trigger.yml
vendored
28
.github/workflows/cmfa-update-deps-trigger.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: CMFA auto update-dependencies trigger
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
pull_request_target:
|
|
||||||
branches:
|
|
||||||
- Alpha
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-dependencies:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: tibdex/github-app-token@v1
|
|
||||||
id: generate-token
|
|
||||||
with:
|
|
||||||
app_id: ${{ secrets.MAINTAINER_APPID }}
|
|
||||||
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Trigger update-dependencies
|
|
||||||
run: |
|
|
||||||
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
|
|
||||||
-H "Accept: application/vnd.github.everest-preview+json" \
|
|
||||||
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
|
|
||||||
-d '{"event_type": "core-updated"}'
|
|
||||||
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
|
||||||
|
|
@ -12,8 +12,6 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A
|
|||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = C.HTTP
|
metadata.Type = C.HTTP
|
||||||
metadata.RawSrcAddr = srcConn.RemoteAddr()
|
|
||||||
metadata.RawDstAddr = srcConn.LocalAddr()
|
|
||||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||||
ApplyAdditions(metadata, additions...)
|
ApplyAdditions(metadata, additions...)
|
||||||
return conn, metadata
|
return conn, metadata
|
||||||
|
@ -10,8 +10,6 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions
|
|||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.UDP
|
metadata.NetWork = C.UDP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
metadata.RawSrcAddr = packet.LocalAddr()
|
|
||||||
metadata.RawDstAddr = metadata.UDPAddr()
|
|
||||||
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
|
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
|
||||||
if p, ok := packet.(C.UDPPacketInAddr); ok {
|
if p, ok := packet.(C.UDPPacketInAddr); ok {
|
||||||
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
|
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
|
||||||
|
@ -58,14 +58,15 @@ type simpleObfsOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type v2rayObfsOption struct {
|
type v2rayObfsOption struct {
|
||||||
Mode string `obfs:"mode"`
|
Mode string `obfs:"mode"`
|
||||||
Host string `obfs:"host,omitempty"`
|
Host string `obfs:"host,omitempty"`
|
||||||
Path string `obfs:"path,omitempty"`
|
Path string `obfs:"path,omitempty"`
|
||||||
TLS bool `obfs:"tls,omitempty"`
|
TLS bool `obfs:"tls,omitempty"`
|
||||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||||
Headers map[string]string `obfs:"headers,omitempty"`
|
Headers map[string]string `obfs:"headers,omitempty"`
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||||
Mux bool `obfs:"mux,omitempty"`
|
Mux bool `obfs:"mux,omitempty"`
|
||||||
|
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type shadowTLSOption struct {
|
type shadowTLSOption struct {
|
||||||
@ -259,10 +260,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
}
|
}
|
||||||
obfsMode = opts.Mode
|
obfsMode = opts.Mode
|
||||||
v2rayOption = &v2rayObfs.Option{
|
v2rayOption = &v2rayObfs.Option{
|
||||||
Host: opts.Host,
|
Host: opts.Host,
|
||||||
Path: opts.Path,
|
Path: opts.Path,
|
||||||
Headers: opts.Headers,
|
Headers: opts.Headers,
|
||||||
Mux: opts.Mux,
|
Mux: opts.Mux,
|
||||||
|
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.TLS {
|
if opts.TLS {
|
||||||
|
@ -53,9 +53,10 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
|
|||||||
if t.option.Network == "ws" {
|
if t.option.Network == "ws" {
|
||||||
host, port, _ := net.SplitHostPort(t.addr)
|
host, port, _ := net.SplitHostPort(t.addr)
|
||||||
wsOpts := &trojan.WebsocketOption{
|
wsOpts := &trojan.WebsocketOption{
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
Path: t.option.WSOpts.Path,
|
Path: t.option.WSOpts.Path,
|
||||||
|
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.option.SNI != "" {
|
if t.option.SNI != "" {
|
||||||
|
@ -93,6 +93,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
Path: v.option.WSOpts.Path,
|
Path: v.option.WSOpts.Path,
|
||||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||||
|
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Headers: http.Header{},
|
Headers: http.Header{},
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ type WSOptions struct {
|
|||||||
Headers map[string]string `proxy:"headers,omitempty"`
|
Headers map[string]string `proxy:"headers,omitempty"`
|
||||||
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
||||||
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
||||||
|
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamConnContext implements C.ProxyAdapter
|
// StreamConnContext implements C.ProxyAdapter
|
||||||
@ -110,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
Path: v.option.WSOpts.Path,
|
Path: v.option.WSOpts.Path,
|
||||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||||
|
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Headers: http.Header{},
|
Headers: http.Header{},
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package outboundgroup
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProxyGroup interface {
|
|
||||||
C.ProxyAdapter
|
|
||||||
|
|
||||||
Providers() []provider.ProxyProvider
|
|
||||||
Proxies() []C.Proxy
|
|
||||||
Now() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fallback) Providers() []provider.ProxyProvider {
|
|
||||||
return f.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
|
|
||||||
return lb.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Fallback) Proxies() []C.Proxy {
|
|
||||||
return f.GetProxies(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lb *LoadBalance) Proxies() []C.Proxy {
|
|
||||||
return lb.GetProxies(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lb *LoadBalance) Now() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) Providers() []provider.ProxyProvider {
|
|
||||||
return r.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) Proxies() []C.Proxy {
|
|
||||||
return r.GetProxies(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Relay) Now() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Selector) Providers() []provider.ProxyProvider {
|
|
||||||
return s.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Selector) Proxies() []C.Proxy {
|
|
||||||
return s.GetProxies(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *URLTest) Providers() []provider.ProxyProvider {
|
|
||||||
return u.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *URLTest) Proxies() []C.Proxy {
|
|
||||||
return u.GetProxies(false)
|
|
||||||
}
|
|
@ -18,7 +18,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
defaultURLTestTimeout = time.Second * 5
|
defaultURLTestTimeout = time.Second * 5
|
||||||
defaultURLTestURL = "https://www.gstatic.com/generate_204"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HealthCheckOption struct {
|
type HealthCheckOption struct {
|
||||||
@ -149,11 +148,6 @@ func (hc *HealthCheck) stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) check() {
|
func (hc *HealthCheck) check() {
|
||||||
|
|
||||||
if len(hc.proxies) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||||
id := utils.NewUUIDV4().String()
|
id := utils.NewUUIDV4().String()
|
||||||
log.Debugln("Start New Health Checking {%s}", id)
|
log.Debugln("Start New Health Checking {%s}", id)
|
||||||
@ -229,7 +223,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
|
|||||||
if len(url) == 0 {
|
if len(url) == 0 {
|
||||||
interval = 0
|
interval = 0
|
||||||
expectedStatus = nil
|
expectedStatus = nil
|
||||||
url = defaultURLTestURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HealthCheck{
|
return &HealthCheck{
|
||||||
|
@ -68,6 +68,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
|||||||
case "http":
|
case "http":
|
||||||
if schema.Path != "" {
|
if schema.Path != "" {
|
||||||
path := C.Path.Resolve(schema.Path)
|
path := C.Path.Resolve(schema.Path)
|
||||||
|
if !C.Path.IsSafePath(path) {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||||
|
}
|
||||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||||
} else {
|
} else {
|
||||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
suspended bool
|
|
||||||
)
|
|
||||||
|
|
||||||
type UpdatableProvider interface {
|
|
||||||
UpdatedAt() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) UpdatedAt() time.Time {
|
|
||||||
return pp.Fetcher.UpdatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) Close() error {
|
|
||||||
pp.healthCheck.close()
|
|
||||||
pp.Fetcher.Destroy()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Close() error {
|
|
||||||
cp.healthCheck.close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Suspend(s bool) {
|
|
||||||
suspended = s
|
|
||||||
}
|
|
@ -12,6 +12,7 @@ func DefaultValue[T any]() T {
|
|||||||
|
|
||||||
type TypedValue[T any] struct {
|
type TypedValue[T any] struct {
|
||||||
value atomic.Value
|
value atomic.Value
|
||||||
|
_ noCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TypedValue[T]) Load() T {
|
func (t *TypedValue[T]) Load() T {
|
||||||
@ -55,3 +56,9 @@ func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
|||||||
v.Store(t)
|
v.Store(t)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type noCopy struct{}
|
||||||
|
|
||||||
|
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||||
|
func (*noCopy) Lock() {}
|
||||||
|
func (*noCopy) Unlock() {}
|
||||||
|
@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn {
|
|||||||
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
|
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WarpConnWithBioReader(c net.Conn, br *bufio.Reader) net.Conn {
|
||||||
|
if br != nil && br.Buffered() > 0 {
|
||||||
|
if bc, ok := c.(*BufferedConn); ok && bc.r == br {
|
||||||
|
return bc
|
||||||
|
}
|
||||||
|
return &BufferedConn{br, NewExtendedConn(c), true}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// Reader returns the internal bufio.Reader.
|
// Reader returns the internal bufio.Reader.
|
||||||
func (c *BufferedConn) Reader() *bufio.Reader {
|
func (c *BufferedConn) Reader() *bufio.Reader {
|
||||||
return c.r
|
return c.r
|
||||||
|
49
common/net/cached.go
Normal file
49
common/net/cached.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ExtendedConn = (*CachedConn)(nil)
|
||||||
|
|
||||||
|
type CachedConn struct {
|
||||||
|
ExtendedConn
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCachedConn(c net.Conn, data []byte) *CachedConn {
|
||||||
|
return &CachedConn{NewExtendedConn(c), data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedConn) Read(b []byte) (n int, err error) {
|
||||||
|
if len(c.data) > 0 {
|
||||||
|
n = copy(b, c.data)
|
||||||
|
c.data = c.data[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy
|
||||||
|
if len(c.data) > 0 {
|
||||||
|
return buf.As(c.data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedConn) ReaderReplaceable() bool {
|
||||||
|
if len(c.data) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
31
common/net/context.go
Normal file
31
common/net/context.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine.
|
||||||
|
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
|
||||||
|
var (
|
||||||
|
quit = make(chan struct{})
|
||||||
|
interrupt = make(chan error, 1)
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
interrupt <- nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Close the connection, discarding the error
|
||||||
|
_ = conn.Close()
|
||||||
|
interrupt <- ctx.Err()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func(inputErr *error) {
|
||||||
|
close(quit)
|
||||||
|
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil {
|
||||||
|
// Return context error to user.
|
||||||
|
inputErr = &ctxErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,5 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/puzpuzpuz/xsync/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
Verify(user string, pass string) bool
|
Verify(user string, pass string) bool
|
||||||
Users() []string
|
Users() []string
|
||||||
@ -15,12 +11,12 @@ type AuthUser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type inMemoryAuthenticator struct {
|
type inMemoryAuthenticator struct {
|
||||||
storage *xsync.MapOf[string, string]
|
storage map[string]string
|
||||||
usernames []string
|
usernames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
|
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
|
||||||
realPass, ok := au.storage.Load(user)
|
realPass, ok := au.storage[user]
|
||||||
return ok && realPass == pass
|
return ok && realPass == pass
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
|||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
au := &inMemoryAuthenticator{
|
||||||
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
|
storage: make(map[string]string),
|
||||||
for _, user := range users {
|
usernames: make([]string, 0, len(users)),
|
||||||
au.storage.Store(user.User, user.Pass)
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
au.storage[user.User] = user.Pass
|
||||||
|
au.usernames = append(au.usernames, user.User)
|
||||||
}
|
}
|
||||||
usernames := make([]string, 0, len(users))
|
|
||||||
au.storage.Range(func(key string, value string) bool {
|
|
||||||
usernames = append(usernames, key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
au.usernames = usernames
|
|
||||||
|
|
||||||
return au
|
return au
|
||||||
}
|
}
|
||||||
|
@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
|
|||||||
listenAddr = "255.255.255.255:68"
|
listenAddr = "255.255.255.255:68"
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true))
|
options := []dialer.Option{
|
||||||
|
dialer.WithInterface(ifaceName),
|
||||||
|
dialer.WithAddrReuse(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback bind on windows, because syscall bind can not receive broadcast
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
options = append(options, dialer.WithFallbackBind(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addr *netip.Prefix
|
var addr netip.Prefix
|
||||||
switch network {
|
switch network {
|
||||||
case "udp4", "tcp4":
|
case "udp4", "tcp4":
|
||||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||||
@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
|
|||||||
|
|
||||||
return nil, iface.ErrAddrNotFound
|
return nil, iface.ErrAddrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
||||||
|
if !destination.IsGlobalUnicast() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
local := uint64(0)
|
||||||
|
if dialer.LocalAddr != nil {
|
||||||
|
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||||
|
if err == nil {
|
||||||
|
local, _ = strconv.ParseUint(port, 10, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.LocalAddr = addr
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||||
|
_, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
port = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
local, _ := strconv.ParseUint(port, 10, 16)
|
||||||
|
|
||||||
|
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackParseNetwork(network string, addr netip.Addr) string {
|
||||||
|
// fix fallbackBindIfaceToListenConfig() force bind to an ipv4 address
|
||||||
|
if !strings.HasSuffix(network, "4") &&
|
||||||
|
!strings.HasSuffix(network, "6") &&
|
||||||
|
addr.Unmap().Is6() {
|
||||||
|
network += "6"
|
||||||
|
}
|
||||||
|
return network
|
||||||
|
}
|
||||||
|
@ -5,55 +5,16 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
||||||
if !destination.IsGlobalUnicast() {
|
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
local := uint64(0)
|
|
||||||
if dialer.LocalAddr != nil {
|
|
||||||
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
|
||||||
if err == nil {
|
|
||||||
local, _ = strconv.ParseUint(port, 10, 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
|
||||||
_, port, err := net.SplitHostPort(address)
|
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
|
||||||
if err != nil {
|
|
||||||
port = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
local, _ := strconv.ParseUint(port, 10, 16)
|
|
||||||
|
|
||||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseNetwork(network string, addr netip.Addr) string {
|
func ParseNetwork(network string, addr netip.Addr) string {
|
||||||
// fix bindIfaceToListenConfig() force bind to an ipv4 address
|
return fallbackParseNetwork(network, addr)
|
||||||
if !strings.HasSuffix(network, "4") &&
|
|
||||||
!strings.HasSuffix(network, "6") &&
|
|
||||||
addr.Unmap().Is6() {
|
|
||||||
network += "6"
|
|
||||||
}
|
|
||||||
return network
|
|
||||||
}
|
}
|
||||||
|
@ -70,14 +70,15 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||||
if DefaultSocketHook != nil {
|
|
||||||
return listenPacketHooked(ctx, network, address)
|
|
||||||
}
|
|
||||||
cfg := applyOptions(options...)
|
cfg := applyOptions(options...)
|
||||||
|
|
||||||
lc := &net.ListenConfig{}
|
lc := &net.ListenConfig{}
|
||||||
if cfg.interfaceName != "" {
|
if cfg.interfaceName != "" {
|
||||||
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
bind := bindIfaceToListenConfig
|
||||||
|
if cfg.fallbackBind {
|
||||||
|
bind = fallbackBindIfaceToListenConfig
|
||||||
|
}
|
||||||
|
addr, err := bind(cfg.interfaceName, lc, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -113,9 +114,6 @@ func GetTcpConcurrent() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
if DefaultSocketHook != nil {
|
|
||||||
return dialContextHooked(ctx, network, destination, port)
|
|
||||||
}
|
|
||||||
address := net.JoinHostPort(destination.String(), port)
|
address := net.JoinHostPort(destination.String(), port)
|
||||||
|
|
||||||
netDialer := opt.netDialer
|
netDialer := opt.netDialer
|
||||||
@ -131,7 +129,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||||||
|
|
||||||
dialer := netDialer.(*net.Dialer)
|
dialer := netDialer.(*net.Dialer)
|
||||||
if opt.interfaceName != "" {
|
if opt.interfaceName != "" {
|
||||||
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
bind := bindIfaceToDialer
|
||||||
|
if opt.fallbackBind {
|
||||||
|
bind = fallbackBindIfaceToDialer
|
||||||
|
}
|
||||||
|
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ type NetDialer interface {
|
|||||||
|
|
||||||
type option struct {
|
type option struct {
|
||||||
interfaceName string
|
interfaceName string
|
||||||
|
fallbackBind bool
|
||||||
addrReuse bool
|
addrReuse bool
|
||||||
routingMark int
|
routingMark int
|
||||||
network int
|
network int
|
||||||
@ -38,6 +39,12 @@ func WithInterface(name string) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithFallbackBind(fallback bool) Option {
|
||||||
|
return func(opt *option) {
|
||||||
|
opt.fallbackBind = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithAddrReuse(reuse bool) Option {
|
func WithAddrReuse(reuse bool) Option {
|
||||||
return func(opt *option) {
|
return func(opt *option) {
|
||||||
opt.addrReuse = reuse
|
opt.addrReuse = reuse
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
|
||||||
|
|
||||||
var DefaultSocketHook SocketControl
|
|
||||||
|
|
||||||
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
|
|
||||||
dialer := &net.Dialer{
|
|
||||||
Control: DefaultSocketHook,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t, ok := conn.(*net.TCPConn); ok {
|
|
||||||
t.SetKeepAlive(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
|
|
||||||
lc := &net.ListenConfig{
|
|
||||||
Control: DefaultSocketHook,
|
|
||||||
}
|
|
||||||
|
|
||||||
return lc.ListenPacket(ctx, network, address)
|
|
||||||
}
|
|
@ -36,7 +36,7 @@ type Pool struct {
|
|||||||
cycle bool
|
cycle bool
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
host *trie.DomainTrie[struct{}]
|
host *trie.DomainTrie[struct{}]
|
||||||
ipnet *netip.Prefix
|
ipnet netip.Prefix
|
||||||
store store
|
store store
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IPNet return raw ipnet
|
// IPNet return raw ipnet
|
||||||
func (p *Pool) IPNet() *netip.Prefix {
|
func (p *Pool) IPNet() netip.Prefix {
|
||||||
return p.ipnet
|
return p.ipnet
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ func (p *Pool) restoreState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
IPNet *netip.Prefix
|
IPNet netip.Prefix
|
||||||
Host *trie.DomainTrie[struct{}]
|
Host *trie.DomainTrie[struct{}]
|
||||||
|
|
||||||
// Size sets the maximum number of entries in memory
|
// Size sets the maximum number of entries in memory
|
||||||
@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) {
|
|||||||
hostAddr = options.IPNet.Masked().Addr()
|
hostAddr = options.IPNet.Masked().Addr()
|
||||||
gateway = hostAddr.Next()
|
gateway = hostAddr.Next()
|
||||||
first = gateway.Next().Next().Next() // default start with 198.18.0.4
|
first = gateway.Next().Next().Next() // default start with 198.18.0.4
|
||||||
last = nnip.UnMasked(*options.IPNet)
|
last = nnip.UnMasked(options.IPNet)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
|
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
|
||||||
|
@ -51,7 +51,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
|
|||||||
func TestPool_Basic(t *testing.T) {
|
func TestPool_Basic(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.0/28")
|
ipnet := netip.MustParsePrefix("192.168.0.0/28")
|
||||||
pools, tempfile, err := createPools(Options{
|
pools, tempfile, err := createPools(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) {
|
|||||||
func TestPool_BasicV6(t *testing.T) {
|
func TestPool_BasicV6(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
|
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
|
||||||
pools, tempfile, err := createPools(Options{
|
pools, tempfile, err := createPools(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) {
|
|||||||
func TestPool_Case_Insensitive(t *testing.T) {
|
func TestPool_Case_Insensitive(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/29")
|
ipnet := netip.MustParsePrefix("192.168.0.1/29")
|
||||||
pools, tempfile, err := createPools(Options{
|
pools, tempfile, err := createPools(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) {
|
|||||||
func TestPool_CycleUsed(t *testing.T) {
|
func TestPool_CycleUsed(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.16/28")
|
ipnet := netip.MustParsePrefix("192.168.0.16/28")
|
||||||
pools, tempfile, err := createPools(Options{
|
pools, tempfile, err := createPools(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) {
|
|||||||
tree := trie.New[struct{}]()
|
tree := trie.New[struct{}]()
|
||||||
tree.Insert("example.com", struct{}{})
|
tree.Insert("example.com", struct{}{})
|
||||||
pools, tempfile, err := createPools(Options{
|
pools, tempfile, err := createPools(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
Host: tree,
|
Host: tree,
|
||||||
})
|
})
|
||||||
@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) {
|
|||||||
func TestPool_MaxCacheSize(t *testing.T) {
|
func TestPool_MaxCacheSize(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||||
pool, _ := New(Options{
|
pool, _ := New(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 2,
|
Size: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
|
|||||||
func TestPool_DoubleMapping(t *testing.T) {
|
func TestPool_DoubleMapping(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||||
pool, _ := New(Options{
|
pool, _ := New(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 2,
|
Size: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) {
|
|||||||
func TestPool_Clone(t *testing.T) {
|
func TestPool_Clone(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||||
pool, _ := New(Options{
|
pool, _ := New(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 2,
|
Size: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) {
|
|||||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||||
|
|
||||||
newPool, _ := New(Options{
|
newPool, _ := New(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 2,
|
Size: 2,
|
||||||
})
|
})
|
||||||
newPool.CloneFrom(pool)
|
newPool.CloneFrom(pool)
|
||||||
@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) {
|
|||||||
func TestPool_Error(t *testing.T) {
|
func TestPool_Error(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/31")
|
ipnet := netip.MustParsePrefix("192.168.0.1/31")
|
||||||
_, err := New(Options{
|
_, err := New(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) {
|
|||||||
func TestPool_FlushFileCache(t *testing.T) {
|
func TestPool_FlushFileCache(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
||||||
pools, tempfile, err := createPools(Options{
|
pools, tempfile, err := createPools(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) {
|
|||||||
func TestPool_FlushMemoryCache(t *testing.T) {
|
func TestPool_FlushMemoryCache(t *testing.T) {
|
||||||
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
||||||
pool, _ := New(Options{
|
pool, _ := New(Options{
|
||||||
IPNet: &ipnet,
|
IPNet: ipnet,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
type Interface struct {
|
type Interface struct {
|
||||||
Index int
|
Index int
|
||||||
Name string
|
Name string
|
||||||
Addrs []*netip.Prefix
|
Addrs []netip.Prefix
|
||||||
HardwareAddr net.HardwareAddr
|
HardwareAddr net.HardwareAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ipNets := make([]*netip.Prefix, 0, len(addrs))
|
ipNets := make([]netip.Prefix, 0, len(addrs))
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
ipNet := addr.(*net.IPNet)
|
ipNet := addr.(*net.IPNet)
|
||||||
ip, _ := netip.AddrFromSlice(ipNet.IP)
|
ip, _ := netip.AddrFromSlice(ipNet.IP)
|
||||||
@ -59,7 +59,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pf := netip.PrefixFrom(ip, ones)
|
pf := netip.PrefixFrom(ip, ones)
|
||||||
ipNets = append(ipNets, &pf)
|
ipNets = append(ipNets, pf)
|
||||||
}
|
}
|
||||||
|
|
||||||
r[iface.Name] = &Interface{
|
r[iface.Name] = &Interface{
|
||||||
@ -89,27 +89,27 @@ func FlushCache() {
|
|||||||
interfaces.Reset()
|
interfaces.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) {
|
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||||
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
|
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
|
||||||
return addr.Addr().Is4()
|
return addr.Addr().Is4()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) {
|
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||||
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
|
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
|
||||||
return addr.Addr().Is6()
|
return addr.Addr().Is6()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) {
|
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
|
||||||
var fallback *netip.Prefix
|
var fallback netip.Prefix
|
||||||
|
|
||||||
for _, addr := range iface.Addrs {
|
for _, addr := range iface.Addrs {
|
||||||
if !accept(addr) {
|
if !accept(addr) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if fallback == nil && !addr.Addr().IsLinkLocalUnicast() {
|
if !fallback.IsValid() && !addr.Addr().IsLinkLocalUnicast() {
|
||||||
fallback = addr
|
fallback = addr
|
||||||
|
|
||||||
if !destination.IsValid() {
|
if !destination.IsValid() {
|
||||||
@ -122,8 +122,8 @@ func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fallback == nil {
|
if !fallback.IsValid() {
|
||||||
return nil, ErrAddrNotFound
|
return netip.Prefix{}, ErrAddrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return fallback, nil
|
return fallback, nil
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package mmdb
|
|
||||||
|
|
||||||
import "github.com/oschwald/maxminddb-golang"
|
|
||||||
|
|
||||||
func InstallOverride(override *maxminddb.Reader) {
|
|
||||||
newReader := Reader{Reader: override}
|
|
||||||
switch override.Metadata.DatabaseType {
|
|
||||||
case "sing-geoip":
|
|
||||||
reader.databaseType = typeSing
|
|
||||||
case "Meta-geoip0":
|
|
||||||
reader.databaseType = typeMetaV0
|
|
||||||
default:
|
|
||||||
reader.databaseType = typeMaxmind
|
|
||||||
}
|
|
||||||
reader = newReader
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package process
|
|
||||||
|
|
||||||
import "github.com/Dreamacro/clash/constant"
|
|
||||||
|
|
||||||
type PackageNameResolver func(metadata *constant.Metadata) (string, error)
|
|
||||||
|
|
||||||
var DefaultPackageNameResolver PackageNameResolver
|
|
||||||
|
|
||||||
func FindPackageName(metadata *constant.Metadata) (string, error) {
|
|
||||||
if resolver := DefaultPackageNameResolver; resolver != nil {
|
|
||||||
return resolver(metadata)
|
|
||||||
}
|
|
||||||
return "", ErrPlatformNotSupport
|
|
||||||
}
|
|
@ -13,10 +13,6 @@ import (
|
|||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
minInterval = time.Minute * 5
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fileMode os.FileMode = 0o666
|
fileMode os.FileMode = 0o666
|
||||||
dirMode os.FileMode = 0o755
|
dirMode os.FileMode = 0o755
|
||||||
@ -28,7 +24,8 @@ type Fetcher[V any] struct {
|
|||||||
resourceType string
|
resourceType string
|
||||||
name string
|
name string
|
||||||
vehicle types.Vehicle
|
vehicle types.Vehicle
|
||||||
UpdatedAt time.Time
|
UpdatedAt *time.Time
|
||||||
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
hash [16]byte
|
hash [16]byte
|
||||||
parser Parser[V]
|
parser Parser[V]
|
||||||
@ -59,7 +56,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||||
buf, err = os.ReadFile(f.vehicle.Path())
|
buf, err = os.ReadFile(f.vehicle.Path())
|
||||||
modTime := stat.ModTime()
|
modTime := stat.ModTime()
|
||||||
f.UpdatedAt = modTime
|
f.UpdatedAt = &modTime
|
||||||
isLocal = true
|
isLocal = true
|
||||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||||
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
||||||
@ -67,7 +64,6 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buf, err = f.vehicle.Read()
|
buf, err = f.vehicle.Read()
|
||||||
f.UpdatedAt = time.Now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,7 +113,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
f.hash = md5.Sum(buf)
|
f.hash = md5.Sum(buf)
|
||||||
|
|
||||||
// pull contents automatically
|
// pull contents automatically
|
||||||
if f.interval > 0 {
|
if f.ticker != nil {
|
||||||
go f.pullLoop()
|
go f.pullLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +129,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
hash := md5.Sum(buf)
|
hash := md5.Sum(buf)
|
||||||
if bytes.Equal(f.hash[:], hash[:]) {
|
if bytes.Equal(f.hash[:], hash[:]) {
|
||||||
f.UpdatedAt = now
|
f.UpdatedAt = &now
|
||||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||||
return lo.Empty[V](), true, nil
|
return lo.Empty[V](), true, nil
|
||||||
}
|
}
|
||||||
@ -149,31 +145,23 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.UpdatedAt = now
|
f.UpdatedAt = &now
|
||||||
f.hash = hash
|
f.hash = hash
|
||||||
|
|
||||||
return contents, false, nil
|
return contents, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Destroy() error {
|
func (f *Fetcher[V]) Destroy() error {
|
||||||
if f.interval > 0 {
|
if f.ticker != nil {
|
||||||
f.done <- struct{}{}
|
f.done <- struct{}{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) pullLoop() {
|
func (f *Fetcher[V]) pullLoop() {
|
||||||
initialInterval := f.interval - time.Since(f.UpdatedAt)
|
|
||||||
if initialInterval < minInterval {
|
|
||||||
initialInterval = minInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
timer := time.NewTimer(initialInterval)
|
|
||||||
defer timer.Stop()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-f.ticker.C:
|
||||||
timer.Reset(f.interval)
|
|
||||||
elm, same, err := f.Update()
|
elm, same, err := f.Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||||
@ -190,6 +178,7 @@ func (f *Fetcher[V]) pullLoop() {
|
|||||||
f.OnUpdate(elm)
|
f.OnUpdate(elm)
|
||||||
}
|
}
|
||||||
case <-f.done:
|
case <-f.done:
|
||||||
|
f.ticker.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,12 +197,17 @@ func safeWrite(path string, buf []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
|
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
|
||||||
|
var ticker *time.Ticker
|
||||||
|
if interval != 0 {
|
||||||
|
ticker = time.NewTicker(interval)
|
||||||
|
}
|
||||||
|
|
||||||
return &Fetcher[V]{
|
return &Fetcher[V]{
|
||||||
name: name,
|
name: name,
|
||||||
|
ticker: ticker,
|
||||||
vehicle: vehicle,
|
vehicle: vehicle,
|
||||||
parser: parser,
|
parser: parser,
|
||||||
done: make(chan struct{}, 8),
|
done: make(chan struct{}, 1),
|
||||||
OnUpdate: onUpdate,
|
OnUpdate: onUpdate,
|
||||||
interval: interval,
|
interval: interval,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,8 @@ type RealityConfig struct {
|
|||||||
func aesgcmPreferred(ciphers []uint16) bool
|
func aesgcmPreferred(ciphers []uint16) bool
|
||||||
|
|
||||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||||
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
retry := 0
|
||||||
|
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
|
||||||
verifier := &realityVerifier{
|
verifier := &realityVerifier{
|
||||||
serverName: tlsConfig.ServerName,
|
serverName: tlsConfig.ServerName,
|
||||||
}
|
}
|
||||||
@ -80,7 +81,15 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
|||||||
|
|
||||||
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||||
|
|
||||||
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:])
|
ecdheParams := uConn.HandshakeState.State13.EcdheParams
|
||||||
|
if ecdheParams == nil {
|
||||||
|
// WTF???
|
||||||
|
if retry > 2 {
|
||||||
|
return nil, errors.New("nil ecdheParams")
|
||||||
|
}
|
||||||
|
continue // retry
|
||||||
|
}
|
||||||
|
authKey := ecdheParams.SharedKey(realityConfig.PublicKey[:])
|
||||||
if authKey == nil {
|
if authKey == nil {
|
||||||
return nil, errors.New("nil auth_key")
|
return nil, errors.New("nil auth_key")
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ type UClientHelloID struct {
|
|||||||
var initRandomFingerprint UClientHelloID
|
var initRandomFingerprint UClientHelloID
|
||||||
var initUtlsClient string
|
var initUtlsClient string
|
||||||
|
|
||||||
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) net.Conn {
|
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn {
|
||||||
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
|
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
|
||||||
Client: fingerprint.Client,
|
Client: fingerprint.Client,
|
||||||
Version: fingerprint.Version,
|
Version: fingerprint.Version,
|
||||||
|
175
config/config.go
175
config/config.go
@ -20,7 +20,6 @@ import (
|
|||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
@ -59,6 +58,7 @@ type General struct {
|
|||||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
||||||
Sniffing bool `json:"sniffing"`
|
Sniffing bool `json:"sniffing"`
|
||||||
|
EBpf EBpf `json:"-"`
|
||||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||||
GlobalUA string `json:"global-ua"`
|
GlobalUA string `json:"global-ua"`
|
||||||
}
|
}
|
||||||
@ -122,7 +122,7 @@ type DNS struct {
|
|||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
GeoIPCode string `yaml:"geoip-code"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
IPCIDR []*netip.Prefix `yaml:"ipcidr"`
|
IPCIDR []netip.Prefix `yaml:"ipcidr"`
|
||||||
Domain []string `yaml:"domain"`
|
Domain []string `yaml:"domain"`
|
||||||
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
||||||
}
|
}
|
||||||
@ -193,34 +193,29 @@ type RawNTP struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDNS struct {
|
type RawDNS struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
|
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||||
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
|
UseHosts bool `yaml:"use-hosts"`
|
||||||
NameServer []string `yaml:"nameserver" json:"nameserver"`
|
NameServer []string `yaml:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback" json:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
|
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen" json:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||||
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
|
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||||
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
|
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||||
NameServerPolicy map[string]any `yaml:"nameserver-policy" json:"nameserver-policy"`
|
NameServerPolicy map[string]any `yaml:"nameserver-policy"`
|
||||||
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
|
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip" json:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
GeoIPCode string `yaml:"geoip-code" json:"geoip-code"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
IPCIDR []string `yaml:"ipcidr" json:"ipcidr"`
|
IPCIDR []string `yaml:"ipcidr"`
|
||||||
Domain []string `yaml:"domain" json:"domain"`
|
Domain []string `yaml:"domain"`
|
||||||
GeoSite []string `yaml:"geosite" json:"geosite"`
|
GeoSite []string `yaml:"geosite"`
|
||||||
}
|
|
||||||
|
|
||||||
type RawClashForAndroid struct {
|
|
||||||
AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"`
|
|
||||||
UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawTun struct {
|
type RawTun struct {
|
||||||
@ -234,20 +229,22 @@ type RawTun struct {
|
|||||||
|
|
||||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||||
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
|
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"`
|
||||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
|
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"`
|
||||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||||
|
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||||
|
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawTuicServer struct {
|
type RawTuicServer struct {
|
||||||
@ -266,23 +263,23 @@ type RawTuicServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
Port int `yaml:"port" json:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port" json:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
RedirPort int `yaml:"redir-port" json:"redir-port"`
|
RedirPort int `yaml:"redir-port"`
|
||||||
TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"`
|
TProxyPort int `yaml:"tproxy-port"`
|
||||||
MixedPort int `yaml:"mixed-port" json:"mixed-port"`
|
MixedPort int `yaml:"mixed-port"`
|
||||||
ShadowSocksConfig string `yaml:"ss-config"`
|
ShadowSocksConfig string `yaml:"ss-config"`
|
||||||
VmessConfig string `yaml:"vmess-config"`
|
VmessConfig string `yaml:"vmess-config"`
|
||||||
InboundTfo bool `yaml:"inbound-tfo"`
|
InboundTfo bool `yaml:"inbound-tfo"`
|
||||||
InboundMPTCP bool `yaml:"inbound-mptcp"`
|
InboundMPTCP bool `yaml:"inbound-mptcp"`
|
||||||
Authentication []string `yaml:"authentication" json:"authentication"`
|
Authentication []string `yaml:"authentication"`
|
||||||
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
|
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
|
||||||
AllowLan bool `yaml:"allow-lan" json:"allow-lan"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
BindAddress string `yaml:"bind-address" json:"bind-address"`
|
BindAddress string `yaml:"bind-address"`
|
||||||
Mode T.TunnelMode `yaml:"mode" json:"mode"`
|
Mode T.TunnelMode `yaml:"mode"`
|
||||||
UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"`
|
UnifiedDelay bool `yaml:"unified-delay"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
ExternalController string `yaml:"external-controller"`
|
ExternalController string `yaml:"external-controller"`
|
||||||
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
||||||
ExternalUI string `yaml:"external-ui"`
|
ExternalUI string `yaml:"external-ui"`
|
||||||
@ -292,20 +289,20 @@ type RawConfig struct {
|
|||||||
Interface string `yaml:"interface-name"`
|
Interface string `yaml:"interface-name"`
|
||||||
RoutingMark int `yaml:"routing-mark"`
|
RoutingMark int `yaml:"routing-mark"`
|
||||||
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
||||||
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"`
|
GeodataMode bool `yaml:"geodata-mode"`
|
||||||
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"`
|
GeodataLoader string `yaml:"geodata-loader"`
|
||||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||||
GlobalUA string `yaml:"global-ua"`
|
GlobalUA string `yaml:"global-ua"`
|
||||||
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
||||||
|
|
||||||
Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"`
|
Sniffer RawSniffer `yaml:"sniffer"`
|
||||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||||
Hosts map[string]any `yaml:"hosts" json:"hosts"`
|
Hosts map[string]any `yaml:"hosts"`
|
||||||
NTP RawNTP `yaml:"ntp" json:"ntp"`
|
NTP RawNTP `yaml:"ntp"`
|
||||||
DNS RawDNS `yaml:"dns" json:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Tun RawTun `yaml:"tun"`
|
Tun RawTun `yaml:"tun"`
|
||||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||||
EBpf EBpf `yaml:"ebpf"`
|
EBpf EBpf `yaml:"ebpf"`
|
||||||
@ -319,8 +316,6 @@ type RawConfig struct {
|
|||||||
SubRules map[string][]string `yaml:"sub-rules"`
|
SubRules map[string][]string `yaml:"sub-rules"`
|
||||||
RawTLS TLS `yaml:"tls"`
|
RawTLS TLS `yaml:"tls"`
|
||||||
Listeners []map[string]any `yaml:"listeners"`
|
Listeners []map[string]any `yaml:"listeners"`
|
||||||
|
|
||||||
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeoXUrl struct {
|
type GeoXUrl struct {
|
||||||
@ -468,9 +463,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
StoreSelected: true,
|
StoreSelected: true,
|
||||||
},
|
},
|
||||||
GeoXUrl: GeoXUrl{
|
GeoXUrl: GeoXUrl{
|
||||||
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb",
|
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||||
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
|
||||||
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
|
||||||
},
|
},
|
||||||
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
|
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
|
||||||
}
|
}
|
||||||
@ -488,6 +483,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
config.Experimental = &rawCfg.Experimental
|
config.Experimental = &rawCfg.Experimental
|
||||||
config.Profile = &rawCfg.Profile
|
config.Profile = &rawCfg.Profile
|
||||||
|
config.IPTables = &rawCfg.IPTables
|
||||||
config.TLS = &rawCfg.RawTLS
|
config.TLS = &rawCfg.RawTLS
|
||||||
|
|
||||||
general, err := parseGeneral(rawCfg)
|
general, err := parseGeneral(rawCfg)
|
||||||
@ -548,6 +544,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.DNS = dnsCfg
|
config.DNS = dnsCfg
|
||||||
|
|
||||||
|
err = parseTun(rawCfg.Tun, config.General)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = parseTuicServer(rawCfg.TuicServer, config.General)
|
err = parseTuicServer(rawCfg.TuicServer, config.General)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -644,6 +645,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
GeodataLoader: cfg.GeodataLoader,
|
GeodataLoader: cfg.GeodataLoader,
|
||||||
TCPConcurrent: cfg.TCPConcurrent,
|
TCPConcurrent: cfg.TCPConcurrent,
|
||||||
FindProcessMode: cfg.FindProcessMode,
|
FindProcessMode: cfg.FindProcessMode,
|
||||||
|
EBpf: cfg.EBpf,
|
||||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||||
GlobalUA: cfg.GlobalUA,
|
GlobalUA: cfg.GlobalUA,
|
||||||
}, nil
|
}, nil
|
||||||
@ -1047,7 +1049,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
|||||||
Net: dnsNetType,
|
Net: dnsNetType,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
ProxyName: proxyName,
|
ProxyName: proxyName,
|
||||||
Interface: dialer.DefaultInterface,
|
|
||||||
Params: params,
|
Params: params,
|
||||||
PreferH3: preferH3,
|
PreferH3: preferH3,
|
||||||
},
|
},
|
||||||
@ -1149,15 +1150,15 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
|
|||||||
return policy, nil
|
return policy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
|
func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
|
||||||
var ipNets []*netip.Prefix
|
var ipNets []netip.Prefix
|
||||||
|
|
||||||
for idx, ip := range ips {
|
for idx, ip := range ips {
|
||||||
ipnet, err := netip.ParsePrefix(ip)
|
ipnet, err := netip.ParsePrefix(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
|
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
|
||||||
}
|
}
|
||||||
ipNets = append(ipNets, &ipnet)
|
ipNets = append(ipNets, ipnet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipNets, nil
|
return ipNets, nil
|
||||||
@ -1225,7 +1226,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
|||||||
IPv6: cfg.IPv6,
|
IPv6: cfg.IPv6,
|
||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
FallbackFilter: FallbackFilter{
|
FallbackFilter: FallbackFilter{
|
||||||
IPCIDR: []*netip.Prefix{},
|
IPCIDR: []netip.Prefix{},
|
||||||
GeoSite: []*router.DomainMatcher{},
|
GeoSite: []*router.DomainMatcher{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1299,7 +1300,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
|||||||
}
|
}
|
||||||
|
|
||||||
pool, err := fakeip.New(fakeip.Options{
|
pool, err := fakeip.New(fakeip.Options{
|
||||||
IPNet: &fakeIPRange,
|
IPNet: fakeIPRange,
|
||||||
Size: 1000,
|
Size: 1000,
|
||||||
Host: host,
|
Host: host,
|
||||||
Persistence: rawCfg.Profile.StoreFakeIP,
|
Persistence: rawCfg.Profile.StoreFakeIP,
|
||||||
@ -1362,22 +1363,24 @@ func parseTun(rawTun RawTun, general *General) error {
|
|||||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||||
RedirectToTun: rawTun.RedirectToTun,
|
RedirectToTun: rawTun.RedirectToTun,
|
||||||
|
|
||||||
MTU: rawTun.MTU,
|
MTU: rawTun.MTU,
|
||||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
||||||
Inet6Address: rawTun.Inet6Address,
|
Inet6Address: rawTun.Inet6Address,
|
||||||
StrictRoute: rawTun.StrictRoute,
|
StrictRoute: rawTun.StrictRoute,
|
||||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||||
IncludeUID: rawTun.IncludeUID,
|
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
||||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
||||||
ExcludeUID: rawTun.ExcludeUID,
|
IncludeUID: rawTun.IncludeUID,
|
||||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
ExcludeUID: rawTun.ExcludeUID,
|
||||||
IncludePackage: rawTun.IncludePackage,
|
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||||
ExcludePackage: rawTun.ExcludePackage,
|
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
IncludePackage: rawTun.IncludePackage,
|
||||||
UDPTimeout: rawTun.UDPTimeout,
|
ExcludePackage: rawTun.ExcludePackage,
|
||||||
FileDescriptor: rawTun.FileDescriptor,
|
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||||
|
UDPTimeout: rawTun.UDPTimeout,
|
||||||
|
FileDescriptor: rawTun.FileDescriptor,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -2,7 +2,7 @@ package constant
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
GeodataMode bool
|
GeodataMode bool
|
||||||
GeoIpUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat"
|
GeoIpUrl string
|
||||||
MmdbUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb"
|
MmdbUrl string
|
||||||
GeoSiteUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat"
|
GeoSiteUrl string
|
||||||
)
|
)
|
||||||
|
@ -147,9 +147,6 @@ type Metadata struct {
|
|||||||
SpecialProxy string `json:"specialProxy"`
|
SpecialProxy string `json:"specialProxy"`
|
||||||
SpecialRules string `json:"specialRules"`
|
SpecialRules string `json:"specialRules"`
|
||||||
RemoteDst string `json:"remoteDestination"`
|
RemoteDst string `json:"remoteDestination"`
|
||||||
|
|
||||||
RawSrcAddr net.Addr `json:"-"`
|
|
||||||
RawDstAddr net.Addr `json:"-"`
|
|
||||||
// Only domain rule
|
// Only domain rule
|
||||||
SniffHost string `json:"sniffHost"`
|
SniffHost string `json:"sniffHost"`
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/atomic"
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
"github.com/Dreamacro/clash/component/ca"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
@ -23,7 +22,7 @@ type client struct {
|
|||||||
r *Resolver
|
r *Resolver
|
||||||
port string
|
port string
|
||||||
host string
|
host string
|
||||||
iface atomic.TypedValue[string]
|
iface string
|
||||||
proxyAdapter C.ProxyAdapter
|
proxyAdapter C.ProxyAdapter
|
||||||
proxyName string
|
proxyName string
|
||||||
addr string
|
addr string
|
||||||
@ -48,10 +47,6 @@ func (c *client) Address() string {
|
|||||||
return c.addr
|
return c.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
|
|
||||||
return c.ExchangeContext(context.Background(), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||||
var (
|
var (
|
||||||
ip netip.Addr
|
ip netip.Addr
|
||||||
@ -78,8 +73,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var options []dialer.Option
|
var options []dialer.Option
|
||||||
if c.iface.Load() != "" {
|
if c.iface != "" {
|
||||||
options = append(options, dialer.WithInterface(c.iface.Load()))
|
options = append(options, dialer.WithInterface(c.iface))
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
|
conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
|
||||||
|
17
dns/dhcp.go
17
dns/dhcp.go
@ -1,6 +1,3 @@
|
|||||||
//go:build disabled
|
|
||||||
// +build disabled
|
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,11 +8,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/atomic"
|
|
||||||
"github.com/Dreamacro/clash/component/dhcp"
|
"github.com/Dreamacro/clash/component/dhcp"
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +26,7 @@ type dhcpClient struct {
|
|||||||
ifaceInvalidate time.Time
|
ifaceInvalidate time.Time
|
||||||
dnsInvalidate time.Time
|
dnsInvalidate time.Time
|
||||||
|
|
||||||
ifaceAddr *netip.Prefix
|
ifaceAddr netip.Prefix
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
clients []dnsClient
|
clients []dnsClient
|
||||||
err error
|
err error
|
||||||
@ -49,13 +43,6 @@ func (d *dhcpClient) Address() string {
|
|||||||
return strings.Join(addrs, ",")
|
return strings.Join(addrs, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return d.ExchangeContext(ctx, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
clients, err := d.resolve(ctx)
|
clients, err := d.resolve(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,7 +76,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
|
|||||||
for _, item := range dns {
|
for _, item := range dns {
|
||||||
nameserver = append(nameserver, NameServer{
|
nameserver = append(nameserver, NameServer{
|
||||||
Addr: net.JoinHostPort(item.String(), "53"),
|
Addr: net.JoinHostPort(item.String(), "53"),
|
||||||
Interface: atomic.NewTypedValue(d.ifaceName),
|
Interface: d.ifaceName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,11 +157,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
|
|||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange implements the Upstream interface for *dnsOverHTTPS.
|
|
||||||
func (doh *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) {
|
|
||||||
return doh.ExchangeContext(context.Background(), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements the Upstream interface for *dnsOverHTTPS.
|
// Close implements the Upstream interface for *dnsOverHTTPS.
|
||||||
func (doh *dnsOverHTTPS) Close() (err error) {
|
func (doh *dnsOverHTTPS) Close() (err error) {
|
||||||
doh.clientMu.Lock()
|
doh.clientMu.Lock()
|
||||||
|
@ -134,11 +134,6 @@ func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange implements the Upstream interface for *dnsOverQUIC.
|
|
||||||
func (doq *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
return doq.ExchangeContext(context.Background(), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements the Upstream interface for *dnsOverQUIC.
|
// Close implements the Upstream interface for *dnsOverQUIC.
|
||||||
func (doq *dnsOverQUIC) Close() (err error) {
|
func (doq *dnsOverQUIC) Close() (err error) {
|
||||||
doq.connMu.Lock()
|
doq.connMu.Lock()
|
||||||
|
@ -45,7 +45,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ipnetFilter struct {
|
type ipnetFilter struct {
|
||||||
ipnet *netip.Prefix
|
ipnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inf *ipnetFilter) Match(ip netip.Addr) bool {
|
func (inf *ipnetFilter) Match(ip netip.Addr) bool {
|
||||||
|
79
dns/patch.go
79
dns/patch.go
@ -1,79 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
"github.com/Dreamacro/clash/component/dhcp"
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const SystemDNSPlaceholder = "system"
|
|
||||||
|
|
||||||
var systemResolver *Resolver
|
|
||||||
var isolateHandler handler
|
|
||||||
|
|
||||||
var _ dnsClient = (*dhcpClient)(nil)
|
|
||||||
|
|
||||||
type dhcpClient struct {
|
|
||||||
enable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dhcpClient) Address() string {
|
|
||||||
return SystemDNSPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
return d.ExchangeContext(context.Background(), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
if s := systemResolver; s != nil {
|
|
||||||
return s.ExchangeContext(ctx, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, dhcp.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
|
|
||||||
if h := isolateHandler; h != nil {
|
|
||||||
return handlerWithContext(context.Background(), h, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, D.ErrTime
|
|
||||||
}
|
|
||||||
|
|
||||||
func FlushCacheWithDefaultResolver() {
|
|
||||||
if r := resolver.DefaultResolver; r != nil {
|
|
||||||
r.(*Resolver).lruCache = cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateSystemDNS(addr []string) {
|
|
||||||
if len(addr) == 0 {
|
|
||||||
systemResolver = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ns := make([]NameServer, 0, len(addr))
|
|
||||||
for _, d := range addr {
|
|
||||||
ns = append(ns, NameServer{Addr: d})
|
|
||||||
}
|
|
||||||
|
|
||||||
systemResolver = NewResolver(Config{Main: ns})
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
|
|
||||||
if resolver == nil {
|
|
||||||
isolateHandler = nil
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isolateHandler = NewHandler(resolver, mapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDHCPClient(ifaceName string) *dhcpClient {
|
|
||||||
return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder}
|
|
||||||
}
|
|
@ -39,16 +39,12 @@ type rcodeClient struct {
|
|||||||
|
|
||||||
var _ dnsClient = rcodeClient{}
|
var _ dnsClient = rcodeClient{}
|
||||||
|
|
||||||
func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) {
|
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||||
m.Response = true
|
m.Response = true
|
||||||
m.Rcode = r.rcode
|
m.Rcode = r.rcode
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
|
||||||
return r.Exchange(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r rcodeClient) Address() string {
|
func (r rcodeClient) Address() string {
|
||||||
return r.addr
|
return r.addr
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/atomic"
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
@ -23,7 +22,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type dnsClient interface {
|
type dnsClient interface {
|
||||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
|
||||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||||
Address() string
|
Address() string
|
||||||
}
|
}
|
||||||
@ -136,11 +134,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange a batch of dns request, and it use cache
|
|
||||||
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
|
||||||
return r.ExchangeContext(context.Background(), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
||||||
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
if len(m.Question) == 0 {
|
if len(m.Question) == 0 {
|
||||||
@ -210,10 +203,10 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||||
result, cache, err = r.batchExchange(ctx, matched, m)
|
result, cache, err = batchExchange(ctx, matched, m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result, cache, err = r.batchExchange(ctx, r.main, m)
|
result, cache, err = batchExchange(ctx, r.main, m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,13 +248,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return batchExchange(ctx, clients, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||||
if r.policy == nil {
|
if r.policy == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -385,7 +371,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i
|
|||||||
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
|
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
|
||||||
ch := make(chan *result, 1)
|
ch := make(chan *result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
res, _, err := r.batchExchange(ctx, client, msg)
|
res, _, err := batchExchange(ctx, client, msg)
|
||||||
ch <- &result{Msg: res, Error: err}
|
ch <- &result{Msg: res, Error: err}
|
||||||
}()
|
}()
|
||||||
return ch
|
return ch
|
||||||
@ -402,7 +388,7 @@ func (r *Resolver) Invalid() bool {
|
|||||||
type NameServer struct {
|
type NameServer struct {
|
||||||
Net string
|
Net string
|
||||||
Addr string
|
Addr string
|
||||||
Interface atomic.TypedValue[string]
|
Interface string
|
||||||
ProxyAdapter C.ProxyAdapter
|
ProxyAdapter C.ProxyAdapter
|
||||||
ProxyName string
|
ProxyName string
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
@ -412,7 +398,7 @@ type NameServer struct {
|
|||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool
|
GeoIP bool
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
IPCIDR []*netip.Prefix
|
IPCIDR []netip.Prefix
|
||||||
Domain []string
|
Domain []string
|
||||||
GeoSite []*router.DomainMatcher
|
GeoSite []*router.DomainMatcher
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,6 @@ func (s *Server) SetHandler(handler handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
||||||
UpdateIsolateHandler(resolver, mapper)
|
|
||||||
if addr == address && resolver != nil {
|
if addr == address && resolver != nil {
|
||||||
handler := NewHandler(resolver, mapper)
|
handler := NewHandler(resolver, mapper)
|
||||||
server.SetHandler(handler)
|
server.SetHandler(handler)
|
||||||
|
116
dns/system.go
116
dns/system.go
@ -1,23 +1,113 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadSystemResolver() (clients []dnsClient, err error) {
|
const (
|
||||||
nameservers, err := dnsReadConfig()
|
SystemDnsFlushTime = 5 * time.Minute
|
||||||
|
SystemDnsDeleteTimes = 12 // 12*5 = 60min
|
||||||
|
)
|
||||||
|
|
||||||
|
type systemDnsClient struct {
|
||||||
|
disableTimes uint32
|
||||||
|
dnsClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemClient struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
dnsClients map[string]*systemDnsClient
|
||||||
|
lastFlush time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
var err error
|
||||||
|
if time.Since(c.lastFlush) > SystemDnsFlushTime {
|
||||||
|
var nameservers []string
|
||||||
|
if nameservers, err = dnsReadConfig(); err == nil {
|
||||||
|
log.Debugln("[DNS] system dns update to %s", nameservers)
|
||||||
|
for _, addr := range nameservers {
|
||||||
|
if _, ok := c.dnsClients[addr]; !ok {
|
||||||
|
clients := transform(
|
||||||
|
[]NameServer{{
|
||||||
|
Addr: net.JoinHostPort(addr, "53"),
|
||||||
|
Net: "udp",
|
||||||
|
}},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if len(clients) > 0 {
|
||||||
|
c.dnsClients[addr] = &systemDnsClient{
|
||||||
|
disableTimes: 0,
|
||||||
|
dnsClient: clients[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
available := 0
|
||||||
|
for nameserver, sdc := range c.dnsClients {
|
||||||
|
if slices.Contains(nameservers, nameserver) {
|
||||||
|
sdc.disableTimes = 0 // enable
|
||||||
|
available++
|
||||||
|
} else {
|
||||||
|
if sdc.disableTimes > SystemDnsDeleteTimes {
|
||||||
|
delete(c.dnsClients, nameserver) // drop too old dnsClient
|
||||||
|
} else {
|
||||||
|
sdc.disableTimes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if available > 0 {
|
||||||
|
c.lastFlush = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dnsClients := make([]dnsClient, 0, len(c.dnsClients))
|
||||||
|
for _, sdc := range c.dnsClients {
|
||||||
|
if sdc.disableTimes == 0 {
|
||||||
|
dnsClients = append(dnsClients, sdc.dnsClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dnsClients) > 0 {
|
||||||
|
return dnsClients, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
dnsClients, err := c.getDnsClients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(nameservers) == 0 {
|
msg, _, err = batchExchange(ctx, dnsClients, m)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
servers := make([]NameServer, 0, len(nameservers))
|
|
||||||
for _, addr := range nameservers {
|
// Address implements dnsClient
|
||||||
servers = append(servers, NameServer{
|
func (c *systemClient) Address() string {
|
||||||
Addr: net.JoinHostPort(addr, "53"),
|
dnsClients, _ := c.getDnsClients()
|
||||||
Net: "udp",
|
addrs := make([]string, 0, len(dnsClients))
|
||||||
})
|
for _, c := range dnsClients {
|
||||||
}
|
addrs = append(addrs, c.Address())
|
||||||
return transform(servers, nil), nil
|
}
|
||||||
|
return fmt.Sprintf("system(%s)", strings.Join(addrs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ dnsClient = (*systemClient)(nil)
|
||||||
|
|
||||||
|
func newSystemClient() *systemClient {
|
||||||
|
return &systemClient{
|
||||||
|
dnsClients: map[string]*systemDnsClient{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
38
dns/util.go
38
dns/util.go
@ -107,16 +107,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
|||||||
ret = append(ret, newDHCPClient(s.Addr))
|
ret = append(ret, newDHCPClient(s.Addr))
|
||||||
continue
|
continue
|
||||||
case "system":
|
case "system":
|
||||||
clients, err := loadSystemResolver()
|
ret = append(ret, newSystemClient())
|
||||||
if err != nil {
|
|
||||||
log.Errorln("[DNS:system] load system resolver failed: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(clients) == 0 {
|
|
||||||
log.Errorln("[DNS:system] no nameserver found in system")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ret = append(ret, clients...)
|
|
||||||
continue
|
continue
|
||||||
case "rcode":
|
case "rcode":
|
||||||
ret = append(ret, newRCodeClient(s.Addr))
|
ret = append(ret, newRCodeClient(s.Addr))
|
||||||
@ -289,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
|||||||
DstPort: uint16(uintPort),
|
DstPort: uint16(uintPort),
|
||||||
}
|
}
|
||||||
if proxyAdapter == nil {
|
if proxyAdapter == nil {
|
||||||
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !proxyAdapter.SupportUDP() {
|
if !proxyAdapter.SupportUDP() {
|
||||||
@ -299,14 +290,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
|||||||
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errIPNotFound = errors.New("couldn't find ip")
|
||||||
|
|
||||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||||
cache = true
|
cache = true
|
||||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||||
defer fast.Close()
|
defer fast.Close()
|
||||||
domain := msgToDomain(m)
|
domain := msgToDomain(m)
|
||||||
|
var noIpMsg *D.Msg
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
||||||
msg, err = client.Exchange(m)
|
msg, err = client.ExchangeContext(ctx, m)
|
||||||
return msg, false, err
|
return msg, false, err
|
||||||
}
|
}
|
||||||
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
||||||
@ -320,13 +314,31 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
|||||||
// so we would ignore RCode errors from RCode clients.
|
// so we would ignore RCode errors from RCode clients.
|
||||||
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
|
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
|
||||||
}
|
}
|
||||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address())
|
if ips := msgToIP(m); len(m.Question) > 0 {
|
||||||
|
qType := m.Question[0].Qtype
|
||||||
|
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address())
|
||||||
|
switch qType {
|
||||||
|
case D.TypeAAAA:
|
||||||
|
if len(ips) == 0 {
|
||||||
|
noIpMsg = m
|
||||||
|
return nil, errIPNotFound
|
||||||
|
}
|
||||||
|
case D.TypeA:
|
||||||
|
if len(ips) == 0 {
|
||||||
|
noIpMsg = m
|
||||||
|
return nil, errIPNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = fast.Wait()
|
msg = fast.Wait()
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
|
if noIpMsg != nil {
|
||||||
|
return noIpMsg, false, nil
|
||||||
|
}
|
||||||
err = errors.New("all DNS requests failed")
|
err = errors.New("all DNS requests failed")
|
||||||
if fErr := fast.Error(); fErr != nil {
|
if fErr := fast.Error(); fErr != nil {
|
||||||
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
||||||
|
@ -352,16 +352,17 @@ proxies: # socks5
|
|||||||
plugin: v2ray-plugin
|
plugin: v2ray-plugin
|
||||||
plugin-opts:
|
plugin-opts:
|
||||||
mode: websocket # no QUIC now
|
mode: websocket # no QUIC now
|
||||||
# tls: true # wss
|
# tls: true # wss
|
||||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||||
# 配置指纹将实现 SSL Pining 效果
|
# 配置指纹将实现 SSL Pining 效果
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# host: bing.com
|
# host: bing.com
|
||||||
# path: "/"
|
# path: "/"
|
||||||
# mux: true
|
# mux: true
|
||||||
# headers:
|
# headers:
|
||||||
# custom: value
|
# custom: value
|
||||||
|
# v2ray-http-upgrade: false
|
||||||
|
|
||||||
- name: "ss4-shadow-tls"
|
- name: "ss4-shadow-tls"
|
||||||
type: ss
|
type: ss
|
||||||
@ -434,11 +435,12 @@ proxies: # socks5
|
|||||||
# servername: example.com # priority over wss host
|
# servername: example.com # priority over wss host
|
||||||
# network: ws
|
# network: ws
|
||||||
# ws-opts:
|
# ws-opts:
|
||||||
# path: /path
|
# path: /path
|
||||||
# headers:
|
# headers:
|
||||||
# Host: v2ray.com
|
# Host: v2ray.com
|
||||||
# max-early-data: 2048
|
# max-early-data: 2048
|
||||||
# early-data-header-name: Sec-WebSocket-Protocol
|
# early-data-header-name: Sec-WebSocket-Protocol
|
||||||
|
# v2ray-http-upgrade: false
|
||||||
|
|
||||||
- name: "vmess-h2"
|
- name: "vmess-h2"
|
||||||
type: vmess
|
type: vmess
|
||||||
@ -566,6 +568,7 @@ proxies: # socks5
|
|||||||
path: "/"
|
path: "/"
|
||||||
headers:
|
headers:
|
||||||
Host: example.com
|
Host: example.com
|
||||||
|
# v2ray-http-upgrade: false
|
||||||
|
|
||||||
# Trojan
|
# Trojan
|
||||||
- name: "trojan"
|
- name: "trojan"
|
||||||
@ -606,9 +609,10 @@ proxies: # socks5
|
|||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
udp: true
|
udp: true
|
||||||
# ws-opts:
|
# ws-opts:
|
||||||
# path: /path
|
# path: /path
|
||||||
# headers:
|
# headers:
|
||||||
# Host: example.com
|
# Host: example.com
|
||||||
|
# v2ray-http-upgrade: false
|
||||||
|
|
||||||
- name: "trojan-xtls"
|
- name: "trojan-xtls"
|
||||||
type: trojan
|
type: trojan
|
||||||
|
3
go.mod
3
go.mod
@ -23,7 +23,7 @@ require (
|
|||||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
|
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.5
|
github.com/metacubex/sing-shadowsocks v0.2.5
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.1.4
|
github.com/metacubex/sing-shadowsocks2 v0.1.4
|
||||||
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700
|
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
|
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170
|
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170
|
||||||
github.com/miekg/dns v1.1.56
|
github.com/miekg/dns v1.1.56
|
||||||
@ -101,6 +101,7 @@ require (
|
|||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||||
go.uber.org/mock v0.3.0 // indirect
|
go.uber.org/mock v0.3.0 // indirect
|
||||||
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||||
golang.org/x/mod v0.13.0 // indirect
|
golang.org/x/mod v0.13.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
6
go.sum
6
go.sum
@ -107,8 +107,8 @@ github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW
|
|||||||
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
|
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
|
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
|
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
|
||||||
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700 h1:JToLa8cxHrd6tOUHWCg9YM+o/4MXmjgagG909itmnyE=
|
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
|
||||||
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700/go.mod h1:atkIOs6Y5NeUzstK5SBvnrFo4z1JLuORhEfQECEVUpI=
|
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA=
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
|
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
|
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
|
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
|
||||||
@ -211,6 +211,8 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
|||||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||||
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
@ -172,7 +172,7 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
|||||||
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
||||||
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
||||||
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
||||||
// listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
|
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
|
||||||
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
|
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
|
||||||
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
|
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
|
||||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
||||||
|
@ -70,20 +70,22 @@ type tunSchema struct {
|
|||||||
|
|
||||||
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||||
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||||
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||||
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||||
|
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||||
|
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tuicServerSchema struct {
|
type tuicServerSchema struct {
|
||||||
@ -148,6 +150,18 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
|||||||
if p.Inet6Address != nil {
|
if p.Inet6Address != nil {
|
||||||
def.Inet6Address = *p.Inet6Address
|
def.Inet6Address = *p.Inet6Address
|
||||||
}
|
}
|
||||||
|
if p.Inet4RouteAddress != nil {
|
||||||
|
def.Inet4RouteAddress = *p.Inet4RouteAddress
|
||||||
|
}
|
||||||
|
if p.Inet6RouteAddress != nil {
|
||||||
|
def.Inet6RouteAddress = *p.Inet6RouteAddress
|
||||||
|
}
|
||||||
|
if p.Inet4RouteExcludeAddress != nil {
|
||||||
|
def.Inet4RouteExcludeAddress = *p.Inet4RouteExcludeAddress
|
||||||
|
}
|
||||||
|
if p.Inet6RouteExcludeAddress != nil {
|
||||||
|
def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress
|
||||||
|
}
|
||||||
if p.IncludeUID != nil {
|
if p.IncludeUID != nil {
|
||||||
def.IncludeUID = *p.IncludeUID
|
def.IncludeUID = *p.IncludeUID
|
||||||
}
|
}
|
||||||
|
@ -27,20 +27,22 @@ type Tun struct {
|
|||||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||||
RedirectToTun []string `yaml:"-" json:"-"`
|
RedirectToTun []string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||||
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||||
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||||
|
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||||
|
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
func (l *Listener) Listener() net.Listener {
|
|
||||||
return l.listener
|
|
||||||
}
|
|
@ -65,9 +65,6 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t, ok := conn.(*net.TCPConn); ok {
|
|
||||||
t.SetKeepAlive(false)
|
|
||||||
}
|
|
||||||
go HandleConn(conn, tunnel, c, additions...)
|
go HandleConn(conn, tunnel, c, additions...)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -18,22 +18,24 @@ type TunOption struct {
|
|||||||
AutoRoute bool `inbound:"auto-route,omitempty"`
|
AutoRoute bool `inbound:"auto-route,omitempty"`
|
||||||
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
|
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
|
||||||
|
|
||||||
MTU uint32 `inbound:"mtu,omitempty"`
|
MTU uint32 `inbound:"mtu,omitempty"`
|
||||||
Inet4Address []string `inbound:"inet4_address,omitempty"`
|
Inet4Address []string `inbound:"inet4_address,omitempty"`
|
||||||
Inet6Address []string `inbound:"inet6_address,omitempty"`
|
Inet6Address []string `inbound:"inet6_address,omitempty"`
|
||||||
StrictRoute bool `inbound:"strict_route,omitempty"`
|
StrictRoute bool `inbound:"strict_route,omitempty"`
|
||||||
Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"`
|
Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"`
|
||||||
Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"`
|
Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"`
|
||||||
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
|
Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"`
|
||||||
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
|
Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"`
|
||||||
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
|
IncludeUID []uint32 `inbound:"include_uid,omitempty"`
|
||||||
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
|
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
|
||||||
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
|
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
|
||||||
IncludePackage []string `inbound:"include_package,omitempty"`
|
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
|
||||||
ExcludePackage []string `inbound:"exclude_package,omitempty"`
|
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
|
||||||
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
|
IncludePackage []string `inbound:"include_package,omitempty"`
|
||||||
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
|
ExcludePackage []string `inbound:"exclude_package,omitempty"`
|
||||||
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
|
||||||
|
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
|
||||||
|
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o TunOption) Equal(config C.InboundConfig) bool {
|
func (o TunOption) Equal(config C.InboundConfig) bool {
|
||||||
@ -72,32 +74,42 @@ func NewTun(options *TunOption) (*Tun, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &Tun{
|
return &Tun{
|
||||||
Base: base,
|
Base: base,
|
||||||
config: options,
|
config: options,
|
||||||
tun: LC.Tun{
|
tun: LC.Tun{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
Device: options.Device,
|
Device: options.Device,
|
||||||
Stack: stack,
|
Stack: stack,
|
||||||
DNSHijack: options.DNSHijack,
|
DNSHijack: options.DNSHijack,
|
||||||
AutoRoute: options.AutoRoute,
|
AutoRoute: options.AutoRoute,
|
||||||
AutoDetectInterface: options.AutoDetectInterface,
|
AutoDetectInterface: options.AutoDetectInterface,
|
||||||
MTU: options.MTU,
|
MTU: options.MTU,
|
||||||
Inet4Address: inet4Address,
|
Inet4Address: inet4Address,
|
||||||
Inet6Address: inet6Address,
|
Inet6Address: inet6Address,
|
||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
Inet4RouteAddress: inet4RouteAddress,
|
Inet4RouteAddress: inet4RouteAddress,
|
||||||
Inet6RouteAddress: inet6RouteAddress,
|
Inet6RouteAddress: inet6RouteAddress,
|
||||||
IncludeUID: options.IncludeUID,
|
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||||
IncludeUIDRange: options.IncludeUIDRange,
|
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||||
ExcludeUID: options.ExcludeUID,
|
IncludeUID: options.IncludeUID,
|
||||||
ExcludeUIDRange: options.ExcludeUIDRange,
|
IncludeUIDRange: options.IncludeUIDRange,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
ExcludeUID: options.ExcludeUID,
|
||||||
IncludePackage: options.IncludePackage,
|
ExcludeUIDRange: options.ExcludeUIDRange,
|
||||||
ExcludePackage: options.ExcludePackage,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
IncludePackage: options.IncludePackage,
|
||||||
UDPTimeout: options.UDPTimeout,
|
ExcludePackage: options.ExcludePackage,
|
||||||
FileDescriptor: options.FileDescriptor,
|
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||||
|
UDPTimeout: options.UDPTimeout,
|
||||||
|
FileDescriptor: options.FileDescriptor,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ func HandleTcp(address string) (conn net.Conn, err error) {
|
|||||||
metadata.Type = C.INNER
|
metadata.Type = C.INNER
|
||||||
metadata.DNSMode = C.DNSNormal
|
metadata.DNSMode = C.DNSNormal
|
||||||
metadata.Process = C.ClashName
|
metadata.Process = C.ClashName
|
||||||
metadata.RawSrcAddr = conn1.RemoteAddr()
|
|
||||||
metadata.RawDstAddr = conn1.LocalAddr()
|
|
||||||
if h, port, err := net.SplitHostPort(address); err == nil {
|
if h, port, err := net.SplitHostPort(address); err == nil {
|
||||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||||
metadata.DstPort = uint16(port)
|
metadata.DstPort = uint16(port)
|
||||||
|
@ -849,6 +849,14 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
|
|||||||
return tunConf.Inet6RouteAddress[i].String() < tunConf.Inet6RouteAddress[j].String()
|
return tunConf.Inet6RouteAddress[i].String() < tunConf.Inet6RouteAddress[j].String()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sort.Slice(tunConf.Inet4RouteExcludeAddress, func(i, j int) bool {
|
||||||
|
return tunConf.Inet4RouteExcludeAddress[i].String() < tunConf.Inet4RouteExcludeAddress[j].String()
|
||||||
|
})
|
||||||
|
|
||||||
|
sort.Slice(tunConf.Inet6RouteExcludeAddress, func(i, j int) bool {
|
||||||
|
return tunConf.Inet6RouteExcludeAddress[i].String() < tunConf.Inet6RouteExcludeAddress[j].String()
|
||||||
|
})
|
||||||
|
|
||||||
sort.Slice(tunConf.IncludeUID, func(i, j int) bool {
|
sort.Slice(tunConf.IncludeUID, func(i, j int) bool {
|
||||||
return tunConf.IncludeUID[i] < tunConf.IncludeUID[j]
|
return tunConf.IncludeUID[i] < tunConf.IncludeUID[j]
|
||||||
})
|
})
|
||||||
@ -882,6 +890,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
|
|||||||
!slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) ||
|
!slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) ||
|
||||||
!slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||
|
!slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||
|
||||||
!slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) ||
|
!slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) ||
|
||||||
|
!slices.Equal(tunConf.Inet4RouteExcludeAddress, LastTunConf.Inet4RouteExcludeAddress) ||
|
||||||
|
!slices.Equal(tunConf.Inet6RouteExcludeAddress, LastTunConf.Inet6RouteExcludeAddress) ||
|
||||||
!slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) ||
|
!slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) ||
|
||||||
!slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) ||
|
!slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) ||
|
||||||
!slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) ||
|
!slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) ||
|
||||||
|
@ -199,22 +199,24 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunOptions := tun.Options{
|
tunOptions := tun.Options{
|
||||||
Name: tunName,
|
Name: tunName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
Inet4Address: options.Inet4Address,
|
Inet4Address: options.Inet4Address,
|
||||||
Inet6Address: options.Inet6Address,
|
Inet6Address: options.Inet6Address,
|
||||||
AutoRoute: options.AutoRoute,
|
AutoRoute: options.AutoRoute,
|
||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||||
IncludeUID: includeUID,
|
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
||||||
ExcludeUID: excludeUID,
|
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeUID: includeUID,
|
||||||
IncludePackage: options.IncludePackage,
|
ExcludeUID: excludeUID,
|
||||||
ExcludePackage: options.ExcludePackage,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
FileDescriptor: options.FileDescriptor,
|
IncludePackage: options.IncludePackage,
|
||||||
InterfaceMonitor: defaultInterfaceMonitor,
|
ExcludePackage: options.ExcludePackage,
|
||||||
TableIndex: 2022,
|
FileDescriptor: options.FileDescriptor,
|
||||||
|
InterfaceMonitor: defaultInterfaceMonitor,
|
||||||
|
TableIndex: 2022,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.buildAndroidRules(&tunOptions)
|
err = l.buildAndroidRules(&tunOptions)
|
||||||
|
@ -22,7 +22,7 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption {
|
|||||||
|
|
||||||
type IPCIDR struct {
|
type IPCIDR struct {
|
||||||
*Base
|
*Base
|
||||||
ipnet *netip.Prefix
|
ipnet netip.Prefix
|
||||||
adapter string
|
adapter string
|
||||||
isSourceIP bool
|
isSourceIP bool
|
||||||
noResolveIP bool
|
noResolveIP bool
|
||||||
@ -63,7 +63,7 @@ func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error)
|
|||||||
|
|
||||||
ipcidr := &IPCIDR{
|
ipcidr := &IPCIDR{
|
||||||
Base: &Base{},
|
Base: &Base{},
|
||||||
ipnet: &ipnet,
|
ipnet: ipnet,
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,11 @@ func ruleParse(ruleRaw string) (string, string, []string) {
|
|||||||
} else if len(item) == 2 {
|
} else if len(item) == 2 {
|
||||||
return item[0], item[1], nil
|
return item[0], item[1], nil
|
||||||
} else if len(item) > 2 {
|
} else if len(item) > 2 {
|
||||||
return item[0], item[1], item[2:]
|
if item[0] == "NOT" || item[0] == "OR" || item[0] == "AND" || item[0] == "SUB-RULE" {
|
||||||
|
return item[0], strings.Join(item[1:len(item)], ","), nil
|
||||||
|
} else {
|
||||||
|
return item[0], item[1], item[2:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", nil
|
return "", "", nil
|
||||||
@ -85,7 +89,7 @@ func ruleParse(ruleRaw string) (string, string, []string) {
|
|||||||
func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy {
|
func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy {
|
||||||
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||||
switch tp {
|
switch tp {
|
||||||
case "MATCH", "SUB-RULE":
|
case "MATCH":
|
||||||
return nil, fmt.Errorf("unsupported rule type on rule-set")
|
return nil, fmt.Errorf("unsupported rule type on rule-set")
|
||||||
default:
|
default:
|
||||||
return parse(tp, payload, target, params, nil)
|
return parse(tp, payload, target, params, nil)
|
||||||
|
@ -62,11 +62,15 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
|
|||||||
case "http":
|
case "http":
|
||||||
if schema.Path != "" {
|
if schema.Path != "" {
|
||||||
path := C.Path.Resolve(schema.Path)
|
path := C.Path.Resolve(schema.Path)
|
||||||
|
if !C.Path.IsSafePath(path) {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||||
|
}
|
||||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||||
} else {
|
} else {
|
||||||
path := C.Path.GetPathByHash("rules", schema.URL)
|
path := C.Path.GetPathByHash("rules", schema.URL)
|
||||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
var (
|
|
||||||
suspended bool
|
|
||||||
)
|
|
||||||
|
|
||||||
type UpdatableProvider interface {
|
|
||||||
UpdatedAt() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *ruleSetProvider) UpdatedAt() time.Time {
|
|
||||||
return f.Fetcher.UpdatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *ruleSetProvider) Close() error {
|
|
||||||
rp.Fetcher.Destroy()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Suspend(s bool) {
|
|
||||||
suspended = s
|
|
||||||
}
|
|
@ -209,11 +209,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
|||||||
if realityConfig == nil {
|
if realityConfig == nil {
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||||
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
if err := utlsConn.HandshakeContext(ctx); err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
state := utlsConn.(*tlsC.UConn).ConnectionState()
|
state := utlsConn.ConnectionState()
|
||||||
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||||
utlsConn.Close()
|
utlsConn.Close()
|
||||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||||
|
@ -55,10 +55,11 @@ type Option struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketOption struct {
|
type WebsocketOption struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
Path string
|
Path string
|
||||||
Headers http.Header
|
Headers http.Header
|
||||||
|
V2rayHttpUpgrade bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Trojan struct {
|
type Trojan struct {
|
||||||
@ -132,6 +133,7 @@ func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptio
|
|||||||
Port: wsOptions.Port,
|
Port: wsOptions.Port,
|
||||||
Path: wsOptions.Path,
|
Path: wsOptions.Path,
|
||||||
Headers: wsOptions.Headers,
|
Headers: wsOptions.Headers,
|
||||||
|
V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade,
|
||||||
TLS: true,
|
TLS: true,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
ClientFingerprint: t.option.ClientFingerprint,
|
ClientFingerprint: t.option.ClientFingerprint,
|
||||||
|
@ -12,14 +12,15 @@ import (
|
|||||||
|
|
||||||
// Option is options of websocket obfs
|
// Option is options of websocket obfs
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
Path string
|
Path string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
TLS bool
|
TLS bool
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
Mux bool
|
Mux bool
|
||||||
|
V2rayHttpUpgrade bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewV2rayObfs return a HTTPObfs
|
// NewV2rayObfs return a HTTPObfs
|
||||||
@ -30,10 +31,11 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := &vmess.WebsocketConfig{
|
config := &vmess.WebsocketConfig{
|
||||||
Host: option.Host,
|
Host: option.Host,
|
||||||
Port: option.Port,
|
Port: option.Port,
|
||||||
Path: option.Path,
|
Path: option.Path,
|
||||||
Headers: header,
|
V2rayHttpUpgrade: option.V2rayHttpUpgrade,
|
||||||
|
Headers: header,
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"github.com/gobwas/ws"
|
"github.com/gobwas/ws"
|
||||||
"github.com/gobwas/ws/wsutil"
|
"github.com/gobwas/ws/wsutil"
|
||||||
@ -56,6 +58,7 @@ type WebsocketConfig struct {
|
|||||||
MaxEarlyData int
|
MaxEarlyData int
|
||||||
EarlyDataHeaderName string
|
EarlyDataHeaderName string
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
|
V2rayHttpUpgrade bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read implements net.Conn.Read()
|
// Read implements net.Conn.Read()
|
||||||
@ -317,48 +320,80 @@ func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) {
|
func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) {
|
||||||
dialer := ws.Dialer{
|
|
||||||
NetDial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
},
|
|
||||||
TLSConfig: c.TLSConfig,
|
|
||||||
}
|
|
||||||
scheme := "ws"
|
|
||||||
if c.TLS {
|
|
||||||
scheme = "wss"
|
|
||||||
if len(c.ClientFingerprint) != 0 {
|
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists {
|
|
||||||
utlsConn := tlsC.UClient(conn, c.TLSConfig, fingerprint)
|
|
||||||
|
|
||||||
if err := utlsConn.(*tlsC.UConn).BuildWebsocketHandshakeState(); err != nil {
|
|
||||||
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer.TLSClient = func(conn net.Conn, hostname string) net.Conn {
|
|
||||||
return utlsConn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(c.Path)
|
u, err := url.Parse(c.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := url.URL{
|
uri := url.URL{
|
||||||
Scheme: scheme,
|
Scheme: "ws",
|
||||||
Host: net.JoinHostPort(c.Host, c.Port),
|
Host: net.JoinHostPort(c.Host, c.Port),
|
||||||
Path: u.Path,
|
Path: u.Path,
|
||||||
RawQuery: u.RawQuery,
|
RawQuery: u.RawQuery,
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := http.Header{}
|
if c.TLS {
|
||||||
headers.Set("User-Agent", "Go-http-client/1.1") // match golang's net/http
|
uri.Scheme = "wss"
|
||||||
if c.Headers != nil {
|
config := c.TLSConfig
|
||||||
for k := range c.Headers {
|
if config == nil { // The config cannot be nil
|
||||||
headers.Add(k, c.Headers.Get(k))
|
config = &tls.Config{NextProtos: []string{"http/1.1"}}
|
||||||
}
|
}
|
||||||
|
if config.ServerName == "" && !config.InsecureSkipVerify { // users must set either ServerName or InsecureSkipVerify in the config.
|
||||||
|
config = config.Clone()
|
||||||
|
config.ServerName = uri.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.ClientFingerprint) != 0 {
|
||||||
|
if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists {
|
||||||
|
utlsConn := tlsC.UClient(conn, config, fingerprint)
|
||||||
|
if err = utlsConn.BuildWebsocketHandshakeState(); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
|
||||||
|
}
|
||||||
|
conn = utlsConn
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conn = tls.Client(conn, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConn, ok := conn.(interface {
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
}); ok {
|
||||||
|
if err = tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: &uri,
|
||||||
|
Header: c.Headers.Clone(),
|
||||||
|
Host: c.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Header.Set("Connection", "Upgrade")
|
||||||
|
request.Header.Set("Upgrade", "websocket")
|
||||||
|
|
||||||
|
if host := request.Header.Get("Host"); host != "" {
|
||||||
|
// For client requests, Host optionally overrides the Host
|
||||||
|
// header to send. If empty, the Request.Write method uses
|
||||||
|
// the value of URL.Host. Host may contain an international
|
||||||
|
// domain name.
|
||||||
|
request.Host = host
|
||||||
|
}
|
||||||
|
request.Header.Del("Host")
|
||||||
|
|
||||||
|
var secKey string
|
||||||
|
if !c.V2rayHttpUpgrade {
|
||||||
|
const nonceKeySize = 16
|
||||||
|
// NOTE: bts does not escape.
|
||||||
|
bts := make([]byte, nonceKeySize)
|
||||||
|
if _, err = fastrand.Read(bts); err != nil {
|
||||||
|
return nil, fmt.Errorf("rand read error: %w", err)
|
||||||
|
}
|
||||||
|
secKey = base64.StdEncoding.EncodeToString(bts)
|
||||||
|
request.Header.Set("Sec-WebSocket-Version", "13")
|
||||||
|
request.Header.Set("Sec-WebSocket-Key", secKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if earlyData != nil {
|
if earlyData != nil {
|
||||||
@ -366,39 +401,61 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
|
|||||||
if c.EarlyDataHeaderName == "" {
|
if c.EarlyDataHeaderName == "" {
|
||||||
uri.Path += earlyDataString
|
uri.Path += earlyDataString
|
||||||
} else {
|
} else {
|
||||||
headers.Set(c.EarlyDataHeaderName, earlyDataString)
|
request.Header.Set(c.EarlyDataHeaderName, earlyDataString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// gobwas/ws will check server's response "Sec-Websocket-Protocol" so must add Protocols to ws.Dialer
|
if ctx.Done() != nil {
|
||||||
// if not will cause ws.ErrHandshakeBadSubProtocol
|
done := N.SetupContextForConn(ctx, conn)
|
||||||
if secProtocol := headers.Get("Sec-WebSocket-Protocol"); len(secProtocol) > 0 {
|
defer done(&err)
|
||||||
// gobwas/ws will set "Sec-Websocket-Protocol" according dialer.Protocols
|
|
||||||
// to avoid send repeatedly don't set it to headers
|
|
||||||
headers.Del("Sec-WebSocket-Protocol")
|
|
||||||
dialer.Protocols = []string{secProtocol}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// gobwas/ws send "Host" directly in Upgrade() by `httpWriteHeader(bw, headerHost, u.Host)`
|
err = request.Write(conn)
|
||||||
// if headers has "Host" will send repeatedly
|
|
||||||
if host := headers.Get("Host"); host != "" {
|
|
||||||
headers.Del("Host")
|
|
||||||
uri.Host = host
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer.Header = ws.HandshakeHeaderHTTP(headers)
|
|
||||||
|
|
||||||
conn, reader, _, err := dialer.Dial(ctx, uri.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dial %s error: %w", uri.Host, err)
|
return nil, err
|
||||||
|
}
|
||||||
|
bufferedConn := N.NewBufferedConn(conn)
|
||||||
|
response, err := http.ReadResponse(bufferedConn.Reader(), request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusSwitchingProtocols ||
|
||||||
|
!strings.EqualFold(response.Header.Get("Connection"), "upgrade") ||
|
||||||
|
!strings.EqualFold(response.Header.Get("Upgrade"), "websocket") {
|
||||||
|
return nil, fmt.Errorf("unexpected status: %s", response.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn = newWebsocketConn(conn, reader, ws.StateClientSide)
|
if c.V2rayHttpUpgrade {
|
||||||
|
return bufferedConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if log.Level() == log.DEBUG { // we might not check this for performance
|
||||||
|
secAccept := response.Header.Get("Sec-Websocket-Accept")
|
||||||
|
const acceptSize = 28 // base64.StdEncoding.EncodedLen(sha1.Size)
|
||||||
|
if lenSecAccept := len(secAccept); lenSecAccept != acceptSize {
|
||||||
|
return nil, fmt.Errorf("unexpected Sec-Websocket-Accept length: %d", lenSecAccept)
|
||||||
|
}
|
||||||
|
if getSecAccept(secKey) != secAccept {
|
||||||
|
return nil, errors.New("unexpected Sec-Websocket-Accept")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = newWebsocketConn(conn, ws.StateClientSide)
|
||||||
// websocketConn can't correct handle ReadDeadline
|
// websocketConn can't correct handle ReadDeadline
|
||||||
// so call N.NewDeadlineConn to add a safe wrapper
|
// so call N.NewDeadlineConn to add a safe wrapper
|
||||||
return N.NewDeadlineConn(conn), nil
|
return N.NewDeadlineConn(conn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSecAccept(secKey string) string {
|
||||||
|
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
|
const nonceSize = 24 // base64.StdEncoding.EncodedLen(nonceKeySize)
|
||||||
|
p := make([]byte, nonceSize+len(magic))
|
||||||
|
copy(p[:nonceSize], secKey)
|
||||||
|
copy(p[nonceSize:], magic)
|
||||||
|
sum := sha1.Sum(p)
|
||||||
|
return base64.StdEncoding.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||||
if u, err := url.Parse(c.Path); err == nil {
|
if u, err := url.Parse(c.Path); err == nil {
|
||||||
if q := u.Query(); q.Get("ed") != "" {
|
if q := u.Query(); q.Get("ed") != "" {
|
||||||
@ -419,19 +476,13 @@ func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig)
|
|||||||
return streamWebsocketConn(ctx, conn, c, nil)
|
return streamWebsocketConn(ctx, conn, c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebsocketConn(conn net.Conn, br *bufio.Reader, state ws.State) *websocketConn {
|
func newWebsocketConn(conn net.Conn, state ws.State) *websocketConn {
|
||||||
controlHandler := wsutil.ControlFrameHandler(conn, state)
|
controlHandler := wsutil.ControlFrameHandler(conn, state)
|
||||||
var reader io.Reader
|
|
||||||
if br != nil && br.Buffered() > 0 {
|
|
||||||
reader = br
|
|
||||||
} else {
|
|
||||||
reader = conn
|
|
||||||
}
|
|
||||||
return &websocketConn{
|
return &websocketConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
state: state,
|
state: state,
|
||||||
reader: &wsutil.Reader{
|
reader: &wsutil.Reader{
|
||||||
Source: reader,
|
Source: conn,
|
||||||
State: state,
|
State: state,
|
||||||
SkipHeaderCheck: true,
|
SkipHeaderCheck: true,
|
||||||
CheckUTF8: false,
|
CheckUTF8: false,
|
||||||
@ -458,23 +509,53 @@ func decodeXray0rtt(requestHeader http.Header) []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsWebSocketUpgrade(r *http.Request) bool {
|
||||||
|
return r.Header.Get("Upgrade") == "websocket"
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsV2rayHttpUpdate(r *http.Request) bool {
|
||||||
|
return IsWebSocketUpgrade(r) && r.Header.Get("Sec-WebSocket-Key") == ""
|
||||||
|
}
|
||||||
|
|
||||||
func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
|
func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
|
||||||
wsConn, rw, _, err := ws.UpgradeHTTP(r, w)
|
var conn net.Conn
|
||||||
|
var rw *bufio.ReadWriter
|
||||||
|
var err error
|
||||||
|
isRaw := IsV2rayHttpUpdate(r)
|
||||||
|
w.Header().Set("Connection", "upgrade")
|
||||||
|
w.Header().Set("Upgrade", "websocket")
|
||||||
|
if !isRaw {
|
||||||
|
w.Header().Set("Sec-Websocket-Accept", getSecAccept(r.Header.Get("Sec-WebSocket-Key")))
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||||
|
if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher {
|
||||||
|
err = flusher.FlushError()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("flush response: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hijacker, canHijack := w.(http.Hijacker)
|
||||||
|
if !canHijack {
|
||||||
|
return nil, errors.New("invalid connection, maybe HTTP/2")
|
||||||
|
}
|
||||||
|
conn, rw, err = hijacker.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("hijack failed: %w", err)
|
||||||
}
|
}
|
||||||
conn := newWebsocketConn(wsConn, rw.Reader, ws.StateServerSide)
|
|
||||||
|
// rw.Writer was flushed, so we only need warp rw.Reader
|
||||||
|
conn = N.WarpConnWithBioReader(conn, rw.Reader)
|
||||||
|
|
||||||
|
if !isRaw {
|
||||||
|
conn = newWebsocketConn(conn, ws.StateServerSide)
|
||||||
|
// websocketConn can't correct handle ReadDeadline
|
||||||
|
// so call N.NewDeadlineConn to add a safe wrapper
|
||||||
|
conn = N.NewDeadlineConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
if edBuf := decodeXray0rtt(r.Header); len(edBuf) > 0 {
|
if edBuf := decodeXray0rtt(r.Header); len(edBuf) > 0 {
|
||||||
return N.NewDeadlineConn(&websocketWithReaderConn{conn, io.MultiReader(bytes.NewReader(edBuf), conn)}), nil
|
conn = N.NewCachedConn(conn, edBuf)
|
||||||
}
|
}
|
||||||
return N.NewDeadlineConn(conn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type websocketWithReaderConn struct {
|
return conn, nil
|
||||||
*websocketConn
|
|
||||||
reader io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *websocketWithReaderConn) Read(b []byte) (n int, err error) {
|
|
||||||
return ws.reader.Read(b)
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package statistic
|
|
||||||
|
|
||||||
func (m *Manager) Total() (up, down int64) {
|
|
||||||
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -46,8 +47,6 @@ var (
|
|||||||
findProcessMode P.FindProcessMode
|
findProcessMode P.FindProcessMode
|
||||||
|
|
||||||
fakeIPRange netip.Prefix
|
fakeIPRange netip.Prefix
|
||||||
|
|
||||||
procesCache string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tunnel struct{}
|
type tunnel struct{}
|
||||||
@ -594,8 +593,8 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
configMux.RLock()
|
configMux.RLock()
|
||||||
defer configMux.RUnlock()
|
defer configMux.RUnlock()
|
||||||
var (
|
var (
|
||||||
resolved bool
|
resolved bool
|
||||||
processFound bool
|
attemptProcessLookup = true
|
||||||
)
|
)
|
||||||
|
|
||||||
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
||||||
@ -619,17 +618,15 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !findProcessMode.Off() && !processFound && (findProcessMode.Always() || rule.ShouldFindProcess()) {
|
if attemptProcessLookup && !findProcessMode.Off() && (findProcessMode.Always() || rule.ShouldFindProcess()) {
|
||||||
pkg, err := P.FindPackageName(metadata)
|
attemptProcessLookup = false
|
||||||
|
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
||||||
} else {
|
} else {
|
||||||
metadata.Process = pkg
|
metadata.Process = filepath.Base(path)
|
||||||
processFound = true
|
metadata.ProcessPath = path
|
||||||
if procesCache != metadata.Process {
|
metadata.Uid = uid
|
||||||
log.Debugln("[Process] %s from process %s", metadata.String(), metadata.Process)
|
|
||||||
}
|
|
||||||
procesCache = metadata.Process
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user