Compare commits

...

137 Commits

Author SHA1 Message Date
63917aa020 fix: uuid-map return failed error 2022-05-14 23:45:10 +08:00
5016f529af revert: yaml v2 2022-05-14 23:36:19 +08:00
5bd5f1bfda chore: remove Script mode residual code. 2022-05-14 13:00:33 +08:00
d4dcbce9cb chore: log show all ips when all ips shake hands failed 2022-05-13 21:43:42 +08:00
df8196a68c fix: print process path logic 2022-05-12 18:57:30 +08:00
c1631759fc feat: add strategy:sticky-sessions for LoadBalance
Signed-off-by: Meta <maze.y2b@gmail.com>
2022-05-09 18:56:36 +08:00
9e9c3c810f fixed: make log api unblocked 2022-05-09 18:54:00 +08:00
463101aec1 fix: limit load provider concurrent size 2022-05-08 22:52:46 +08:00
2072964701 revert: tls handshake timeout recovery 10s 2022-05-08 21:56:59 +08:00
aded1b78b5 chore: sniffer give the err to the caller 2022-05-08 09:09:39 +08:00
ca9c859059 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-05-08 07:59:17 +08:00
55811dae32 fix: Adjust the timing of loading proxy selection 2022-05-08 07:58:26 +08:00
7136d145f8 chore: update dependencies 2022-05-08 00:47:01 +08:00
2fbbf7519f fix: provider auto update 2022-05-08 00:04:16 +08:00
663bf4fbb0 fix: remove misjudgment 2022-05-07 12:53:13 +08:00
f0a22a4a4c chore: modify sniff error log 2022-05-07 12:44:28 +08:00
4ab91520bd refactor: reuse uuid namespace 2022-05-07 12:35:14 +08:00
980d8a2641 refactor: string map to uuidv5 2022-05-06 14:02:34 +08:00
a95d439852 chore: the uuid-map is transferred to the protocol 2022-05-06 13:28:09 +08:00
a08e39faec fix uuid match 2022-05-06 13:08:27 +08:00
b3295262c1 chore: Initialize provider ahead of time 2022-05-05 21:14:46 +08:00
27aa026568 fix: use actual metadata 2022-05-04 20:13:12 +08:00
9969e1706e fix: loadbalance group npe 2022-05-04 19:52:48 +08:00
fb58595d44 feat: Expose remote destination (udp proxy maybe domain of node) 2022-05-04 16:57:08 +08:00
bdfa16ca6f fix: wrong parameters 2022-05-04 01:04:43 +08:00
b307bcb4a9 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	listener/tun/ipstack/commons/router_linux.go
2022-05-04 01:04:11 +08:00
f26941091b fix: default router with fakeIP when tun enable 2022-05-04 00:59:04 +08:00
6cd5769ed7 fix: default router with fakeIP when tun enable 2022-05-04 00:36:44 +08:00
41adfa65b3 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	listener/tun/ipstack/commons/router_linux.go
2022-05-03 23:59:41 +08:00
3fbb7c7a2d chore: add default router when tun enable 2022-05-03 23:58:11 +08:00
0aa82c04ea chore: add default router 198.18.0.0/16 when tun enable 2022-05-03 23:28:02 +08:00
5c6f2694c7 chore: sniffer param skip-sni renamed to ship-domain, old param will be removed in the release version 2022-05-03 23:10:59 +08:00
eca7615f08 fix: patch update support tun 2022-05-03 19:31:00 +08:00
52d559bb38 feat: rule-provider support NetWork rule 2022-05-03 01:36:03 +08:00
259736390a feat: rule-provider support rules field 2022-05-03 00:53:22 +08:00
7db07630a7 fix: DNS mapping error when sniffing result is ip, Discard sniffs that result in ip 2022-05-02 22:24:14 +08:00
d617b0f447 style: uid log tidy 2022-05-02 19:52:34 +08:00
80ff5917f7 fix: The sniffer does not clean up the original address 2022-05-02 17:09:24 +08:00
b401da5eba refactor: provider init order 2022-05-02 16:47:48 +08:00
05b25c334f Merge branch 'makefile' into Alpha 2022-05-02 14:43:51 +08:00
2c5a47a275 fix: Failed to get version tag 2022-05-02 14:43:01 +08:00
b2605a9012 fix: tun dns 2022-05-02 14:21:37 +08:00
b929a19f48 refactor: Unified active health detection, supported by load balancing policy group 2022-05-02 13:50:10 +08:00
4b04faa88b fix: http sniffer return host that was handled correctly 2022-05-02 09:51:26 +08:00
5fee0b5bf1 chore: adjust pass to reject.go 2022-05-02 09:16:47 +08:00
27120fb0f5 Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-05-02 08:49:23 +08:00
0cf539fb82 chore: adjust sniffer constant 2022-05-02 08:49:18 +08:00
26a38bd8de chore: adjust sniffer constant 2022-05-02 08:46:24 +08:00
ebbce4d061 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-05-02 08:28:00 +08:00
5acd2f6c3a chore: workflow 2022-05-02 08:27:17 +08:00
fe2bc903b8 fix trojan and snell's normal udp 2022-05-02 06:28:27 +08:00
658f1f5cda fix code mistake 2022-05-02 05:34:20 +08:00
5ccc047fe4 chore: adjust sniffer err info 2022-05-02 05:17:13 +08:00
6d704b9cd1 feat: sniffer support http 2022-05-02 05:10:18 +08:00
5f957b5cf9 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	.github/workflows/prerelease.yml
#	.github/workflows/release.yaml
2022-05-02 05:01:33 +08:00
8681728b8d chore: doq parameters 2022-05-02 05:01:07 +08:00
bb1eb5979b chore: Merge alpha and beta 2022-05-02 01:14:30 +08:00
032b6a2cc5 chore: workflow 2022-05-02 00:59:41 +08:00
45a02e3439 chore: workflow 2022-05-02 00:19:28 +08:00
9d8cd036ff refactor: remove dns and tun relationship, the enabled of dns module should be decided by user 2022-05-01 09:41:27 +08:00
2aad9818e8 fix trojan and snell's udp over tcp 2022-04-30 22:26:38 +08:00
4d5c0d2bf4 fix: auto-route priority wlan0 in Android 2022-04-30 17:43:37 +08:00
861205dbbe support udp in relay if last proxy could udp-over-tcp 2022-04-30 11:36:42 +08:00
9dbe20f2c5 fix: npe when with resolver is nil 2022-04-29 13:03:55 +08:00
8dea62cd63 refactor: del useless file 2022-04-28 23:49:24 +08:00
2d99f86780 fix: dhcp ifacename type 2022-04-28 23:44:37 +08:00
fac63625fc fix: replace with sync.map for GroupBase 2022-04-28 23:43:10 +08:00
b9f270162a refactor: field name 2022-04-28 23:10:08 +08:00
bbbe371ea9 fix: dns specified interface does not change 2022-04-28 22:40:06 +08:00
e2409f9639 Merge branch 'doq' into Alpha 2022-04-28 22:24:02 +08:00
4f634edaa9 refactor: doq dialer 2022-04-28 22:21:48 +08:00
f1dab9e9ce refactor: optimize the performance of filter in proxy-group 2022-04-28 19:01:13 +08:00
8217db82ce Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-28 17:47:29 +08:00
f9dd0a6bfc [Skip CI] chore: add docker workflow 2022-04-28 14:24:38 +08:00
b9b25be68d Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	.github/workflows/docker.yaml
2022-04-28 14:19:25 +08:00
179e5aad58 chore: add docker workflow 2022-04-28 14:18:54 +08:00
fc1eaf9a9d chore: add docker workflow 2022-04-28 14:11:35 +08:00
1756730693 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-04-28 12:44:51 +08:00
c7bc67e44c fix: handle metadata when dst is ip:port 2022-04-28 12:44:27 +08:00
949c1551f8 fixup! chore: system err log 2022-04-28 12:37:53 +08:00
b23a333acf Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-28 11:51:57 +08:00
47568051bf fix: problems caused when uid is 0 2022-04-28 11:51:40 +08:00
22c1e05e1c fix: rule provider http api crash 2022-04-28 09:44:29 +08:00
c161d5e6be fix: inner request error 2022-04-28 09:24:40 +08:00
573be855f7 fix: file not change modify time when updated rule 2022-04-28 09:07:58 +08:00
a38b2bcb6d Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-04-28 08:56:00 +08:00
2e74986fe4 refactor: adjust provider loading order, remove meaningless pointers 2022-04-28 08:55:45 +08:00
30eaa8add6 fix: count error 2022-04-28 08:54:33 +08:00
b498d2dda2 chore: system err log 2022-04-27 22:54:12 +08:00
96a32f5038 refactor: tcp concurrent 2022-04-27 21:37:20 +08:00
e11f6f84df chore: adjust workflows 2022-04-27 18:05:03 +08:00
5a1e1050b7 chore: adjust sniffer log 2022-04-27 18:04:02 +08:00
73aa8c7be7 feat: support uuid with custom string 2022-04-27 18:02:29 +08:00
183973e823 chore: Adjust the tcp-concurrent and sniffer log 2022-04-27 15:22:42 +08:00
2e08a4b4e1 fix: undefined parameter 2022-04-27 14:42:58 +08:00
564a6fdf35 Chore: http 2022-04-27 12:52:11 +08:00
cca3a1a934 Fix: http proxy Upgrade behavior (#2097) 2022-04-27 12:38:31 +08:00
ffb49ba4c5 fix: gvisor panic 2022-04-25 18:39:45 +08:00
e4fb10faf9 Chore: update dependencies 2022-04-25 13:23:56 +08:00
16a35527b7 Chore: increase nattable capacity 2022-04-25 13:18:41 +08:00
4fd7d0f707 Chore: use generics as possible 2022-04-25 13:18:30 +08:00
dee1aeb6c3 fix: logic of auto-detect-interface 2022-04-23 23:42:42 +08:00
2f95d56a12 pref: uid style in log 2022-04-23 17:37:50 +08:00
c77993eed3 refactor: tidy auto-route code 2022-04-23 17:31:16 +08:00
ce663a7b96 fix: ipv6 enable logic 2022-04-23 14:21:58 +08:00
9c2ca0feb0 fix: uid match 2022-04-23 13:37:37 +08:00
b8d5321615 feat: cache uid 2022-04-23 12:11:26 +08:00
eb6f7e3158 fix: relay conn error when addr is domain 2022-04-23 10:26:22 +08:00
0947cb4a5a fix: whitelist 2022-04-23 09:52:23 +08:00
0368bb4180 fix: sniffer port whitelist error 2022-04-23 09:36:11 +08:00
4aeac0e227 chore: Adjust the connection IP log 2022-04-23 08:53:51 +08:00
bd3c493c9f fix: ipv6 enable logic 2022-04-23 02:03:10 +08:00
0a99fc4d74 fix: wrong parameter name 2022-04-23 00:45:43 +08:00
19fc70b2c4 fix: general ipv6 is false should be broke ipv6 conn 2022-04-23 00:30:25 +08:00
81b5543b0d feat: support tcp concurrent, Separate dialing and dns resolver ipv6
tcp-concurrent:true
2022-04-23 00:27:22 +08:00
2e1d9a4f2e fix: hotspot for android 2022-04-22 22:25:45 +08:00
de4341c8cd Revert: "fix: proxy-groups filter logic"
This reverts commit 8a85c63b08.
2022-04-22 18:56:35 +08:00
8a85c63b08 fix: proxy-groups filter logic 2022-04-22 17:27:55 +08:00
b0dd74e74e fix: sniffer 2022-04-22 17:00:39 +08:00
4dd9e199b7 fix: uid rule only support linux and android 2022-04-22 16:51:01 +08:00
3d6aea4c1e feat: support uid rule
eg. UID,1000/5000-6000,Proxy
2022-04-22 16:27:51 +08:00
0cb5270452 Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-22 15:58:57 +08:00
3f6d2e5f91 feat: dnsHijack support "any"
chore: adjust process debug display logic
2022-04-22 13:30:04 +08:00
3b16fcef92 Chore: wait for system stack to close 2022-04-22 12:44:51 +08:00
f91d106cdf Chore: fix typos 2022-04-22 12:42:20 +08:00
9e6ba64940 fix: add wait timeout, and log 2022-04-21 08:08:37 -07:00
bee1bddceb feat: add sniffer port whitelist, when empty will add all ports 2022-04-21 07:06:08 -07:00
e98dcc4267 [fix] logic 2022-04-21 18:56:33 +08:00
4b79f8de93 [fix] auto-route for android 2022-04-21 17:47:04 +08:00
f40c2eb71d Chore: update dependencies 2022-04-20 22:55:30 +08:00
7ca1a03d73 Refactor: metadata use netip.Addr 2022-04-20 22:52:05 +08:00
6c4791480e Chore: IpToAddr 2022-04-20 22:09:16 +08:00
42d853a7e6 chore: upgrade dependencies 2022-04-20 01:31:33 +08:00
5d36d8b139 Improve: replace bootstrap dns (#2080) 2022-04-19 22:49:39 +08:00
5a4441c47f Chore: add none alias to dummy on ShadowsocksR (#2056) 2022-04-19 22:49:22 +08:00
0ca10798ea Chore: fix typo 2022-04-19 22:38:20 +08:00
3ea3653d7a Chore: persistence fakeip pool state 2022-04-19 22:37:47 +08:00
151 changed files with 3445 additions and 1611 deletions

61
.github/workflows/docker.yaml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Docker
on:
push:
branches:
- Beta
tags:
- "v*"
env:
REGISTRY: docker.io
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -1,8 +1,15 @@
name: Alpha name: Prerelease
on: [push] on:
push:
branches:
- Alpha
- Beta
pull_request:
branches:
- Alpha
- Beta
jobs: jobs:
Feature-build: Build:
if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Get latest go version - name: Get latest go version
@ -24,9 +31,13 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go- ${{ runner.os }}-go-
# - name: Get dependencies, run test
# run: |
# go test ./... - name: Test
if: ${{github.ref_name=='Beta'}}
run: |
go test ./...
- name: Build - name: Build
if: success() if: success()
env: env:
@ -38,34 +49,22 @@ jobs:
uses: andreaswilli/delete-release-assets-action@v2.0.0 uses: andreaswilli/delete-release-assets-action@v2.0.0
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
tag: alpha tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false deleteOnlyFromDrafts: false
- name: Tag Repo - name: Tag Repo
uses: richardsimko/update-tag@v1 uses: richardsimko/update-tag@v1
with: with:
tag_name: alpha tag_name: Prerelease-${{ github.ref_name }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Alpha - name: Upload Alpha
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: ${{ env.GIT_BRANCH != 'Meta' && success() }} if: ${{ success() }}
with: with:
tag: ${{ github.ref }} tag: ${{ github.ref_name }}
tag_name: alpha tag_name: Prerelease-${{ github.ref_name }}
files: bin/* files: bin/*
prerelease: true prerelease: true
generate_release_notes: true
- name: send telegram message on push
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TTELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_TOKEN }}
message: |
${{ github.actor }} created commit:
Commit message: ${{ github.event.commits[0].message }}
Repository: ${{ github.repository }}
See changes: https://github.com/${{ github.repository }}/commit/${{github.sha}}

44
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Release
on:
push:
tags:
- "v*"
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Test
run: |
go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j releases
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() && startsWith(github.ref, 'refs/tags/')}}
with:
tag: ${{ github.ref }}
files: bin/*
generate_release_notes: true

View File

@ -1,18 +1,26 @@
FROM golang:alpine as builder FROM golang:alpine as builder
RUN apk add --no-cache make git && \ RUN apk add --no-cache make git && \
wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb mkdir /clash-config && \
WORKDIR /clash-src wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
COPY --from=tonistiigi/xx:golang / / wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
COPY . /clash-src COPY . /clash-src
RUN go mod download && \ WORKDIR /clash-src
make docker && \ RUN go mod download &&\
mv ./bin/clash-docker /clash make docker &&\
mv ./bin/Clash.Meta-docker /clash
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash" LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
RUN apk add --no-cache ca-certificates tzdata RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash / VOLUME ["/root/.config/clash/"]
ENTRYPOINT ["/clash"]
COPY --from=builder /clash-config/ /root/.config/clash/
COPY --from=builder /clash /clash
RUN chmod +x /clash
ENTRYPOINT [ "/clash" ]

View File

@ -1,7 +1,16 @@
NAME=Clash.Meta NAME=Clash.Meta
BINDIR=bin BINDIR=bin
BRANCH=$(shell git rev-parse --abbrev-ref HEAD) BRANCH=$(shell git branch --show-current)
ifeq ($(BRANCH),Alpha)
VERSION=alpha-$(shell git rev-parse --short HEAD) VERSION=alpha-$(shell git rev-parse --short HEAD)
else ifeq ($(BRANCH),Beta)
VERSION=beta-$(shell git rev-parse --short HEAD)
else ifeq ($(BRANCH),)
VERSION=$(shell git describe --tags)
else
VERSION=unknown
endif
BUILDTIME=$(shell date -u) BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
@ -43,7 +52,7 @@ all:linux-amd64 linux-arm64\
windows-amd64 windows-arm64\ windows-amd64 windows-arm64\
docker: docker:
$(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64v3: darwin-amd64v3:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -64,9 +65,9 @@ func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
// DelayHistory implements C.Proxy // DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory { func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy() queueM := p.history.Copy()
histories := []C.DelayHistory{} histories := []C.DelayHistory{}
for _, item := range queue { for _, item := range queueM {
histories = append(histories, item) histories = append(histories, item)
} }
return histories return histories
@ -95,7 +96,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
} }
mapping := map[string]any{} mapping := map[string]any{}
json.Unmarshal(inner, &mapping) _ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP() mapping["udp"] = p.SupportUDP()
@ -129,7 +130,9 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
if err != nil { if err != nil {
return return
} }
defer instance.Close() defer func() {
_ = instance.Close()
}()
req, err := http.NewRequest(http.MethodHead, url, nil) req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil { if err != nil {
@ -138,7 +141,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
transport := &http.Transport{ transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) { DialContext: func(context.Context, string, string) (net.Conn, error) {
return instance, nil return instance, nil
}, },
// from http.DefaultTransport // from http.DefaultTransport
@ -167,8 +170,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
return return
} }
} }
_ = resp.Body.Close()
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond) t = uint16(time.Since(start) / time.Millisecond)
return return
} }
@ -199,7 +201,7 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
addr = C.Metadata{ addr = C.Metadata{
AddrType: C.AtypDomainName, AddrType: C.AtypDomainName,
Host: u.Hostname(), Host: u.Hostname(),
DstIP: nil, DstIP: netip.Addr{},
DstPort: port, DstPort: port,
} }
return return

View File

@ -2,6 +2,7 @@ package inbound
import ( import (
"net" "net"
"net/netip"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/context"
@ -33,14 +34,15 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
metadata.Host = host metadata.Host = host
metadata.AddrType = C.AtypDomainName metadata.AddrType = C.AtypDomainName
metadata.Process = C.ClashName metadata.Process = C.ClashName
if ip, port, err := parseAddr(dst); err == nil { if h, port, err := net.SplitHostPort(dst); err == nil {
metadata.DstPort = port metadata.DstPort = port
if host == "" { if host == "" {
metadata.DstIP = ip if ip, err := netip.ParseAddr(h); err == nil {
if ip.To4() == nil { metadata.DstIP = ip
metadata.AddrType = C.AtypIPv6
} else {
metadata.AddrType = C.AtypIPv4 metadata.AddrType = C.AtypIPv4
if ip.Is6() {
metadata.AddrType = C.AtypIPv6
}
} }
} }
} }

View File

@ -3,9 +3,11 @@ package inbound
import ( import (
"net" "net"
"net/http" "net/http"
"net/netip"
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
@ -21,12 +23,10 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4: case socks5.AtypIPv4:
ip := net.IP(target[1 : 1+net.IPv4len]) metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
metadata.DstIP = ip
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6: case socks5.AtypIPv6:
ip := net.IP(target[1 : 1+net.IPv6len]) metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv6len]))
metadata.DstIP = ip
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
} }
@ -47,14 +47,14 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
NetWork: C.TCP, NetWork: C.TCP,
AddrType: C.AtypDomainName, AddrType: C.AtypDomainName,
Host: host, Host: host,
DstIP: nil, DstIP: netip.Addr{},
DstPort: port, DstPort: port,
} }
ip := net.ParseIP(host) ip, err := netip.ParseAddr(host)
if ip != nil { if err == nil {
switch { switch {
case ip.To4() == nil: case ip.Is6():
metadata.AddrType = C.AtypIPv6 metadata.AddrType = C.AtypIPv6
default: default:
metadata.AddrType = C.AtypIPv4 metadata.AddrType = C.AtypIPv4
@ -65,12 +65,12 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata return metadata
} }
func parseAddr(addr string) (net.IP, string, error) { func parseAddr(addr string) (netip.Addr, string, error) {
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, "", err return netip.Addr{}, "", err
} }
ip := net.ParseIP(host) ip, err := netip.ParseAddr(host)
return ip, port, nil return ip, port, err
} }

View File

@ -39,6 +39,16 @@ func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op
return nil, errors.New("no support") return nil, errors.New("no support")
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (b *Base) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
return nil, errors.New("no support")
}
// SupportUOT implements C.ProxyAdapter
func (b *Base) SupportUOT() bool {
return false
}
// SupportUDP implements C.ProxyAdapter // SupportUDP implements C.ProxyAdapter
func (b *Base) SupportUDP() bool { func (b *Base) SupportUDP() bool {
return b.udp return b.udp

View File

@ -56,13 +56,3 @@ func NewCompatible() *Direct {
}, },
} }
} }
func NewPass() *Direct {
return &Direct{
Base: &Base{
name: "PASS",
tp: C.Pass,
udp: true,
},
}
}

View File

@ -34,6 +34,16 @@ func NewReject() *Reject {
} }
} }
func NewPass() *Reject {
return &Reject{
Base: &Base{
name: "PASS",
tp: C.Pass,
udp: true,
},
}
}
type nopConn struct{} type nopConn struct{}
func (rw *nopConn) Read(b []byte) (int, error) { func (rw *nopConn) Read(b []byte) (int, error) {

View File

@ -92,6 +92,12 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
} }
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility
// https://github.com/Dreamacro/clash/pull/2056
if option.Cipher == "none" {
option.Cipher = "dummy"
}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher cipher := option.Cipher
password := option.Password password := option.Password
@ -103,13 +109,14 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
ivSize int ivSize int
key []byte key []byte
) )
if option.Cipher == "dummy" { if option.Cipher == "dummy" {
ivSize = 0 ivSize = 0
key = core.Kdf(option.Password, 16) key = core.Kdf(option.Password, 16)
} else { } else {
ciph, ok := coreCiph.(*core.StreamCipher) ciph, ok := coreCiph.(*core.StreamCipher)
if !ok { if !ok {
return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher) return nil, fmt.Errorf("%s is not none or a supported stream cipher in ssr", cipher)
} }
ivSize = ciph.IVSize() ivSize = ciph.IVSize()
key = ciph.Key key = ciph.Key

View File

@ -53,6 +53,10 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
if metadata.NetWork == C.UDP {
err := snell.WriteUDPHeader(c, s.version)
return c, err
}
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16) port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version) err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
return c, err return c, err
@ -104,6 +108,17 @@ func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
return newPacketConn(pc, s), nil return newPacketConn(pc, s), nil
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (s *Snell) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
}
// SupportUOT implements C.ProxyAdapter
func (s *Snell) SupportUOT() bool {
return true
}
func NewSnell(option SnellOption) (*Snell, error) { func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk) psk := []byte(option.Psk)

View File

@ -13,8 +13,6 @@ import (
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan" "github.com/Dreamacro/clash/transport/trojan"
"github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vless"
"golang.org/x/net/http2"
) )
type Trojan struct { type Trojan struct {
@ -25,7 +23,7 @@ type Trojan struct {
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *http2.Transport transport *gun.TransportWrap
} }
type TrojanOption struct { type TrojanOption struct {
@ -90,6 +88,10 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return nil, err return nil, err
} }
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err return c, err
} }
@ -166,6 +168,17 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
}
// SupportUOT implements C.ProxyAdapter
func (t *Trojan) SupportUOT() bool {
return true
}
func NewTrojan(option TrojanOption) (*Trojan, error) { func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))

View File

@ -22,8 +22,8 @@ var (
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) _ = tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(30 * time.Second) _ = tcp.SetKeepAlivePeriod(30 * time.Second)
} }
} }
@ -48,14 +48,14 @@ func serializesSocksAddr(metadata *C.Metadata) []byte {
port := []byte{uint8(p >> 8), uint8(p & 0xff)} port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType { switch metadata.AddrType {
case socks5.AtypDomainName: case socks5.AtypDomainName:
len := uint8(len(metadata.Host)) lenM := uint8(len(metadata.Host))
host := []byte(metadata.Host) host := []byte(metadata.Host)
buf = [][]byte{{aType, len}, host, port} buf = [][]byte{{aType, lenM}, host, port}
case socks5.AtypIPv4: case socks5.AtypIPv4:
host := metadata.DstIP.To4() host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port} buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6: case socks5.AtypIPv6:
host := metadata.DstIP.To16() host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port} buf = [][]byte{{aType}, host, port}
} }
return bytes.Join(buf, nil) return bytes.Join(buf, nil)
@ -76,6 +76,6 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
func safeConnClose(c net.Conn, err error) { func safeConnClose(c net.Conn, err error) {
if err != nil { if err != nil {
c.Close() _ = c.Close()
} }
} }

View File

@ -18,8 +18,6 @@ import (
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2"
) )
const ( const (
@ -35,7 +33,7 @@ type Vless struct {
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *http2.Transport transport *gun.TransportWrap
} }
type VlessOption struct { type VlessOption struct {
@ -249,26 +247,36 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
return nil, fmt.Errorf("new vless client error: %v", err) return nil, fmt.Errorf("new vless client error: %v", err)
} }
return v.ListenPacketOnStreamConn(c, metadata)
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
} }
// SupportUOT implements C.ProxyAdapter
func (v *Vless) SupportUOT() bool {
return true
}
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
var addrType byte var addrType byte
var addr []byte var addr []byte
switch metadata.AddrType { switch metadata.AddrType {
case C.AtypIPv4: case C.AtypIPv4:
addrType = byte(vless.AtypIPv4) addrType = vless.AtypIPv4
addr = make([]byte, net.IPv4len) addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.To4()) copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypIPv6: case C.AtypIPv6:
addrType = byte(vless.AtypIPv6) addrType = vless.AtypIPv6
addr = make([]byte, net.IPv6len) addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.To16()) copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypDomainName: case C.AtypDomainName:
addrType = byte(vless.AtypDomainName) addrType = vless.AtypDomainName
addr = make([]byte, len(metadata.Host)+1) addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host)) addr[0] = byte(len(metadata.Host))
copy(addr[1:], []byte(metadata.Host)) copy(addr[1:], metadata.Host)
} }
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16) port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)

View File

@ -15,8 +15,6 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
"golang.org/x/net/http2"
) )
type Vmess struct { type Vmess struct {
@ -27,7 +25,7 @@ type Vmess struct {
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *http2.Transport transport *gun.TransportWrap
} }
type VmessOption struct { type VmessOption struct {
@ -260,9 +258,19 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
return v.ListenPacketOnStreamConn(c, metadata)
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
} }
// SupportUOT implements C.ProxyAdapter
func (v *Vmess) SupportUOT() bool {
return true
}
func NewVmess(option VmessOption) (*Vmess, error) { func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher) security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{ client, err := vmess.NewClient(vmess.Config{
@ -342,11 +350,11 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
case C.AtypIPv4: case C.AtypIPv4:
addrType = byte(vmess.AtypIPv4) addrType = byte(vmess.AtypIPv4)
addr = make([]byte, net.IPv4len) addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.To4()) copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypIPv6: case C.AtypIPv6:
addrType = byte(vmess.AtypIPv6) addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len) addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.To16()) copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypDomainName: case C.AtypDomainName:
addrType = byte(vmess.AtypDomainName) addrType = byte(vmess.AtypDomainName)
addr = make([]byte, len(metadata.Host)+1) addr = make([]byte, len(metadata.Host)+1)

View File

@ -1,55 +0,0 @@
package outboundgroup
import (
"github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2"
"time"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
const (
defaultGetProxiesDuration = time.Second * 5
)
func getProvidersProxies(providers []provider.ProxyProvider, touch bool, filter string) []C.Proxy {
proxies := []C.Proxy{}
for _, provider := range providers {
if touch {
proxies = append(proxies, provider.ProxiesWithTouch()...)
} else {
proxies = append(proxies, provider.Proxies()...)
}
}
var filterReg *regexp2.Regexp = nil
var matchedProxies []C.Proxy
if len(filter) > 0 {
//filterReg = regexp.MustCompile(filter)
filterReg = regexp2.MustCompile(filter, 0)
for _, p := range proxies {
if p.Type() < 8 {
matchedProxies = append(matchedProxies, p)
}
//if filterReg.MatchString(p.Name()) {
if mat, _ := filterReg.FindStringMatch(p.Name()); mat != nil {
matchedProxies = append(matchedProxies, p)
}
}
if len(matchedProxies) > 0 {
return matchedProxies
} else {
return append([]C.Proxy{}, tunnel.Proxies()["COMPATIBLE"])
}
} else {
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
} else {
return proxies
}
}
}

View File

@ -3,25 +3,15 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/Dreamacro/clash/log"
"go.uber.org/atomic"
"time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
) )
type Fallback struct { type Fallback struct {
*outbound.Base *GroupBase
disableUDP bool disableUDP bool
filter string
single *singledo.Single
providers []provider.ProxyProvider
failedTimes *atomic.Int32
failedTime *atomic.Int64
} }
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
@ -35,8 +25,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(f) c.AppendToChains(f)
f.failedTimes.Store(-1) f.onDialSuccess()
f.failedTime.Store(-1)
} else { } else {
f.onDialFailed() f.onDialFailed()
} }
@ -50,8 +39,7 @@ func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...) pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
pc.AppendToChains(f) pc.AppendToChains(f)
f.failedTimes.Store(-1) f.onDialSuccess()
f.failedTime.Store(-1)
} else { } else {
f.onDialFailed() f.onDialFailed()
} }
@ -59,32 +47,6 @@ func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata
return pc, err return pc, err
} }
func (f *Fallback) onDialFailed() {
if f.failedTime.Load() == -1 {
log.Warnln("%s first failed", f.Name())
now := time.Now().UnixMilli()
f.failedTime.Store(now)
f.failedTimes.Store(1)
} else {
if f.failedTime.Load()-time.Now().UnixMilli() > 5*time.Second.Milliseconds() {
f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} else {
failedCount := f.failedTimes.Inc()
log.Warnln("%s failed count: %d", f.Name(), failedCount)
if failedCount >= 5 {
log.Warnln("because %s failed multiple times, active health check", f.Name())
for _, proxyProvider := range f.providers {
go proxyProvider.HealthCheck()
}
f.failedTimes.Store(-1)
f.failedTime.Store(-1)
}
}
}
}
// SupportUDP implements C.ProxyAdapter // SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool { func (f *Fallback) SupportUDP() bool {
if f.disableUDP { if f.disableUDP {
@ -98,7 +60,7 @@ func (f *Fallback) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
all := []string{} all := []string{}
for _, proxy := range f.proxies(false) { for _, proxy := range f.GetProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -114,16 +76,8 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
return proxy return proxy
} }
func (f *Fallback) proxies(touch bool) []C.Proxy {
elm, _, _ := f.single.Do(func() (any, error) {
return getProvidersProxies(f.providers, touch, f.filter), nil
})
return elm.([]C.Proxy)
}
func (f *Fallback) findAliveProxy(touch bool) C.Proxy { func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.proxies(touch) proxies := f.GetProxies(touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if proxy.Alive() { if proxy.Alive() {
return proxy return proxy
@ -135,17 +89,16 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{ return &Fallback{
Base: outbound.NewBase(outbound.BaseOption{ GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name, outbound.BaseOption{
Type: C.Fallback, Name: option.Name,
Interface: option.Interface, Type: C.Fallback,
RoutingMark: option.RoutingMark, Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), disableUDP: option.DisableUDP,
providers: providers,
disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1),
} }
} }

View File

@ -0,0 +1,144 @@
package outboundgroup
import (
"github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2"
"go.uber.org/atomic"
"sync"
"time"
)
type GroupBase struct {
*outbound.Base
filter *regexp2.Regexp
providers []provider.ProxyProvider
versions sync.Map // map[string]uint
proxies sync.Map // map[string][]C.Proxy
failedTimes *atomic.Int32
failedTime *atomic.Int64
}
type GroupBaseOption struct {
outbound.BaseOption
filter string
providers []provider.ProxyProvider
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
var filter *regexp2.Regexp = nil
if opt.filter != "" {
filter = regexp2.MustCompile(opt.filter, 0)
}
return &GroupBase{
Base: outbound.NewBase(opt.BaseOption),
filter: filter,
providers: opt.providers,
failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1),
}
}
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
if gb.filter == nil {
var proxies []C.Proxy
for _, pd := range gb.providers {
if touch {
proxies = append(proxies, pd.ProxiesWithTouch()...)
} else {
proxies = append(proxies, pd.Proxies()...)
}
}
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
}
return proxies
}
for _, pd := range gb.providers {
if pd.VehicleType() == types.Compatible {
if touch {
gb.proxies.Store(pd.Name(), pd.ProxiesWithTouch())
} else {
gb.proxies.Store(pd.Name(), pd.Proxies())
}
gb.versions.Store(pd.Name(), pd.Version())
continue
}
if version, ok := gb.versions.Load(pd.Name()); !ok || version != pd.Version() {
var (
proxies []C.Proxy
newProxies []C.Proxy
)
if touch {
proxies = pd.ProxiesWithTouch()
} else {
proxies = pd.Proxies()
}
for _, p := range proxies {
if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil {
newProxies = append(newProxies, p)
}
}
gb.proxies.Store(pd.Name(), newProxies)
gb.versions.Store(pd.Name(), pd.Version())
}
}
var proxies []C.Proxy
gb.proxies.Range(func(key, value any) bool {
proxies = append(proxies, value.([]C.Proxy)...)
return true
})
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
}
return proxies
}
func (gb *GroupBase) onDialFailed() {
if gb.failedTime.Load() == -1 {
log.Warnln("%s first failed", gb.Name())
now := time.Now().UnixMilli()
gb.failedTime.Store(now)
gb.failedTimes.Store(1)
} else {
if gb.failedTime.Load()-time.Now().UnixMilli() > gb.failedIntervalTime() {
gb.failedTimes.Store(-1)
gb.failedTime.Store(-1)
} else {
failedCount := gb.failedTimes.Inc()
log.Warnln("%s failed count: %d", gb.Name(), failedCount)
if failedCount >= gb.maxFailedTimes() {
log.Warnln("because %s failed multiple times, active health check", gb.Name())
for _, proxyProvider := range gb.providers {
go proxyProvider.HealthCheck()
}
gb.failedTimes.Store(-1)
gb.failedTime.Store(-1)
}
}
}
}
func (gb *GroupBase) failedIntervalTime() int64 {
return 5 * time.Second.Milliseconds()
}
func (gb *GroupBase) onDialSuccess() {
gb.failedTimes.Store(-1)
gb.failedTime.Store(-1)
}
func (gb *GroupBase) maxFailedTimes() int32 {
return 5
}

View File

@ -5,11 +5,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/rand"
"net" "net"
"time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@ -20,11 +21,8 @@ import (
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
type LoadBalance struct { type LoadBalance struct {
*outbound.Base *GroupBase
disableUDP bool disableUDP bool
single *singledo.Single
filter string
providers []provider.ProxyProvider
strategyFn strategyFn strategyFn strategyFn
} }
@ -40,6 +38,10 @@ func parseStrategy(config map[string]any) string {
} }
func getKey(metadata *C.Metadata) string { func getKey(metadata *C.Metadata) string {
if metadata == nil {
return ""
}
if metadata.Host != "" { if metadata.Host != "" {
// ip host // ip host
if ip := net.ParseIP(metadata.Host); ip != nil { if ip := net.ParseIP(metadata.Host); ip != nil {
@ -51,7 +53,7 @@ func getKey(metadata *C.Metadata) string {
} }
} }
if metadata.DstIP == nil { if !metadata.DstIP.IsValid() {
return "" return ""
} }
@ -75,6 +77,9 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
defer func() { defer func() {
if err == nil { if err == nil {
c.AppendToChains(lb) c.AppendToChains(lb)
lb.onDialSuccess()
} else {
lb.onDialFailed()
} }
}() }()
@ -134,24 +139,70 @@ func strategyConsistentHashing() strategyFn {
} }
} }
// Unwrap implements C.ProxyAdapter func strategyStickySessions() strategyFn {
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { timeout := int64(600)
proxies := lb.proxies(true) type Session struct {
return lb.strategyFn(proxies, metadata) idx int
time time.Time
}
Sessions := make(map[string]map[string]Session)
go func() {
for true {
time.Sleep(time.Second * 60)
now := time.Now().Unix()
for _, subMap := range Sessions {
for dest, session := range subMap {
if now-session.time.Unix() > timeout {
delete(subMap, dest)
}
}
}
}
}()
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
src := metadata.SrcIP.String()
dest := getKey(metadata)
now := time.Now()
length := len(proxies)
if Sessions[src] == nil {
Sessions[src] = make(map[string]Session)
}
session, ok := Sessions[src][dest]
if !ok || now.Unix()-session.time.Unix() > timeout {
session.idx = rand.Intn(length)
}
session.time = now
var i int
var res C.Proxy
for i := 0; i < length; i++ {
idx := (session.idx + i) % length
proxy := proxies[idx]
if proxy.Alive() {
session.idx = idx
res = proxy
break
}
}
if i == length {
session.idx = 0
res = proxies[0]
}
Sessions[src][dest] = session
return res
}
} }
func (lb *LoadBalance) proxies(touch bool) []C.Proxy { // Unwrap implements C.ProxyAdapter
elm, _, _ := lb.single.Do(func() (any, error) { func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
return getProvidersProxies(lb.providers, touch, lb.filter), nil proxies := lb.GetProxies(true)
}) return lb.strategyFn(proxies, metadata)
return elm.([]C.Proxy)
} }
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
all := []string{} var all []string
for _, proxy := range lb.proxies(false) { for _, proxy := range lb.GetProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -167,20 +218,23 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
strategyFn = strategyConsistentHashing() strategyFn = strategyConsistentHashing()
case "round-robin": case "round-robin":
strategyFn = strategyRoundRobin() strategyFn = strategyRoundRobin()
case "sticky-sessions":
strategyFn = strategyStickySessions()
default: default:
return nil, fmt.Errorf("%w: %s", errStrategy, strategy) return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
} }
return &LoadBalance{ return &LoadBalance{
Base: outbound.NewBase(outbound.BaseOption{ GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name, outbound.BaseOption{
Type: C.LoadBalance, Name: option.Name,
Interface: option.Interface, Type: C.LoadBalance,
RoutingMark: option.RoutingMark, Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
strategyFn: strategyFn, strategyFn: strategyFn,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
}, nil }, nil
} }

View File

@ -6,27 +6,18 @@ import (
"fmt" "fmt"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
) )
type Relay struct { type Relay struct {
*outbound.Base *GroupBase
single *singledo.Single
providers []provider.ProxyProvider
filter string
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
var proxies []C.Proxy proxies, chainProxies := r.proxies(metadata, true)
for _, proxy := range r.proxies(metadata, true) {
if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
proxies = append(proxies, proxy)
}
}
switch len(proxies) { switch len(proxies) {
case 0: case 0:
@ -64,13 +55,86 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
} }
return outbound.NewConn(c, r), nil conn := outbound.NewConn(c, last)
for i := len(chainProxies) - 2; i >= 0; i-- {
conn.AppendToChains(chainProxies[i])
}
conn.AppendToChains(r)
return conn, nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1:
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
first := proxies[0]
last := proxies[len(proxies)-1]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
}
c, err = last.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
}
var pc C.PacketConn
pc, err = last.ListenPacketOnStreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
for i := len(chainProxies) - 2; i >= 0; i-- {
pc.AppendToChains(chainProxies[i])
}
pc.AppendToChains(r)
return pc, nil
}
// SupportUDP implements C.ProxyAdapter
func (r *Relay) SupportUDP() bool {
proxies, _ := r.proxies(nil, false)
if len(proxies) == 0 { // C.Direct
return true
}
last := proxies[len(proxies)-1]
return last.SupportUDP() && last.SupportUOT()
} }
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) { func (r *Relay) MarshalJSON() ([]byte, error) {
all := []string{} all := []string{}
for _, proxy := range r.rawProxies(false) { for _, proxy := range r.GetProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -79,38 +143,49 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
}) })
} }
func (r *Relay) rawProxies(touch bool) []C.Proxy { func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) {
elm, _, _ := r.single.Do(func() (any, error) { rawProxies := r.GetProxies(touch)
return getProvidersProxies(r.providers, touch, r.filter), nil
})
return elm.([]C.Proxy) var proxies []C.Proxy
} var chainProxies []C.Proxy
var targetProxies []C.Proxy
func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { for n, proxy := range rawProxies {
proxies := r.rawProxies(touch) proxies = append(proxies, proxy)
chainProxies = append(chainProxies, proxy)
for n, proxy := range proxies {
subproxy := proxy.Unwrap(metadata) subproxy := proxy.Unwrap(metadata)
for subproxy != nil { for subproxy != nil {
chainProxies = append(chainProxies, subproxy)
proxies[n] = subproxy proxies[n] = subproxy
subproxy = subproxy.Unwrap(metadata) subproxy = subproxy.Unwrap(metadata)
} }
} }
return proxies for _, proxy := range proxies {
if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
targetProxies = append(targetProxies, proxy)
}
}
return targetProxies, chainProxies
}
func (r *Relay) Addr() string {
proxies, _ := r.proxies(nil, true)
return proxies[len(proxies)-1].Addr()
} }
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{ return &Relay{
Base: outbound.NewBase(outbound.BaseOption{ GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name, outbound.BaseOption{
Type: C.Relay, Name: option.Name,
Interface: option.Interface, Type: C.Relay,
RoutingMark: option.RoutingMark, Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
"",
providers,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
filter: option.Filter,
} }
} }

View File

@ -6,19 +6,15 @@ import (
"errors" "errors"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
) )
type Selector struct { type Selector struct {
*outbound.Base *GroupBase
disableUDP bool disableUDP bool
single *singledo.Single
selected string selected string
filter string
providers []provider.ProxyProvider
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -51,7 +47,7 @@ func (s *Selector) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
all := []string{} all := []string{}
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) { for _, proxy := range s.GetProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
@ -67,10 +63,9 @@ func (s *Selector) Now() string {
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
for _, proxy := range getProvidersProxies(s.providers, false, s.filter) { for _, proxy := range s.GetProxies(false) {
if proxy.Name() == name { if proxy.Name() == name {
s.selected = name s.selected = name
s.single.Reset()
return nil return nil
} }
} }
@ -84,32 +79,29 @@ func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
} }
func (s *Selector) selectedProxy(touch bool) C.Proxy { func (s *Selector) selectedProxy(touch bool) C.Proxy {
elm, _, _ := s.single.Do(func() (any, error) { proxies := s.GetProxies(touch)
proxies := getProvidersProxies(s.providers, touch, s.filter) for _, proxy := range proxies {
for _, proxy := range proxies { if proxy.Name() == s.selected {
if proxy.Name() == s.selected { return proxy
return proxy, nil
}
} }
}
return proxies[0], nil return proxies[0]
})
return elm.(C.Proxy)
} }
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
return &Selector{ return &Selector{
Base: outbound.NewBase(outbound.BaseOption{ GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name, outbound.BaseOption{
Type: C.Selector, Name: option.Name,
Interface: option.Interface, Type: C.Selector,
RoutingMark: option.RoutingMark, Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
selected: "COMPATIBLE", selected: "COMPATIBLE",
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
filter: option.Filter,
} }
} }

View File

@ -3,8 +3,6 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/Dreamacro/clash/log"
"go.uber.org/atomic"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
@ -23,16 +21,11 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
} }
type URLTest struct { type URLTest struct {
*outbound.Base *GroupBase
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
fastNode C.Proxy fastNode C.Proxy
filter string fastSingle *singledo.Single[C.Proxy]
single *singledo.Single
fastSingle *singledo.Single
providers []provider.ProxyProvider
failedTimes *atomic.Int32
failedTime *atomic.Int64
} }
func (u *URLTest) Now() string { func (u *URLTest) Now() string {
@ -57,11 +50,11 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
pc.AppendToChains(u) pc.AppendToChains(u)
u.failedTimes.Store(-1) u.onDialSuccess()
u.failedTime.Store(-1)
} else { } else {
u.onDialFailed() u.onDialFailed()
} }
return pc, err return pc, err
} }
@ -70,17 +63,9 @@ func (u *URLTest) Unwrap(*C.Metadata) C.Proxy {
return u.fast(true) return u.fast(true)
} }
func (u *URLTest) proxies(touch bool) []C.Proxy {
elm, _, _ := u.single.Do(func() (any, error) {
return getProvidersProxies(u.providers, touch, u.filter), nil
})
return elm.([]C.Proxy)
}
func (u *URLTest) fast(touch bool) C.Proxy { func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (any, error) { elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) {
proxies := u.proxies(touch) proxies := u.GetProxies(touch)
fast := proxies[0] fast := proxies[0]
min := fast.LastDelay() min := fast.LastDelay()
fastNotExist := true fastNotExist := true
@ -109,7 +94,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
return u.fastNode, nil return u.fastNode, nil
}) })
return elm.(C.Proxy) return elm
} }
// SupportUDP implements C.ProxyAdapter // SupportUDP implements C.ProxyAdapter
@ -124,7 +109,7 @@ func (u *URLTest) SupportUDP() bool {
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
all := []string{} all := []string{}
for _, proxy := range u.proxies(false) { for _, proxy := range u.GetProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -134,32 +119,6 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
}) })
} }
func (u *URLTest) onDialFailed() {
if u.failedTime.Load() == -1 {
log.Warnln("%s first failed", u.Name())
now := time.Now().UnixMilli()
u.failedTime.Store(now)
u.failedTimes.Store(1)
} else {
if u.failedTime.Load()-time.Now().UnixMilli() > 5*1000 {
u.failedTimes.Store(-1)
u.failedTime.Store(-1)
} else {
failedCount := u.failedTimes.Inc()
log.Warnln("%s failed count: %d", u.Name(), failedCount)
if failedCount >= 5 {
log.Warnln("because %s failed multiple times, active health check", u.Name())
for _, proxyProvider := range u.providers {
go proxyProvider.HealthCheck()
}
u.failedTimes.Store(-1)
u.failedTime.Store(-1)
}
}
}
}
func parseURLTestOption(config map[string]any) []urlTestOption { func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{} opts := []urlTestOption{}
@ -175,19 +134,19 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{ urlTest := &URLTest{
Base: outbound.NewBase(outbound.BaseOption{ GroupBase: NewGroupBase(GroupBaseOption{
Name: option.Name, outbound.BaseOption{
Type: C.URLTest, Name: option.Name,
Interface: option.Interface, Type: C.URLTest,
RoutingMark: option.RoutingMark, Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
fastSingle: singledo.NewSingle(time.Second * 10), disableUDP: option.DisableUDP,
providers: providers,
disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1),
} }
for _, option := range options { for _, option := range options {

View File

@ -3,6 +3,7 @@ package outboundgroup
import ( import (
"fmt" "fmt"
"net" "net"
"net/netip"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -15,20 +16,21 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
return return
} }
ip := net.ParseIP(host) ip, err := netip.ParseAddr(host)
if ip == nil { if err != nil {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypDomainName, AddrType: C.AtypDomainName,
Host: host, Host: host,
DstIP: nil, DstIP: netip.Addr{},
DstPort: port, DstPort: port,
} }
err = nil
return return
} else if ip4 := ip.To4(); ip4 != nil { } else if ip.Is4() {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypIPv4, AddrType: C.AtypIPv4,
Host: "", Host: "",
DstIP: ip4, DstIP: ip,
DstPort: port, DstPort: port,
} }
return return
@ -45,7 +47,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true) _ = tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(30 * time.Second) _ = tcp.SetKeepAlivePeriod(30 * time.Second)
} }
} }

View File

@ -43,6 +43,14 @@ func (f *fetcher) Initial() (any, error) {
err error err error
isLocal bool isLocal bool
) )
defer func() {
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
}()
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path()) buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime() modTime := stat.ModTime()
@ -84,11 +92,6 @@ func (f *fetcher) Initial() (any, error) {
f.hash = md5.Sum(buf) f.hash = md5.Sum(buf)
// pull proxies automatically
if f.ticker != nil {
go f.pullLoop()
}
return proxies, nil return proxies, nil
} }

View File

@ -65,14 +65,14 @@ func (hc *HealthCheck) touch() {
} }
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10)) b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
p := proxy p := proxy
b.Go(p.Name(), func() (any, error) { b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel() defer cancel()
p.URLTest(ctx, hc.url) _, _ = p.URLTest(ctx, hc.url)
return nil, nil return false, nil
}) })
} }
b.Wait() b.Wait()

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"math"
"runtime" "runtime"
"time" "time"
@ -32,6 +33,11 @@ type proxySetProvider struct {
*fetcher *fetcher
proxies []C.Proxy proxies []C.Proxy
healthCheck *HealthCheck healthCheck *HealthCheck
version uint
}
func (pp *proxySetProvider) Version() uint {
return pp.version
} }
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
@ -94,6 +100,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
if pp.healthCheck.auto() { if pp.healthCheck.auto() {
go pp.healthCheck.check() go pp.healthCheck.check()
} }
} }
func stopProxyProvider(pd *ProxySetProvider) { func stopProxyProvider(pd *ProxySetProvider) {
@ -116,6 +123,11 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
onUpdate := func(elm any) { onUpdate := func(elm any) {
ret := elm.([]C.Proxy) ret := elm.([]C.Proxy)
pd.setProxies(ret) pd.setProxies(ret)
if pd.version == math.MaxUint {
pd.version = 0
} else {
pd.version++
}
} }
proxiesParseAndFilter := func(buf []byte) (any, error) { proxiesParseAndFilter := func(buf []byte) (any, error) {
@ -170,6 +182,11 @@ type compatibleProvider struct {
name string name string
healthCheck *HealthCheck healthCheck *HealthCheck
proxies []C.Proxy proxies []C.Proxy
version uint
}
func (cp *compatibleProvider) Version() uint {
return cp.version
} }
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {

View File

@ -2,7 +2,6 @@ package provider
import ( import (
"context" "context"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/listener/inner" "github.com/Dreamacro/clash/listener/inner"
"io" "io"
"net" "net"
@ -74,7 +73,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
transport := &http.Transport{ transport := &http.Transport{
// from http.DefaultTransport // from http.DefaultTransport
MaxIdleConns: 100, MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
@ -86,10 +85,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
client := http.Client{Transport: transport} client := http.Client{Transport: transport}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
}
resp, err = client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }

28
check_amd64.sh Normal file
View File

@ -0,0 +1,28 @@
#!/bin/sh
flags=$(grep '^flags\b' </proc/cpuinfo | head -n 1)
flags=" ${flags#*:} "
has_flags () {
for flag; do
case "$flags" in
*" $flag "*) :;;
*) return 1;;
esac
done
}
determine_level () {
level=0
has_flags lm cmov cx8 fpu fxsr mmx syscall sse2 || return 0
level=1
has_flags cx16 lahf_lm popcnt sse4_1 sse4_2 ssse3 || return 0
level=2
has_flags avx avx2 bmi1 bmi2 f16c fma abm movbe xsave || return 0
level=3
has_flags avx512f avx512bw avx512cd avx512dq avx512vl || return 0
level=4
}
determine_level
echo "Your CPU supports amd64-v$level"
return $level

View File

@ -5,10 +5,10 @@ import (
"sync" "sync"
) )
type Option = func(b *Batch) type Option[T any] func(b *Batch[T])
type Result struct { type Result[T any] struct {
Value any Value T
Err error Err error
} }
@ -17,8 +17,8 @@ type Error struct {
Err error Err error
} }
func WithConcurrencyNum(n int) Option { func WithConcurrencyNum[T any](n int) Option[T] {
return func(b *Batch) { return func(b *Batch[T]) {
q := make(chan struct{}, n) q := make(chan struct{}, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
q <- struct{}{} q <- struct{}{}
@ -28,8 +28,8 @@ func WithConcurrencyNum(n int) Option {
} }
// Batch similar to errgroup, but can control the maximum number of concurrent // Batch similar to errgroup, but can control the maximum number of concurrent
type Batch struct { type Batch[T any] struct {
result map[string]Result result map[string]Result[T]
queue chan struct{} queue chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
mux sync.Mutex mux sync.Mutex
@ -38,7 +38,7 @@ type Batch struct {
cancel func() cancel func()
} }
func (b *Batch) Go(key string, fn func() (any, error)) { func (b *Batch[T]) Go(key string, fn func() (T, error)) {
b.wg.Add(1) b.wg.Add(1)
go func() { go func() {
defer b.wg.Done() defer b.wg.Done()
@ -59,14 +59,14 @@ func (b *Batch) Go(key string, fn func() (any, error)) {
}) })
} }
ret := Result{value, err} ret := Result[T]{value, err}
b.mux.Lock() b.mux.Lock()
defer b.mux.Unlock() defer b.mux.Unlock()
b.result[key] = ret b.result[key] = ret
}() }()
} }
func (b *Batch) Wait() *Error { func (b *Batch[T]) Wait() *Error {
b.wg.Wait() b.wg.Wait()
if b.cancel != nil { if b.cancel != nil {
b.cancel() b.cancel()
@ -74,26 +74,26 @@ func (b *Batch) Wait() *Error {
return b.err return b.err
} }
func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) { func (b *Batch[T]) WaitAndGetResult() (map[string]Result[T], *Error) {
err := b.Wait() err := b.Wait()
return b.Result(), err return b.Result(), err
} }
func (b *Batch) Result() map[string]Result { func (b *Batch[T]) Result() map[string]Result[T] {
b.mux.Lock() b.mux.Lock()
defer b.mux.Unlock() defer b.mux.Unlock()
copy := map[string]Result{} copyM := map[string]Result[T]{}
for k, v := range b.result { for k, v := range b.result {
copy[k] = v copyM[k] = v
} }
return copy return copyM
} }
func New(ctx context.Context, opts ...Option) (*Batch, context.Context) { func New[T any](ctx context.Context, opts ...Option[T]) (*Batch[T], context.Context) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
b := &Batch{ b := &Batch[T]{
result: map[string]Result{}, result: map[string]Result[T]{},
} }
for _, o := range opts { for _, o := range opts {

View File

@ -11,14 +11,14 @@ import (
) )
func TestBatch(t *testing.T) { func TestBatch(t *testing.T) {
b, _ := New(context.Background()) b, _ := New[string](context.Background())
now := time.Now() now := time.Now()
b.Go("foo", func() (any, error) { b.Go("foo", func() (string, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return "foo", nil return "foo", nil
}) })
b.Go("bar", func() (any, error) { b.Go("bar", func() (string, error) {
time.Sleep(time.Millisecond * 150) time.Sleep(time.Millisecond * 150)
return "bar", nil return "bar", nil
}) })
@ -32,20 +32,20 @@ func TestBatch(t *testing.T) {
for k, v := range result { for k, v := range result {
assert.NoError(t, v.Err) assert.NoError(t, v.Err)
assert.Equal(t, k, v.Value.(string)) assert.Equal(t, k, v.Value)
} }
} }
func TestBatchWithConcurrencyNum(t *testing.T) { func TestBatchWithConcurrencyNum(t *testing.T) {
b, _ := New( b, _ := New[string](
context.Background(), context.Background(),
WithConcurrencyNum(3), WithConcurrencyNum[string](3),
) )
now := time.Now() now := time.Now()
for i := 0; i < 7; i++ { for i := 0; i < 7; i++ {
idx := i idx := i
b.Go(strconv.Itoa(idx), func() (any, error) { b.Go(strconv.Itoa(idx), func() (string, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return strconv.Itoa(idx), nil return strconv.Itoa(idx), nil
}) })
@ -57,21 +57,21 @@ func TestBatchWithConcurrencyNum(t *testing.T) {
for k, v := range result { for k, v := range result {
assert.NoError(t, v.Err) assert.NoError(t, v.Err)
assert.Equal(t, k, v.Value.(string)) assert.Equal(t, k, v.Value)
} }
} }
func TestBatchContext(t *testing.T) { func TestBatchContext(t *testing.T) {
b, ctx := New(context.Background()) b, ctx := New[string](context.Background())
b.Go("error", func() (any, error) { b.Go("error", func() (string, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return nil, errors.New("test error") return "", errors.New("test error")
}) })
b.Go("ctx", func() (any, error) { b.Go("ctx", func() (string, error) {
<-ctx.Done() <-ctx.Done()
return nil, ctx.Err() return "", ctx.Err()
}) })
result, err := b.WaitAndGetResult() result, err := b.WaitAndGetResult()

View File

@ -3,19 +3,20 @@ package cache
// Modified by https://github.com/die-net/lrucache // Modified by https://github.com/die-net/lrucache
import ( import (
"container/list"
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/common/generics/list"
) )
// Option is part of Functional Options Pattern // Option is part of Functional Options Pattern
type Option[K comparable, V any] func(*LruCache[K, V]) type Option[K comparable, V any] func(*LruCache[K, V])
// EvictCallback is used to get a callback when a cache entry is evicted // EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback = func(key any, value any) type EvictCallback[K comparable, V any] func(key K, value V)
// WithEvict set the evict callback // WithEvict set the evict callback
func WithEvict[K comparable, V any](cb EvictCallback) Option[K, V] { func WithEvict[K comparable, V any](cb EvictCallback[K, V]) Option[K, V] {
return func(l *LruCache[K, V]) { return func(l *LruCache[K, V]) {
l.onEvict = cb l.onEvict = cb
} }
@ -57,18 +58,18 @@ type LruCache[K comparable, V any] struct {
maxAge int64 maxAge int64
maxSize int maxSize int
mu sync.Mutex mu sync.Mutex
cache map[any]*list.Element cache map[K]*list.Element[*entry[K, V]]
lru *list.List // Front is least-recent lru *list.List[*entry[K, V]] // Front is least-recent
updateAgeOnGet bool updateAgeOnGet bool
staleReturn bool staleReturn bool
onEvict EvictCallback onEvict EvictCallback[K, V]
} }
// NewLRUCache creates an LruCache // NewLRUCache creates an LruCache
func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{ lc := &LruCache[K, V]{
lru: list.New(), lru: list.New[*entry[K, V]](),
cache: make(map[any]*list.Element), cache: make(map[K]*list.Element[*entry[K, V]]),
} }
for _, option := range options { for _, option := range options {
@ -129,7 +130,7 @@ func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
if le, ok := c.cache[key]; ok { if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le) c.lru.MoveToBack(le)
e := le.Value.(*entry[K, V]) e := le.Value
e.value = value e.value = value
e.expires = expires.Unix() e.expires = expires.Unix()
} else { } else {
@ -154,11 +155,11 @@ func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
n.lru = list.New() n.lru = list.New[*entry[K, V]]()
n.cache = make(map[any]*list.Element) n.cache = make(map[K]*list.Element[*entry[K, V]])
for e := c.lru.Front(); e != nil; e = e.Next() { for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value.(*entry[K, V]) elm := e.Value
n.cache[elm.key] = n.lru.PushBack(elm) n.cache[elm.key] = n.lru.PushBack(elm)
} }
} }
@ -172,7 +173,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
return nil return nil
} }
if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry[K, V]).expires <= time.Now().Unix() { if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() {
c.deleteElement(le) c.deleteElement(le)
c.maybeDeleteOldest() c.maybeDeleteOldest()
@ -180,7 +181,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
} }
c.lru.MoveToBack(le) c.lru.MoveToBack(le)
el := le.Value.(*entry[K, V]) el := le.Value
if c.maxAge > 0 && c.updateAgeOnGet { if c.maxAge > 0 && c.updateAgeOnGet {
el.expires = time.Now().Unix() + c.maxAge el.expires = time.Now().Unix() + c.maxAge
} }
@ -201,15 +202,15 @@ func (c *LruCache[K, V]) Delete(key K) {
func (c *LruCache[K, V]) maybeDeleteOldest() { func (c *LruCache[K, V]) maybeDeleteOldest() {
if !c.staleReturn && c.maxAge > 0 { if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix() now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry[K, V]).expires <= now; le = c.lru.Front() { for le := c.lru.Front(); le != nil && le.Value.expires <= now; le = c.lru.Front() {
c.deleteElement(le) c.deleteElement(le)
} }
} }
} }
func (c *LruCache[K, V]) deleteElement(le *list.Element) { func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
c.lru.Remove(le) c.lru.Remove(le)
e := le.Value.(*entry[K, V]) e := le.Value
delete(c.cache, e.key) delete(c.cache, e.key)
if c.onEvict != nil { if c.onEvict != nil {
c.onEvict(e.key, e.value) c.onEvict(e.key, e.value)
@ -219,7 +220,7 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element) {
func (c *LruCache[K, V]) Clear() error { func (c *LruCache[K, V]) Clear() error {
c.mu.Lock() c.mu.Lock()
c.cache = make(map[any]*list.Element) c.cache = make(map[K]*list.Element[*entry[K, V]])
c.mu.Unlock() c.mu.Unlock()
return nil return nil

View File

@ -52,18 +52,18 @@ func TestLRUMaxAge(t *testing.T) {
// Add one expired entry // Add one expired entry
c.Set("foo", "bar") c.Set("foo", "bar")
c.lru.Back().Value.(*entry[string, string]).expires = now c.lru.Back().Value.expires = now
// Reset // Reset
c.Set("foo", "bar") c.Set("foo", "bar")
e := c.lru.Back().Value.(*entry[string, string]) e := c.lru.Back().Value
assert.True(t, e.expires >= now) assert.True(t, e.expires >= now)
c.lru.Back().Value.(*entry[string, string]).expires = now c.lru.Back().Value.expires = now
// Set a few and verify expiration times // Set a few and verify expiration times
for _, s := range entries { for _, s := range entries {
c.Set(s.key, s.value) c.Set(s.key, s.value)
e := c.lru.Back().Value.(*entry[string, string]) e := c.lru.Back().Value
assert.True(t, e.expires >= expected && e.expires <= expected+10) assert.True(t, e.expires >= expected && e.expires <= expected+10)
} }
@ -77,7 +77,7 @@ func TestLRUMaxAge(t *testing.T) {
for _, s := range entries { for _, s := range entries {
le, ok := c.cache[s.key] le, ok := c.cache[s.key]
if assert.True(t, ok) { if assert.True(t, ok) {
le.Value.(*entry[string, string]).expires = now le.Value.expires = now
} }
} }
@ -95,11 +95,11 @@ func TestLRUpdateOnGet(t *testing.T) {
// Add one expired entry // Add one expired entry
c.Set("foo", "bar") c.Set("foo", "bar")
c.lru.Back().Value.(*entry[string, string]).expires = expires c.lru.Back().Value.expires = expires
_, ok := c.Get("foo") _, ok := c.Get("foo")
assert.True(t, ok) assert.True(t, ok)
assert.True(t, c.lru.Back().Value.(*entry[string, string]).expires > expires) assert.True(t, c.lru.Back().Value.expires > expires)
} }
func TestMaxSize(t *testing.T) { func TestMaxSize(t *testing.T) {
@ -126,8 +126,8 @@ func TestExist(t *testing.T) {
func TestEvict(t *testing.T) { func TestEvict(t *testing.T) {
temp := 0 temp := 0
evict := func(key any, value any) { evict := func(key int, value int) {
temp = key.(int) + value.(int) temp = key + value
} }
c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1)) c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1))

View File

@ -0,0 +1,235 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package list implements a doubly linked list.
//
// To iterate over a list (where l is a *List):
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.Value
// }
//
package list
// Element is an element of a linked list.
type Element[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element[T]
// The list to which this element belongs.
list *List[T]
// The value stored with this element.
Value T
}
// Next returns the next list element or nil.
func (e *Element[T]) Next() *Element[T] {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// Prev returns the previous list element or nil.
func (e *Element[T]) Prev() *Element[T] {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List[T any] struct {
root Element[T] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
// Init initializes or clears list l.
func (l *List[T]) Init() *List[T] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// New returns an initialized list.
func New[T any]() *List[T] { return new(List[T]).Init() }
// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List[T]) Len() int { return l.len }
// Front returns the first element of list l or nil if the list is empty.
func (l *List[T]) Front() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.next
}
// Back returns the last element of list l or nil if the list is empty.
func (l *List[T]) Back() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}
// lazyInit lazily initializes a zero List value.
func (l *List[T]) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
// insert inserts e after at, increments l.len, and returns e.
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] {
return l.insert(&Element[T]{Value: v}, at)
}
// remove removes e from its list, decrements l.len
func (l *List[T]) remove(e *Element[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--
}
// move moves e to next to at.
func (l *List[T]) move(e, at *Element[T]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}
// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List[T]) Remove(e *Element[T]) T {
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return e.Value
}
// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List[T]) PushFront(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, &l.root)
}
// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List[T]) PushBack(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark.prev)
}
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark)
}
// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToFront(e *Element[T]) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
// MoveToBack moves element e to the back of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToBack(e *Element[T]) {
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
// MoveBefore moves element e to its new position before mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
// MoveAfter moves element e to its new position after mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
// PushBackList inserts a copy of another list at the back of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushBackList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
// PushFrontList inserts a copy of another list at the front of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushFrontList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}

30
common/net/relay.go Normal file
View File

@ -0,0 +1,30 @@
package net
import (
"io"
"net"
"time"
"github.com/Dreamacro/clash/common/pool"
)
// Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) {
ch := make(chan error)
go func() {
buf := pool.Get(pool.RelayBufferSize)
// Wrapping to avoid using *net.TCPConn.(ReadFrom)
// See also https://github.com/Dreamacro/clash/pull/1209
_, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf)
pool.Put(buf)
leftConn.SetReadDeadline(time.Now())
ch <- err
}()
buf := pool.Get(pool.RelayBufferSize)
io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf)
pool.Put(buf)
rightConn.SetReadDeadline(time.Now())
<-ch
}

53
common/nnip/netip.go Normal file
View File

@ -0,0 +1,53 @@
package nnip
import (
"encoding/binary"
"net"
"net/netip"
)
// IpToAddr converts the net.IP to netip.Addr.
// If slice's length is not 4 or 16, IpToAddr returns netip.Addr{}
func IpToAddr(slice net.IP) netip.Addr {
ip := slice
if len(ip) != 4 {
if ip = slice.To4(); ip == nil {
ip = slice
}
}
if addr, ok := netip.AddrFromSlice(ip); ok {
return addr
}
return netip.Addr{}
}
// UnMasked returns p's last IP address.
// If p is invalid, UnMasked returns netip.Addr{}
func UnMasked(p netip.Prefix) netip.Addr {
if !p.IsValid() {
return netip.Addr{}
}
buf := p.Addr().As16()
hi := binary.BigEndian.Uint64(buf[:8])
lo := binary.BigEndian.Uint64(buf[8:])
bits := p.Bits()
if bits <= 32 {
bits += 96
}
hi = hi | ^uint64(0)>>bits
lo = lo | ^(^uint64(0) << (128 - bits))
binary.BigEndian.PutUint64(buf[:8], hi)
binary.BigEndian.PutUint64(buf[8:], lo)
addr := netip.AddrFrom16(buf)
if p.Addr().Is4() {
return addr.Unmap()
}
return addr
}

View File

@ -1,3 +1,3 @@
package observable package observable
type Iterable <-chan any type Iterable[T any] <-chan T

View File

@ -5,14 +5,14 @@ import (
"sync" "sync"
) )
type Observable struct { type Observable[T any] struct {
iterable Iterable iterable Iterable[T]
listener map[Subscription]*Subscriber listener map[Subscription[T]]*Subscriber[T]
mux sync.Mutex mux sync.Mutex
done bool done bool
} }
func (o *Observable) process() { func (o *Observable[T]) process() {
for item := range o.iterable { for item := range o.iterable {
o.mux.Lock() o.mux.Lock()
for _, sub := range o.listener { for _, sub := range o.listener {
@ -23,7 +23,7 @@ func (o *Observable) process() {
o.close() o.close()
} }
func (o *Observable) close() { func (o *Observable[T]) close() {
o.mux.Lock() o.mux.Lock()
defer o.mux.Unlock() defer o.mux.Unlock()
@ -33,18 +33,18 @@ func (o *Observable) close() {
} }
} }
func (o *Observable) Subscribe() (Subscription, error) { func (o *Observable[T]) Subscribe() (Subscription[T], error) {
o.mux.Lock() o.mux.Lock()
defer o.mux.Unlock() defer o.mux.Unlock()
if o.done { if o.done {
return nil, errors.New("Observable is closed") return nil, errors.New("observable is closed")
} }
subscriber := newSubscriber() subscriber := newSubscriber[T]()
o.listener[subscriber.Out()] = subscriber o.listener[subscriber.Out()] = subscriber
return subscriber.Out(), nil return subscriber.Out(), nil
} }
func (o *Observable) UnSubscribe(sub Subscription) { func (o *Observable[T]) UnSubscribe(sub Subscription[T]) {
o.mux.Lock() o.mux.Lock()
defer o.mux.Unlock() defer o.mux.Unlock()
subscriber, exist := o.listener[sub] subscriber, exist := o.listener[sub]
@ -55,10 +55,10 @@ func (o *Observable) UnSubscribe(sub Subscription) {
subscriber.Close() subscriber.Close()
} }
func NewObservable(any Iterable) *Observable { func NewObservable[T any](iter Iterable[T]) *Observable[T] {
observable := &Observable{ observable := &Observable[T]{
iterable: any, iterable: iter,
listener: map[Subscription]*Subscriber{}, listener: map[Subscription[T]]*Subscriber[T]{},
} }
go observable.process() go observable.process()
return observable return observable

View File

@ -9,8 +9,8 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
) )
func iterator(item []any) chan any { func iterator[T any](item []T) chan T {
ch := make(chan any) ch := make(chan T)
go func() { go func() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
for _, elm := range item { for _, elm := range item {
@ -22,8 +22,8 @@ func iterator(item []any) chan any {
} }
func TestObservable(t *testing.T) { func TestObservable(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5}) iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable[int](iter)
data, err := src.Subscribe() data, err := src.Subscribe()
assert.Nil(t, err) assert.Nil(t, err)
count := 0 count := 0
@ -34,15 +34,15 @@ func TestObservable(t *testing.T) {
} }
func TestObservable_MultiSubscribe(t *testing.T) { func TestObservable_MultiSubscribe(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5}) iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable[int](iter)
ch1, _ := src.Subscribe() ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe() ch2, _ := src.Subscribe()
count := atomic.NewInt32(0) count := atomic.NewInt32(0)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
waitCh := func(ch <-chan any) { waitCh := func(ch <-chan int) {
for range ch { for range ch {
count.Inc() count.Inc()
} }
@ -55,8 +55,8 @@ func TestObservable_MultiSubscribe(t *testing.T) {
} }
func TestObservable_UnSubscribe(t *testing.T) { func TestObservable_UnSubscribe(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5}) iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable[int](iter)
data, err := src.Subscribe() data, err := src.Subscribe()
assert.Nil(t, err) assert.Nil(t, err)
src.UnSubscribe(data) src.UnSubscribe(data)
@ -65,8 +65,8 @@ func TestObservable_UnSubscribe(t *testing.T) {
} }
func TestObservable_SubscribeClosedSource(t *testing.T) { func TestObservable_SubscribeClosedSource(t *testing.T) {
iter := iterator([]any{1}) iter := iterator[int]([]int{1})
src := NewObservable(iter) src := NewObservable[int](iter)
data, _ := src.Subscribe() data, _ := src.Subscribe()
<-data <-data
@ -75,18 +75,18 @@ func TestObservable_SubscribeClosedSource(t *testing.T) {
} }
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
sub := Subscription(make(chan any)) sub := Subscription[int](make(chan int))
iter := iterator([]any{1}) iter := iterator[int]([]int{1})
src := NewObservable(iter) src := NewObservable[int](iter)
src.UnSubscribe(sub) src.UnSubscribe(sub)
} }
func TestObservable_SubscribeGoroutineLeak(t *testing.T) { func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5}) iter := iterator[int]([]int{1, 2, 3, 4, 5})
src := NewObservable(iter) src := NewObservable[int](iter)
max := 100 max := 100
var list []Subscription var list []Subscription[int]
for i := 0; i < max; i++ { for i := 0; i < max; i++ {
ch, _ := src.Subscribe() ch, _ := src.Subscribe()
list = append(list, ch) list = append(list, ch)
@ -94,7 +94,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(max) wg.Add(max)
waitCh := func(ch <-chan any) { waitCh := func(ch <-chan int) {
for range ch { for range ch {
} }
wg.Done() wg.Done()
@ -115,11 +115,11 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
} }
func Benchmark_Observable_1000(b *testing.B) { func Benchmark_Observable_1000(b *testing.B) {
ch := make(chan any) ch := make(chan int)
o := NewObservable(ch) o := NewObservable[int](ch)
num := 1000 num := 1000
subs := []Subscription{} subs := []Subscription[int]{}
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
sub, _ := o.Subscribe() sub, _ := o.Subscribe()
subs = append(subs, sub) subs = append(subs, sub)
@ -130,7 +130,7 @@ func Benchmark_Observable_1000(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for _, sub := range subs { for _, sub := range subs {
go func(s Subscription) { go func(s Subscription[int]) {
for range s { for range s {
} }
wg.Done() wg.Done()

View File

@ -4,30 +4,30 @@ import (
"sync" "sync"
) )
type Subscription <-chan any type Subscription[T any] <-chan T
type Subscriber struct { type Subscriber[T any] struct {
buffer chan any buffer chan T
once sync.Once once sync.Once
} }
func (s *Subscriber) Emit(item any) { func (s *Subscriber[T]) Emit(item T) {
s.buffer <- item s.buffer <- item
} }
func (s *Subscriber) Out() Subscription { func (s *Subscriber[T]) Out() Subscription[T] {
return s.buffer return s.buffer
} }
func (s *Subscriber) Close() { func (s *Subscriber[T]) Close() {
s.once.Do(func() { s.once.Do(func() {
close(s.buffer) close(s.buffer)
}) })
} }
func newSubscriber() *Subscriber { func newSubscriber[T any]() *Subscriber[T] {
sub := &Subscriber{ sub := &Subscriber[T]{
buffer: make(chan any, 200), buffer: make(chan T, 200),
} }
return sub return sub
} }

View File

@ -9,7 +9,7 @@ import (
// Picker provides synchronization, and Context cancelation // Picker provides synchronization, and Context cancelation
// for groups of goroutines working on subtasks of a common task. // for groups of goroutines working on subtasks of a common task.
// Inspired by errGroup // Inspired by errGroup
type Picker struct { type Picker[T any] struct {
ctx context.Context ctx context.Context
cancel func() cancel func()
@ -17,12 +17,12 @@ type Picker struct {
once sync.Once once sync.Once
errOnce sync.Once errOnce sync.Once
result any result T
err error err error
} }
func newPicker(ctx context.Context, cancel func()) *Picker { func newPicker[T any](ctx context.Context, cancel func()) *Picker[T] {
return &Picker{ return &Picker[T]{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
} }
@ -30,20 +30,20 @@ func newPicker(ctx context.Context, cancel func()) *Picker {
// WithContext returns a new Picker and an associated Context derived from ctx. // WithContext returns a new Picker and an associated Context derived from ctx.
// and cancel when first element return. // and cancel when first element return.
func WithContext(ctx context.Context) (*Picker, context.Context) { func WithContext[T any](ctx context.Context) (*Picker[T], context.Context) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
return newPicker(ctx, cancel), ctx return newPicker[T](ctx, cancel), ctx
} }
// WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout.
func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { func WithTimeout[T any](ctx context.Context, timeout time.Duration) (*Picker[T], context.Context) {
ctx, cancel := context.WithTimeout(ctx, timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
return newPicker(ctx, cancel), ctx return newPicker[T](ctx, cancel), ctx
} }
// Wait blocks until all function calls from the Go method have returned, // Wait blocks until all function calls from the Go method have returned,
// then returns the first nil error result (if any) from them. // then returns the first nil error result (if any) from them.
func (p *Picker) Wait() any { func (p *Picker[T]) Wait() T {
p.wg.Wait() p.wg.Wait()
if p.cancel != nil { if p.cancel != nil {
p.cancel() p.cancel()
@ -52,13 +52,13 @@ func (p *Picker) Wait() any {
} }
// Error return the first error (if all success return nil) // Error return the first error (if all success return nil)
func (p *Picker) Error() error { func (p *Picker[T]) Error() error {
return p.err return p.err
} }
// Go calls the given function in a new goroutine. // Go calls the given function in a new goroutine.
// The first call to return a nil error cancels the group; its result will be returned by Wait. // The first call to return a nil error cancels the group; its result will be returned by Wait.
func (p *Picker) Go(f func() (any, error)) { func (p *Picker[T]) Go(f func() (T, error)) {
p.wg.Add(1) p.wg.Add(1)
go func() { go func() {

View File

@ -8,33 +8,38 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func sleepAndSend(ctx context.Context, delay int, input any) func() (any, error) { func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, error) {
return func() (any, error) { return func() (T, error) {
timer := time.NewTimer(time.Millisecond * time.Duration(delay)) timer := time.NewTimer(time.Millisecond * time.Duration(delay))
select { select {
case <-timer.C: case <-timer.C:
return input, nil return input, nil
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return getZero[T](), ctx.Err()
} }
} }
} }
func TestPicker_Basic(t *testing.T) { func TestPicker_Basic(t *testing.T) {
picker, ctx := WithContext(context.Background()) picker, ctx := WithContext[int](context.Background())
picker.Go(sleepAndSend(ctx, 30, 2)) picker.Go(sleepAndSend(ctx, 30, 2))
picker.Go(sleepAndSend(ctx, 20, 1)) picker.Go(sleepAndSend(ctx, 20, 1))
number := picker.Wait() number := picker.Wait()
assert.NotNil(t, number) assert.NotNil(t, number)
assert.Equal(t, number.(int), 1) assert.Equal(t, number, 1)
} }
func TestPicker_Timeout(t *testing.T) { func TestPicker_Timeout(t *testing.T) {
picker, ctx := WithTimeout(context.Background(), time.Millisecond*5) picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5)
picker.Go(sleepAndSend(ctx, 20, 1)) picker.Go(sleepAndSend(ctx, 20, 1))
number := picker.Wait() number := picker.Wait()
assert.Nil(t, number) assert.Equal(t, number, getZero[int]())
assert.NotNil(t, picker.Error()) assert.NotNil(t, picker.Error())
} }
func getZero[T any]() T {
var result T
return result
}

View File

@ -5,28 +5,28 @@ import (
"time" "time"
) )
type call struct { type call[T any] struct {
wg sync.WaitGroup wg sync.WaitGroup
val any val T
err error err error
} }
type Single struct { type Single[T any] struct {
mux sync.Mutex mux sync.Mutex
last time.Time last time.Time
wait time.Duration wait time.Duration
call *call call *call[T]
result *Result result *Result[T]
} }
type Result struct { type Result[T any] struct {
Val any Val T
Err error Err error
} }
// Do single.Do likes sync.singleFlight // Do single.Do likes sync.singleFlight
//lint:ignore ST1008 it likes sync.singleFlight //lint:ignore ST1008 it likes sync.singleFlight
func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) { func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
s.mux.Lock() s.mux.Lock()
now := time.Now() now := time.Now()
if now.Before(s.last.Add(s.wait)) { if now.Before(s.last.Add(s.wait)) {
@ -34,31 +34,31 @@ func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) {
return s.result.Val, s.result.Err, true return s.result.Val, s.result.Err, true
} }
if call := s.call; call != nil { if callM := s.call; callM != nil {
s.mux.Unlock() s.mux.Unlock()
call.wg.Wait() callM.wg.Wait()
return call.val, call.err, true return callM.val, callM.err, true
} }
call := &call{} callM := &call[T]{}
call.wg.Add(1) callM.wg.Add(1)
s.call = call s.call = callM
s.mux.Unlock() s.mux.Unlock()
call.val, call.err = fn() callM.val, callM.err = fn()
call.wg.Done() callM.wg.Done()
s.mux.Lock() s.mux.Lock()
s.call = nil s.call = nil
s.result = &Result{call.val, call.err} s.result = &Result[T]{callM.val, callM.err}
s.last = now s.last = now
s.mux.Unlock() s.mux.Unlock()
return call.val, call.err, false return callM.val, callM.err, false
} }
func (s *Single) Reset() { func (s *Single[T]) Reset() {
s.last = time.Time{} s.last = time.Time{}
} }
func NewSingle(wait time.Duration) *Single { func NewSingle[T any](wait time.Duration) *Single[T] {
return &Single{wait: wait} return &Single[T]{wait: wait}
} }

View File

@ -10,13 +10,13 @@ import (
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle[int](time.Millisecond * 30)
foo := 0 foo := 0
shardCount := atomic.NewInt32(0) shardCount := atomic.NewInt32(0)
call := func() (any, error) { call := func() (int, error) {
foo++ foo++
time.Sleep(time.Millisecond * 5) time.Sleep(time.Millisecond * 5)
return nil, nil return 0, nil
} }
var wg sync.WaitGroup var wg sync.WaitGroup
@ -38,32 +38,32 @@ func TestBasic(t *testing.T) {
} }
func TestTimer(t *testing.T) { func TestTimer(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle[int](time.Millisecond * 30)
foo := 0 foo := 0
call := func() (any, error) { callM := func() (int, error) {
foo++ foo++
return nil, nil return 0, nil
} }
single.Do(call) _, _, _ = single.Do(callM)
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
_, _, shard := single.Do(call) _, _, shard := single.Do(callM)
assert.Equal(t, 1, foo) assert.Equal(t, 1, foo)
assert.True(t, shard) assert.True(t, shard)
} }
func TestReset(t *testing.T) { func TestReset(t *testing.T) {
single := NewSingle(time.Millisecond * 30) single := NewSingle[int](time.Millisecond * 30)
foo := 0 foo := 0
call := func() (any, error) { callM := func() (int, error) {
foo++ foo++
return nil, nil return 0, nil
} }
single.Do(call) _, _, _ = single.Do(callM)
single.Reset() single.Reset()
single.Do(call) _, _, _ = single.Do(callM)
assert.Equal(t, 2, foo) assert.Equal(t, 2, foo)
} }

View File

@ -31,7 +31,7 @@ func NewDecoder(option Option) *Decoder {
// Decode transform a map[string]any to a struct // Decode transform a map[string]any to a struct
func (d *Decoder) Decode(src map[string]any, dst any) error { func (d *Decoder) Decode(src map[string]any, dst any) error {
if reflect.TypeOf(dst).Kind() != reflect.Ptr { if reflect.TypeOf(dst).Kind() != reflect.Ptr {
return fmt.Errorf("Decode must recive a ptr struct") return fmt.Errorf("decode must recive a ptr struct")
} }
t := reflect.TypeOf(dst).Elem() t := reflect.TypeOf(dst).Elem()
v := reflect.ValueOf(dst).Elem() v := reflect.ValueOf(dst).Elem()
@ -159,9 +159,19 @@ func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
for valSlice.Len() <= i { for valSlice.Len() <= i {
valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
} }
currentField := valSlice.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i) fieldName := fmt.Sprintf("%s[%d]", name, i)
if currentData == nil {
// in weakly type mode, null will convert to zero value
if d.option.WeaklyTypedInput {
continue
}
// in non-weakly type mode, null will convert to nil if element's zero value is nil, otherwise return an error
if elemKind := valElemType.Kind(); elemKind == reflect.Map || elemKind == reflect.Slice {
continue
}
return fmt.Errorf("'%s' can not be null", fieldName)
}
currentField := valSlice.Index(i)
if err := d.decode(fieldName, currentData, currentField); err != nil { if err := d.decode(fieldName, currentData, currentField); err != nil {
return err return err
} }
@ -291,7 +301,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
field reflect.StructField field reflect.StructField
val reflect.Value val reflect.Value
} }
fields := []field{} var fields []field
for len(structs) > 0 { for len(structs) > 0 {
structVal := structs[0] structVal := structs[0]
structs = structs[1:] structs = structs[1:]

44
common/utils/range.go Normal file
View File

@ -0,0 +1,44 @@
package utils
import (
"golang.org/x/exp/constraints"
)
type Range[T constraints.Ordered] struct {
start T
end T
}
func NewRange[T constraints.Ordered](start, end T) *Range[T] {
if start > end {
return &Range[T]{
start: end,
end: start,
}
}
return &Range[T]{
start: start,
end: end,
}
}
func (r *Range[T]) Contains(t T) bool {
return t >= r.start && t <= r.end
}
func (r *Range[T]) LeftContains(t T) bool {
return t >= r.start && t < r.end
}
func (r *Range[T]) RightContains(t T) bool {
return t > r.start && t <= r.end
}
func (r *Range[T]) Start() T {
return r.start
}
func (r *Range[T]) End() T {
return r.end
}

16
common/utils/uuid.go Normal file
View File

@ -0,0 +1,16 @@
package utils
import (
"github.com/gofrs/uuid"
)
var uuidNamespace, _ = uuid.FromString("00000000-0000-0000-0000-000000000000")
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
func UUIDMap(str string) (uuid.UUID, error) {
u, err := uuid.FromString(str)
if err != nil {
return uuid.NewV5(uuidNamespace, str), nil
}
return u, nil
}

74
common/utils/uuid_test.go Normal file
View File

@ -0,0 +1,74 @@
package utils
import (
"github.com/gofrs/uuid"
"reflect"
"testing"
)
func TestUUIDMap(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want uuid.UUID
wantErr bool
}{
{
name: "uuid-test-1",
args: args{
str: "82410302-039e-41b6-98b0-d964084b4170",
},
want: uuid.FromStringOrNil("82410302-039e-41b6-98b0-d964084b4170"),
wantErr: false,
},
{
name: "uuid-test-2",
args: args{
str: "88c502e6-d7eb-4c8e-8259-94cb13d83c77",
},
want: uuid.FromStringOrNil("88c502e6-d7eb-4c8e-8259-94cb13d83c77"),
wantErr: false,
},
{
name: "uuid-map-1",
args: args{
str: "123456",
},
want: uuid.FromStringOrNil("f8598425-92f2-5508-a071-4fc67f9040ac"),
wantErr: false,
},
// GENERATED BY 'xray uuid -i'
{
name: "uuid-map-2",
args: args{
str: "a9dk23bz0",
},
want: uuid.FromStringOrNil("c91481b6-fc0f-5d9e-b166-5ddf07b9c3c5"),
wantErr: false,
},
{
name: "uuid-map-2",
args: args{
str: "中文123",
},
want: uuid.FromStringOrNil("145c544c-2229-59e5-8dbb-3f33b7610d26"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := UUIDMap(tt.args.str)
if (err != nil) != tt.wantErr {
t.Errorf("UUIDMap() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("UUIDMap() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -4,7 +4,9 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"net/netip"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
"github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4"
@ -15,14 +17,16 @@ var (
ErrNotFound = errors.New("DNS option not found") ErrNotFound = errors.New("DNS option not found")
) )
func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, error) { func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]netip.Addr, error) {
conn, err := ListenDHCPClient(context, ifaceName) conn, err := ListenDHCPClient(context, ifaceName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer conn.Close() defer func() {
_ = conn.Close()
}()
result := make(chan []net.IP, 1) result := make(chan []netip.Addr, 1)
ifaceObj, err := iface.ResolveInterface(ifaceName) ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil { if err != nil {
@ -52,7 +56,7 @@ func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, er
} }
} }
func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []net.IP) { func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []netip.Addr) {
defer close(result) defer close(result)
buf := make([]byte, dhcpv4.MaxMessageSize) buf := make([]byte, dhcpv4.MaxMessageSize)
@ -77,11 +81,17 @@ func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []
} }
dns := pkt.DNS() dns := pkt.DNS()
if len(dns) == 0 { l := len(dns)
if l == 0 {
return return
} }
result <- dns dnsAddr := make([]netip.Addr, l)
for i := 0; i < l; i++ {
dnsAddr[i] = nnip.IpToAddr(dns[i])
}
result <- dnsAddr
return return
} }

View File

@ -2,6 +2,7 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"syscall" "syscall"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
@ -19,12 +20,9 @@ func bindControl(ifaceIdx int, chain controlFn) controlFn {
} }
}() }()
ipStr, _, err := net.SplitHostPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil { if err == nil && !addrPort.Addr().IsGlobalUnicast() {
ip := net.ParseIP(ipStr) return
if ip != nil && !ip.IsGlobalUnicast() {
return
}
} }
var innerErr error var innerErr error
@ -45,7 +43,7 @@ func bindControl(ifaceIdx int, chain controlFn) controlFn {
} }
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
ifaceObj, err := iface.ResolveInterface(ifaceName) ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil { if err != nil {
return err return err

View File

@ -2,6 +2,7 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -17,12 +18,9 @@ func bindControl(ifaceName string, chain controlFn) controlFn {
} }
}() }()
ipStr, _, err := net.SplitHostPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil { if err == nil && !addrPort.Addr().IsGlobalUnicast() {
ip := net.ParseIP(ipStr) return
if ip != nil && !ip.IsGlobalUnicast() {
return
}
} }
var innerErr error var innerErr error
@ -38,7 +36,7 @@ func bindControl(ifaceName string, chain controlFn) controlFn {
} }
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
dialer.Control = bindControl(ifaceName, dialer.Control) dialer.Control = bindControl(ifaceName, dialer.Control)
return nil return nil

View File

@ -4,27 +4,28 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
) )
func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) { func lookupLocalAddr(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName) ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var addr *net.IPNet var addr *netip.Prefix
switch network { switch network {
case "udp4", "tcp4": case "udp4", "tcp4":
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
case "tcp6", "udp6": case "tcp6", "udp6":
addr, err = ifaceObj.PickIPv6Addr(destination) addr, err = ifaceObj.PickIPv6Addr(destination)
default: default:
if destination != nil { if destination.IsValid() {
if destination.To4() != nil { if destination.Is4() {
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
} else { } else {
addr, err = ifaceObj.PickIPv6Addr(destination) addr, err = ifaceObj.PickIPv6Addr(destination)
@ -39,12 +40,12 @@ func lookupLocalAddr(ifaceName string, network string, destination net.IP, port
if strings.HasPrefix(network, "tcp") { if strings.HasPrefix(network, "tcp") {
return &net.TCPAddr{ return &net.TCPAddr{
IP: addr.IP, IP: addr.Addr().AsSlice(),
Port: port, Port: port,
}, nil }, nil
} else if strings.HasPrefix(network, "udp") { } else if strings.HasPrefix(network, "udp") {
return &net.UDPAddr{ return &net.UDPAddr{
IP: addr.IP, IP: addr.Addr().AsSlice(),
Port: port, Port: port,
}, nil }, nil
} }
@ -52,7 +53,7 @@ func lookupLocalAddr(ifaceName string, network string, destination net.IP, port
return nil, iface.ErrAddrNotFound return nil, iface.ErrAddrNotFound
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() { if !destination.IsGlobalUnicast() {
return nil return nil
} }
@ -83,7 +84,7 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
local, _ := strconv.ParseUint(port, 10, 16) local, _ := strconv.ParseUint(port, 10, 16)
addr, err := lookupLocalAddr(ifaceName, network, nil, int(local)) addr, err := lookupLocalAddr(ifaceName, network, netip.Addr{}, int(local))
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -3,11 +3,21 @@ package dialer
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net" "net"
"net/netip"
"sync"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
) )
var (
dialMux sync.Mutex
actualSingleDialContext = singleDialContext
actualDualStackDialContext = dualStackDialContext
DisableIPv6 = false
)
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := &option{ opt := &option{
interfaceName: DefaultInterface.Load(), interfaceName: DefaultInterface.Load(),
@ -24,33 +34,9 @@ func DialContext(ctx context.Context, network, address string, options ...Option
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address) return actualSingleDialContext(ctx, network, address, opt)
if err != nil {
return nil, err
}
var ip net.IP
switch network {
case "tcp4", "udp4":
if !opt.direct {
ip, err = resolver.ResolveIPv4ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv4(host)
}
default:
if !opt.direct {
ip, err = resolver.ResolveIPv6ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv6(host)
}
}
if err != nil {
return nil, err
}
return dialContext(ctx, network, ip, port, opt)
case "tcp", "udp": case "tcp", "udp":
return dualStackDialContext(ctx, network, address, opt) return actualDualStackDialContext(ctx, network, address, opt)
default: default:
return nil, errors.New("network invalid") return nil, errors.New("network invalid")
} }
@ -88,7 +74,20 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
return lc.ListenPacket(ctx, network, address) return lc.ListenPacket(ctx, network, address)
} }
func dialContext(ctx context.Context, network string, destination net.IP, port string, opt *option) (net.Conn, error) { func SetDial(concurrent bool) {
dialMux.Lock()
if concurrent {
actualSingleDialContext = concurrentSingleDialContext
actualDualStackDialContext = concurrentDualStackDialContext
} else {
actualSingleDialContext = singleDialContext
actualDualStackDialContext = concurrentDualStackDialContext
}
dialMux.Unlock()
}
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
dialer := &net.Dialer{} dialer := &net.Dialer{}
if opt.interfaceName != "" { if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
@ -99,6 +98,10 @@ func dialContext(ctx context.Context, network string, destination net.IP, port s
bindMarkToDialer(opt.routingMark, dialer, network, destination) bindMarkToDialer(opt.routingMark, dialer, network, destination)
} }
if DisableIPv6 && destination.Is6() {
return nil, fmt.Errorf("IPv6 is diabled, dialer cancel")
}
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
} }
@ -128,12 +131,12 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
case results <- result: case results <- result:
case <-returned: case <-returned:
if result.Conn != nil { if result.Conn != nil {
result.Conn.Close() _ = result.Conn.Close()
} }
} }
}() }()
var ip net.IP var ip netip.Addr
if ipv6 { if ipv6 {
if !direct { if !direct {
ip, result.error = resolver.ResolveIPv6ProxyServerHost(host) ip, result.error = resolver.ResolveIPv6ProxyServerHost(host)
@ -182,3 +185,130 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
return nil, errors.New("never touched") return nil, errors.New("never touched")
} }
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ips []netip.Addr
if opt.direct {
ips, err = resolver.ResolveAllIP(host)
} else {
ips, err = resolver.ResolveAllIPProxyServerHost(host)
}
return concurrentDialContext(ctx, network, ips, port, opt)
}
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
ip netip.Addr
net.Conn
error
resolved bool
}
results := make(chan dialResult)
tcpRacer := func(ctx context.Context, ip netip.Addr) {
result := dialResult{ip: ip}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
result.Conn.Close()
}
}
}()
v := "4"
if ip.Is6() {
v = "6"
}
result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt)
}
for _, ip := range ips {
go tcpRacer(ctx, ip)
}
connCount := len(ips)
for res := range results {
connCount--
if res.error == nil {
return res.Conn, nil
}
if connCount == 0 {
break
}
}
return nil, fmt.Errorf("all ips %v tcp shake hands failed", ips)
}
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ip netip.Addr
switch network {
case "tcp4", "udp4":
if !opt.direct {
ip, err = resolver.ResolveIPv4ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv4(host)
}
default:
if !opt.direct {
ip, err = resolver.ResolveIPv6ProxyServerHost(host)
} else {
ip, err = resolver.ResolveIPv6(host)
}
}
if err != nil {
return nil, err
}
return dialContext(ctx, network, ip, port, opt)
}
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ips []netip.Addr
switch network {
case "tcp4", "udp4":
if !opt.direct {
ips, err = resolver.ResolveAllIPv4ProxyServerHost(host)
} else {
ips, err = resolver.ResolveAllIPv4(host)
}
default:
if !opt.direct {
ips, err = resolver.ResolveAllIPv6ProxyServerHost(host)
} else {
ips, err = resolver.ResolveAllIPv6(host)
}
}
if err != nil {
return nil, err
}
return concurrentDialContext(ctx, network, ips, port, opt)
}

View File

@ -4,14 +4,15 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"syscall" "syscall"
) )
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) { func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) {
dialer.Control = bindMarkToControl(mark, dialer.Control) dialer.Control = bindMarkToControl(mark, dialer.Control)
} }
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) { func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) {
lc.Control = bindMarkToControl(mark, lc.Control) lc.Control = bindMarkToControl(mark, lc.Control)
} }
@ -23,20 +24,17 @@ func bindMarkToControl(mark int, chain controlFn) controlFn {
} }
}() }()
ipStr, _, err := net.SplitHostPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil { if err == nil && !addrPort.Addr().IsGlobalUnicast() {
ip := net.ParseIP(ipStr) return
if ip != nil && !ip.IsGlobalUnicast() {
return
}
} }
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
switch network { switch network {
case "tcp4", "udp4": case "tcp4", "udp4":
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) _ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
case "tcp6", "udp6": case "tcp6", "udp6":
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) _ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark)
} }
}) })
} }

View File

@ -4,6 +4,7 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"sync" "sync"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -17,10 +18,10 @@ func printMarkWarn() {
}) })
} }
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) { func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) {
printMarkWarn() printMarkWarn()
} }
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) { func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) {
printMarkWarn() printMarkWarn()
} }

View File

@ -3,6 +3,7 @@ package dialer
import ( import (
"context" "context"
"net" "net"
"net/netip"
) )
func init() { func init() {
@ -18,9 +19,9 @@ func resolverDialContext(ctx context.Context, network, address string) (net.Conn
interfaceName := DefaultInterface.Load() interfaceName := DefaultInterface.Load()
if interfaceName != "" { if interfaceName != "" {
dstIP := net.ParseIP(address) dstIP, err := netip.ParseAddr(address)
if dstIP != nil { if err == nil {
bindIfaceToDialer(interfaceName, d, network, dstIP) _ = bindIfaceToDialer(interfaceName, d, network, dstIP)
} }
} }

View File

@ -1,20 +1,19 @@
package fakeip package fakeip
import ( import (
"encoding/binary"
"errors" "errors"
"math/bits"
"net/netip" "net/netip"
"sync" "sync"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
) )
type uint128 struct { const (
hi uint64 offsetKey = "key-offset-fake-ip"
lo uint64 cycleKey = "key-cycle-fake-ip"
} )
type store interface { type store interface {
GetByHost(host string) (netip.Addr, bool) GetByHost(host string) (netip.Addr, bool)
@ -98,22 +97,15 @@ func (p *Pool) CloneFrom(o *Pool) {
} }
func (p *Pool) get(host string) netip.Addr { func (p *Pool) get(host string) netip.Addr {
for { p.offset = p.offset.Next()
p.offset = p.offset.Next()
if !p.offset.Less(p.last) { if !p.offset.Less(p.last) {
p.cycle = true p.cycle = true
p.offset = p.first p.offset = p.first
} }
if p.cycle { if p.cycle || p.store.Exist(p.offset) {
p.store.DelByIP(p.offset) p.store.DelByIP(p.offset)
break
}
if !p.store.Exist(p.offset) {
break
}
} }
p.store.PutByIP(p.offset, host) p.store.PutByIP(p.offset, host)
@ -121,7 +113,39 @@ func (p *Pool) get(host string) netip.Addr {
} }
func (p *Pool) FlushFakeIP() error { func (p *Pool) FlushFakeIP() error {
return p.store.FlushFakeIP() err := p.store.FlushFakeIP()
if err == nil {
p.cycle = false
p.offset = p.first.Prev()
}
return err
}
func (p *Pool) StoreState() {
if s, ok := p.store.(*cachefileStore); ok {
s.PutByHost(offsetKey, p.offset)
if p.cycle {
s.PutByHost(cycleKey, p.offset)
}
}
}
func (p *Pool) restoreState() {
if s, ok := p.store.(*cachefileStore); ok {
if _, exist := s.GetByHost(cycleKey); exist {
p.cycle = true
}
if offset, exist := s.GetByHost(offsetKey); exist {
if p.ipnet.Contains(offset) {
p.offset = offset
} else {
_ = p.FlushFakeIP()
}
} else if s.Exist(p.first) {
_ = p.FlushFakeIP()
}
}
} }
type Options struct { type Options struct {
@ -143,10 +167,10 @@ 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() first = gateway.Next().Next()
last = add(hostAddr, 1<<uint64(hostAddr.BitLen()-options.IPNet.Bits())-1) last = nnip.UnMasked(*options.IPNet)
) )
if !options.IPNet.IsValid() || !first.Less(last) || !options.IPNet.Contains(last) { if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
return nil, errors.New("ipnet don't have valid ip") return nil, errors.New("ipnet don't have valid ip")
} }
@ -167,31 +191,7 @@ func New(options Options) (*Pool, error) {
pool.store = newMemoryStore(options.Size) pool.store = newMemoryStore(options.Size)
} }
pool.restoreState()
return pool, nil return pool, nil
} }
// add returns addr + n.
func add(addr netip.Addr, n uint64) netip.Addr {
buf := addr.As16()
u := uint128{
binary.BigEndian.Uint64(buf[:8]),
binary.BigEndian.Uint64(buf[8:]),
}
lo, carry := bits.Add64(u.lo, n, 0)
u.hi = u.hi + carry
u.lo = lo
binary.BigEndian.PutUint64(buf[:8], u.hi)
binary.BigEndian.PutUint64(buf[8:], u.lo)
a := netip.AddrFrom16(buf)
if addr.Is4() {
return a.Unmap()
}
return a
}

View File

@ -240,13 +240,15 @@ func TestPool_FlushFileCache(t *testing.T) {
err = pool.FlushFakeIP() err = pool.FlushFakeIP()
assert.Nil(t, err) assert.Nil(t, err)
baz := pool.Lookup("foo.com")
next := pool.Lookup("baz.com") next := pool.Lookup("baz.com")
baz := pool.Lookup("foo.com")
nero := pool.Lookup("foo.com") nero := pool.Lookup("foo.com")
assert.True(t, foo == fox) assert.True(t, foo == fox)
assert.True(t, foo == next)
assert.False(t, foo == baz) assert.False(t, foo == baz)
assert.True(t, bar == bax) assert.True(t, bar == bax)
assert.True(t, bar == baz)
assert.False(t, bar == next) assert.False(t, bar == next)
assert.True(t, baz == nero) assert.True(t, baz == nero)
} }
@ -267,13 +269,15 @@ func TestPool_FlushMemoryCache(t *testing.T) {
err := pool.FlushFakeIP() err := pool.FlushFakeIP()
assert.Nil(t, err) assert.Nil(t, err)
baz := pool.Lookup("foo.com")
next := pool.Lookup("baz.com") next := pool.Lookup("baz.com")
baz := pool.Lookup("foo.com")
nero := pool.Lookup("foo.com") nero := pool.Lookup("foo.com")
assert.True(t, foo == fox) assert.True(t, foo == fox)
assert.True(t, foo == next)
assert.False(t, foo == baz) assert.False(t, foo == baz)
assert.True(t, bar == bax) assert.True(t, bar == bax)
assert.True(t, bar == baz)
assert.False(t, bar == next) assert.False(t, bar == next)
assert.True(t, baz == nero) assert.True(t, baz == nero)
} }

View File

@ -1,7 +1,7 @@
package strmatcher package strmatcher
import ( import (
"container/list" "github.com/Dreamacro/clash/common/generics/list"
) )
const validCharCount = 53 const validCharCount = 53
@ -190,7 +190,7 @@ func (ac *ACAutomaton) Add(domain string, t Type) {
} }
func (ac *ACAutomaton) Build() { func (ac *ACAutomaton) Build() {
queue := list.New() queue := list.New[Edge]()
for i := 0; i < validCharCount; i++ { for i := 0; i < validCharCount; i++ {
if ac.trie[0][i].nextNode != 0 { if ac.trie[0][i].nextNode != 0 {
queue.PushBack(ac.trie[0][i]) queue.PushBack(ac.trie[0][i])
@ -201,7 +201,7 @@ func (ac *ACAutomaton) Build() {
if front == nil { if front == nil {
break break
} else { } else {
node := front.Value.(Edge).nextNode node := front.Value.nextNode
queue.Remove(front) queue.Remove(front)
for i := 0; i < validCharCount; i++ { for i := 0; i < validCharCount; i++ {
if ac.trie[node][i].nextNode != 0 { if ac.trie[node][i].nextNode != 0 {

View File

@ -3,6 +3,7 @@ package iface
import ( import (
"errors" "errors"
"net" "net"
"net/netip"
"time" "time"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
@ -11,7 +12,7 @@ import (
type Interface struct { type Interface struct {
Index int Index int
Name string Name string
Addrs []*net.IPNet Addrs []*netip.Prefix
HardwareAddr net.HardwareAddr HardwareAddr net.HardwareAddr
} }
@ -20,10 +21,10 @@ var (
ErrAddrNotFound = errors.New("addr not found") ErrAddrNotFound = errors.New("addr not found")
) )
var interfaces = singledo.NewSingle(time.Second * 20) var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20)
func ResolveInterface(name string) (*Interface, error) { func ResolveInterface(name string) (*Interface, error) {
value, err, _ := interfaces.Do(func() (any, error) { value, err, _ := interfaces.Do(func() (map[string]*Interface, error) {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return nil, err return nil, err
@ -37,14 +38,18 @@ func ResolveInterface(name string) (*Interface, error) {
continue continue
} }
ipNets := make([]*net.IPNet, 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)
if v4 := ipNet.IP.To4(); v4 != nil { ip, _ := netip.AddrFromSlice(ipNet.IP)
ipNet.IP = v4
ones, bits := ipNet.Mask.Size()
if bits == 32 {
ip = ip.Unmap()
} }
ipNets = append(ipNets, ipNet) pf := netip.PrefixFrom(ip, ones)
ipNets = append(ipNets, &pf)
} }
r[iface.Name] = &Interface{ r[iface.Name] = &Interface{
@ -61,7 +66,7 @@ func ResolveInterface(name string) (*Interface, error) {
return nil, err return nil, err
} }
ifaces := value.(map[string]*Interface) ifaces := value
iface, ok := ifaces[name] iface, ok := ifaces[name]
if !ok { if !ok {
return nil, ErrIfaceNotFound return nil, ErrIfaceNotFound
@ -74,35 +79,35 @@ func FlushCache() {
interfaces.Reset() interfaces.Reset()
} }
func (iface *Interface) PickIPv4Addr(destination net.IP) (*net.IPNet, error) { func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr *net.IPNet) bool { return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
return addr.IP.To4() != nil return addr.Addr().Is4()
}) })
} }
func (iface *Interface) PickIPv6Addr(destination net.IP) (*net.IPNet, error) { func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr *net.IPNet) bool { return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
return addr.IP.To4() == nil return addr.Addr().Is6()
}) })
} }
func (iface *Interface) pickIPAddr(destination net.IP, accept func(addr *net.IPNet) bool) (*net.IPNet, error) { func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) {
var fallback *net.IPNet 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.IP.IsLinkLocalUnicast() { if fallback == nil && !addr.Addr().IsLinkLocalUnicast() {
fallback = addr fallback = addr
if destination == nil { if !destination.IsValid() {
break break
} }
} }
if destination != nil && addr.Contains(destination) { if destination.IsValid() && addr.Contains(destination) {
return addr, nil return addr, nil
} }
} }

View File

@ -6,55 +6,55 @@ import (
"time" "time"
) )
type Factory = func(context.Context) (any, error) type Factory[T any] func(context.Context) (T, error)
type entry struct { type entry[T any] struct {
elm any elm T
time time.Time time time.Time
} }
type Option func(*pool) type Option[T any] func(*pool[T])
// WithEvict set the evict callback // WithEvict set the evict callback
func WithEvict(cb func(any)) Option { func WithEvict[T any](cb func(T)) Option[T] {
return func(p *pool) { return func(p *pool[T]) {
p.evict = cb p.evict = cb
} }
} }
// WithAge defined element max age (millisecond) // WithAge defined element max age (millisecond)
func WithAge(maxAge int64) Option { func WithAge[T any](maxAge int64) Option[T] {
return func(p *pool) { return func(p *pool[T]) {
p.maxAge = maxAge p.maxAge = maxAge
} }
} }
// WithSize defined max size of Pool // WithSize defined max size of Pool
func WithSize(maxSize int) Option { func WithSize[T any](maxSize int) Option[T] {
return func(p *pool) { return func(p *pool[T]) {
p.ch = make(chan any, maxSize) p.ch = make(chan *entry[T], maxSize)
} }
} }
// Pool is for GC, see New for detail // Pool is for GC, see New for detail
type Pool struct { type Pool[T any] struct {
*pool *pool[T]
} }
type pool struct { type pool[T any] struct {
ch chan any ch chan *entry[T]
factory Factory factory Factory[T]
evict func(any) evict func(T)
maxAge int64 maxAge int64
} }
func (p *pool) GetContext(ctx context.Context) (any, error) { func (p *pool[T]) GetContext(ctx context.Context) (T, error) {
now := time.Now() now := time.Now()
for { for {
select { select {
case item := <-p.ch: case item := <-p.ch:
elm := item.(*entry) elm := item
if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge { if p.maxAge != 0 && now.Sub(item.time).Milliseconds() > p.maxAge {
if p.evict != nil { if p.evict != nil {
p.evict(elm.elm) p.evict(elm.elm)
} }
@ -68,12 +68,12 @@ func (p *pool) GetContext(ctx context.Context) (any, error) {
} }
} }
func (p *pool) Get() (any, error) { func (p *pool[T]) Get() (T, error) {
return p.GetContext(context.Background()) return p.GetContext(context.Background())
} }
func (p *pool) Put(item any) { func (p *pool[T]) Put(item T) {
e := &entry{ e := &entry[T]{
elm: item, elm: item,
time: time.Now(), time: time.Now(),
} }
@ -90,17 +90,17 @@ func (p *pool) Put(item any) {
} }
} }
func recycle(p *Pool) { func recycle[T any](p *Pool[T]) {
for item := range p.pool.ch { for item := range p.pool.ch {
if p.pool.evict != nil { if p.pool.evict != nil {
p.pool.evict(item.(*entry).elm) p.pool.evict(item.elm)
} }
} }
} }
func New(factory Factory, options ...Option) *Pool { func New[T any](factory Factory[T], options ...Option[T]) *Pool[T] {
p := &pool{ p := &pool[T]{
ch: make(chan any, 10), ch: make(chan *entry[T], 10),
factory: factory, factory: factory,
} }
@ -108,7 +108,7 @@ func New(factory Factory, options ...Option) *Pool {
option(p) option(p)
} }
P := &Pool{p} P := &Pool[T]{p}
runtime.SetFinalizer(P, recycle) runtime.SetFinalizer(P, recycle[T])
return P return P
} }

View File

@ -8,9 +8,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func lg() Factory { func lg() Factory[int] {
initial := -1 initial := -1
return func(context.Context) (any, error) { return func(context.Context) (int, error) {
initial++ initial++
return initial, nil return initial, nil
} }
@ -18,23 +18,23 @@ func lg() Factory {
func TestPool_Basic(t *testing.T) { func TestPool_Basic(t *testing.T) {
g := lg() g := lg()
pool := New(g) pool := New[int](g)
elm, _ := pool.Get() elm, _ := pool.Get()
assert.Equal(t, 0, elm.(int)) assert.Equal(t, 0, elm)
pool.Put(elm) pool.Put(elm)
elm, _ = pool.Get() elm, _ = pool.Get()
assert.Equal(t, 0, elm.(int)) assert.Equal(t, 0, elm)
elm, _ = pool.Get() elm, _ = pool.Get()
assert.Equal(t, 1, elm.(int)) assert.Equal(t, 1, elm)
} }
func TestPool_MaxSize(t *testing.T) { func TestPool_MaxSize(t *testing.T) {
g := lg() g := lg()
size := 5 size := 5
pool := New(g, WithSize(size)) pool := New[int](g, WithSize[int](size))
var items []any var items []int
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
item, _ := pool.Get() item, _ := pool.Get()
@ -42,7 +42,7 @@ func TestPool_MaxSize(t *testing.T) {
} }
extra, _ := pool.Get() extra, _ := pool.Get()
assert.Equal(t, size, extra.(int)) assert.Equal(t, size, extra)
for _, item := range items { for _, item := range items {
pool.Put(item) pool.Put(item)
@ -52,22 +52,22 @@ func TestPool_MaxSize(t *testing.T) {
for _, item := range items { for _, item := range items {
elm, _ := pool.Get() elm, _ := pool.Get()
assert.Equal(t, item.(int), elm.(int)) assert.Equal(t, item, elm)
} }
} }
func TestPool_MaxAge(t *testing.T) { func TestPool_MaxAge(t *testing.T) {
g := lg() g := lg()
pool := New(g, WithAge(20)) pool := New[int](g, WithAge[int](20))
elm, _ := pool.Get() elm, _ := pool.Get()
pool.Put(elm) pool.Put(elm)
elm, _ = pool.Get() elm, _ = pool.Get()
assert.Equal(t, 0, elm.(int)) assert.Equal(t, 0, elm)
pool.Put(elm) pool.Put(elm)
time.Sleep(time.Millisecond * 22) time.Sleep(time.Millisecond * 22)
elm, _ = pool.Get() elm, _ = pool.Get()
assert.Equal(t, 1, elm.(int)) assert.Equal(t, 1, elm)
} }

View File

@ -2,10 +2,11 @@ package process
import ( import (
"errors" "errors"
"net" "github.com/Dreamacro/clash/common/nnip"
"runtime"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"net"
"net/netip"
"runtime"
) )
var ( var (
@ -19,35 +20,43 @@ const (
UDP = "udp" UDP = "udp"
) )
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) { func FindProcessName(network string, srcIP netip.Addr, srcPort int) (string, error) {
return findProcessName(network, srcIP, srcPort) return findProcessName(network, srcIP, srcPort)
} }
func FindUid(network string, srcIP netip.Addr, srcPort int) (int32, error) {
_, uid, err := resolveSocketByNetlink(network, srcIP, srcPort)
if err != nil {
return -1, err
}
return uid, nil
}
func ShouldFindProcess(metadata *C.Metadata) bool { func ShouldFindProcess(metadata *C.Metadata) bool {
if runtime.GOOS == "android" { if runtime.GOOS == "android" {
return false return false
} }
if metadata.Process != "" { if metadata.Process != "" || metadata.ProcessPath != "" {
return false return false
} }
for _, ip := range localIPs { for _, ip := range localIPs {
if ip.Equal(metadata.SrcIP) { if ip == metadata.SrcIP {
return true return true
} }
} }
return false return false
} }
func AppendLocalIPs(ip ...net.IP) { func AppendLocalIPs(ip ...netip.Addr) {
localIPs = append(ip, localIPs...) localIPs = append(ip, localIPs...)
} }
func getLocalIPs() []net.IP { func getLocalIPs() []netip.Addr {
ips := []net.IP{net.IPv4zero, net.IPv6zero} ips := []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()}
netInterfaces, err := net.Interfaces() netInterfaces, err := net.Interfaces()
if err != nil { if err != nil {
ips = append(ips, net.IPv4(127, 0, 0, 1), net.IPv6loopback) ips = append(ips, netip.AddrFrom4([4]byte{127, 0, 0, 1}), nnip.IpToAddr(net.IPv6loopback))
return ips return ips
} }
@ -57,7 +66,7 @@ func getLocalIPs() []net.IP {
for _, address := range adds { for _, address := range adds {
if ipNet, ok := address.(*net.IPNet); ok { if ipNet, ok := address.(*net.IPNet); ok {
ips = append(ips, ipNet.IP) ips = append(ips, nnip.IpToAddr(ipNet.IP))
} }
} }
} }
@ -66,7 +75,7 @@ func getLocalIPs() []net.IP {
return ips return ips
} }
var localIPs []net.IP var localIPs []netip.Addr
func init() { func init() {
localIPs = getLocalIPs() localIPs = getLocalIPs()

View File

@ -2,10 +2,12 @@ package process
import ( import (
"encoding/binary" "encoding/binary"
"net" "net/netip"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -15,7 +17,11 @@ const (
proccallnumpidinfo = 0x2 proccallnumpidinfo = 0x2
) )
func findProcessName(network string, ip net.IP, port int) (string, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
return 0, 0, ErrPlatformNotSupport
}
func findProcessName(network string, ip netip.Addr, port int) (string, error) {
var spath string var spath string
switch network { switch network {
case TCP: case TCP:
@ -26,7 +32,7 @@ func findProcessName(network string, ip net.IP, port int) (string, error) {
return "", ErrInvalidNetwork return "", ErrInvalidNetwork
} }
isIPv4 := ip.To4() != nil isIPv4 := ip.Is4()
value, err := syscall.Sysctl(spath) value, err := syscall.Sysctl(spath)
if err != nil { if err != nil {
@ -57,19 +63,19 @@ func findProcessName(network string, ip net.IP, port int) (string, error) {
// xinpcb_n.inp_vflag // xinpcb_n.inp_vflag
flag := buf[inp+44] flag := buf[inp+44]
var srcIP net.IP var srcIP netip.Addr
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = net.IP(buf[inp+76 : inp+80]) srcIP = nnip.IpToAddr(buf[inp+76 : inp+80])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = net.IP(buf[inp+64 : inp+80]) srcIP = nnip.IpToAddr(buf[inp+64 : inp+80])
default: default:
continue continue
} }
if !ip.Equal(srcIP) && (network == TCP || !srcIP.IsUnspecified()) { if ip != srcIP && (network == TCP || !srcIP.IsUnspecified()) {
continue continue
} }

View File

@ -3,13 +3,14 @@ package process
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net" "net/netip"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
@ -20,7 +21,11 @@ var (
once sync.Once once sync.Once
) )
func findProcessName(network string, ip net.IP, srcPort int) (string, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
return 0, 0, ErrPlatformNotSupport
}
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
once.Do(func() { once.Do(func() {
if err := initSearcher(); err != nil { if err := initSearcher(); err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
@ -102,7 +107,7 @@ type searcher struct {
pid int pid int
} }
func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) { func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (uint32, error) {
var itemSize int var itemSize int
var inpOffset int var inpOffset int
@ -116,7 +121,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3
inpOffset = s.udpInpOffset inpOffset = s.udpInpOffset
} }
isIPv4 := ip.To4() != nil isIPv4 := ip.Is4()
// skip the first xinpgen block // skip the first xinpgen block
for i := s.headSize; i+itemSize <= len(buf); i += itemSize { for i := s.headSize; i+itemSize <= len(buf); i += itemSize {
inp := i + inpOffset inp := i + inpOffset
@ -130,19 +135,19 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3
// xinpcb.inp_vflag // xinpcb.inp_vflag
flag := buf[inp+s.vflag] flag := buf[inp+s.vflag]
var srcIP net.IP var srcIP netip.Addr
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4]) srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4]) srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4])
default: default:
continue continue
} }
if !ip.Equal(srcIP) { if ip != srcIP {
continue continue
} }

View File

@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net" "net"
"net/netip"
"os" "os"
"path" "path"
"strings" "strings"
@ -31,16 +32,15 @@ const (
pathProc = "/proc" pathProc = "/proc"
) )
func findProcessName(network string, ip net.IP, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil { if err != nil {
return "", err return "", err
} }
return resolveProcessNameByProcSearch(inode, uid) return resolveProcessNameByProcSearch(inode, uid)
} }
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int32, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
var family byte var family byte
var protocol byte var protocol byte
@ -53,7 +53,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3
return 0, 0, ErrInvalidNetwork return 0, 0, ErrInvalidNetwork
} }
if ip.To4() != nil { if ip.Is4() {
family = syscall.AF_INET family = syscall.AF_INET
} else { } else {
family = syscall.AF_INET6 family = syscall.AF_INET6
@ -65,10 +65,12 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3
if err != nil { if err != nil {
return 0, 0, fmt.Errorf("dial netlink: %w", err) return 0, 0, fmt.Errorf("dial netlink: %w", err)
} }
defer syscall.Close(socket) defer func() {
_ = syscall.Close(socket)
}()
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100}) _ = syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100}) _ = syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
if err := syscall.Connect(socket, &syscall.SockaddrNetlink{ if err := syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK, Family: syscall.AF_NETLINK,
@ -84,7 +86,9 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3
} }
rb := pool.Get(pool.RelayBufferSize) rb := pool.Get(pool.RelayBufferSize)
defer pool.Put(rb) defer func() {
_ = pool.Put(rb)
}()
n, err := syscall.Read(socket, rb) n, err := syscall.Read(socket, rb)
if err != nil { if err != nil {
@ -103,7 +107,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3
return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR") return 0, 0, fmt.Errorf("netlink message: NLMSG_ERROR")
} }
inode, uid := unpackSocketDiagResponse(&messages[0]) inode, uid := unpackSocketDiagResponse(&message)
if inode < 0 || uid < 0 { if inode < 0 || uid < 0 {
return 0, 0, fmt.Errorf("invalid inode(%d) or uid(%d)", inode, uid) return 0, 0, fmt.Errorf("invalid inode(%d) or uid(%d)", inode, uid)
} }
@ -111,14 +115,10 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int32, int3
return inode, uid, nil return inode, uid, nil
} }
func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte { func packSocketDiagRequest(family, protocol byte, source netip.Addr, sourcePort uint16) []byte {
s := make([]byte, 16) s := make([]byte, 16)
if v4 := source.To4(); v4 != nil { copy(s, source.AsSlice())
copy(s, v4)
} else {
copy(s, source)
}
buf := make([]byte, sizeOfSocketDiagRequest) buf := make([]byte, sizeOfSocketDiagRequest)

View File

@ -2,8 +2,12 @@
package process package process
import "net" import "net/netip"
func findProcessName(network string, ip net.IP, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
return "", ErrPlatformNotSupport return "", ErrPlatformNotSupport
} }
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
return 0, 0, ErrPlatformNotSupport
}

View File

@ -2,11 +2,12 @@ package process
import ( import (
"fmt" "fmt"
"net" "net/netip"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -28,6 +29,10 @@ var (
once sync.Once once sync.Once
) )
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) {
return 0, 0, ErrPlatformNotSupport
}
func initWin32API() error { func initWin32API() error {
h, err := windows.LoadLibrary("iphlpapi.dll") h, err := windows.LoadLibrary("iphlpapi.dll")
if err != nil { if err != nil {
@ -57,7 +62,7 @@ func initWin32API() error {
return nil return nil
} }
func findProcessName(network string, ip net.IP, srcPort int) (string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@ -67,7 +72,7 @@ func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
} }
}) })
family := windows.AF_INET family := windows.AF_INET
if ip.To4() == nil { if ip.Is6() {
family = windows.AF_INET6 family = windows.AF_INET6
} }
@ -107,7 +112,7 @@ type searcher struct {
tcpState int tcpState int
} }
func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) { func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
n := int(readNativeUint32(b[:4])) n := int(readNativeUint32(b[:4]))
itemSize := s.itemSize itemSize := s.itemSize
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -131,9 +136,9 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) {
continue continue
} }
srcIP := net.IP(row[s.ip : s.ip+s.ipSize]) srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize])
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) { if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue
} }
@ -214,8 +219,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
uintptr(h), uintptr(h),
uintptr(1), uintptr(1),
uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)), uintptr(unsafe.Pointer(&size)))
0, 0)
if r1 == 0 { if r1 == 0 {
return "", err return "", err
} }

View File

@ -0,0 +1,12 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
package resolver
import _ "unsafe"
//go:linkname defaultNS net.defaultNS
var defaultNS []string
func init() {
defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"}
}

View File

@ -1,20 +1,19 @@
package resolver package resolver
import ( import "net/netip"
"net"
)
var DefaultHostMapper Enhancer var DefaultHostMapper Enhancer
type Enhancer interface { type Enhancer interface {
FakeIPEnabled() bool FakeIPEnabled() bool
MappingEnabled() bool MappingEnabled() bool
IsFakeIP(net.IP) bool IsFakeIP(netip.Addr) bool
IsFakeBroadcastIP(net.IP) bool IsFakeBroadcastIP(netip.Addr) bool
IsExistFakeIP(net.IP) bool IsExistFakeIP(netip.Addr) bool
FindHostByIP(net.IP) (string, bool) FindHostByIP(netip.Addr) (string, bool)
FlushFakeIP() error FlushFakeIP() error
InsertHostByIP(net.IP, string) InsertHostByIP(netip.Addr, string)
StoreFakePoolState()
} }
func FakeIPEnabled() bool { func FakeIPEnabled() bool {
@ -33,7 +32,7 @@ func MappingEnabled() bool {
return false return false
} }
func IsFakeIP(ip net.IP) bool { func IsFakeIP(ip netip.Addr) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsFakeIP(ip) return mapper.IsFakeIP(ip)
} }
@ -41,7 +40,7 @@ func IsFakeIP(ip net.IP) bool {
return false return false
} }
func IsFakeBroadcastIP(ip net.IP) bool { func IsFakeBroadcastIP(ip netip.Addr) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsFakeBroadcastIP(ip) return mapper.IsFakeBroadcastIP(ip)
} }
@ -49,7 +48,7 @@ func IsFakeBroadcastIP(ip net.IP) bool {
return false return false
} }
func IsExistFakeIP(ip net.IP) bool { func IsExistFakeIP(ip netip.Addr) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsExistFakeIP(ip) return mapper.IsExistFakeIP(ip)
} }
@ -57,13 +56,13 @@ func IsExistFakeIP(ip net.IP) bool {
return false return false
} }
func InsertHostByIP(ip net.IP, host string) { func InsertHostByIP(ip netip.Addr, host string) {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
mapper.InsertHostByIP(ip, host) mapper.InsertHostByIP(ip, host)
} }
} }
func FindHostByIP(ip net.IP) (string, bool) { func FindHostByIP(ip netip.Addr) (string, bool) {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.FindHostByIP(ip) return mapper.FindHostByIP(ip)
} }
@ -77,3 +76,9 @@ func FlushFakeIP() error {
} }
return nil return nil
} }
func StoreFakePoolState() {
if mapper := DefaultHostMapper; mapper != nil {
mapper.StoreFakePoolState()
}
}

View File

@ -6,9 +6,9 @@ import (
"math/rand" "math/rand"
"net" "net"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
) )
@ -37,135 +37,57 @@ var (
) )
type Resolver interface { type Resolver interface {
ResolveIP(host string) (ip net.IP, err error) ResolveIP(host string) (ip netip.Addr, err error)
ResolveIPv4(host string) (ip net.IP, err error) ResolveIPv4(host string) (ip netip.Addr, err error)
ResolveIPv6(host string) (ip net.IP, err error) ResolveIPv6(host string) (ip netip.Addr, err error)
ResolveAllIP(host string) (ip []netip.Addr, err error)
ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err error)
ResolveAllIPv4(host string) (ips []netip.Addr, err error)
ResolveAllIPv6(host string) (ips []netip.Addr, err error)
} }
// ResolveIPv4 with a host, return ipv4 // ResolveIPv4 with a host, return ipv4
func ResolveIPv4(host string) (net.IP, error) { func ResolveIPv4(host string) (netip.Addr, error) {
return ResolveIPv4WithResolver(host, DefaultResolver) return ResolveIPv4WithResolver(host, DefaultResolver)
} }
func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) { func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if ips, err := ResolveAllIPv4WithResolver(host, r); err == nil {
if ip := node.Data; ip.Is4() { return ips[rand.Intn(len(ips))], nil
return ip.AsSlice(), nil } else {
} return netip.Addr{}, nil
} }
ip := net.ParseIP(host)
if ip != nil {
if !strings.Contains(host, ":") {
return ip, nil
}
return nil, ErrIPVersion
}
if r != nil {
return r.ResolveIPv4(host)
}
if DefaultResolver == nil {
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs[rand.Intn(len(ipAddrs))], nil
}
return nil, ErrIPNotFound
} }
// ResolveIPv6 with a host, return ipv6 // ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (net.IP, error) { func ResolveIPv6(host string) (netip.Addr, error) {
return ResolveIPv6WithResolver(host, DefaultResolver) return ResolveIPv6WithResolver(host, DefaultResolver)
} }
func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) { func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
if DisableIPv6 { if ips, err := ResolveAllIPv6WithResolver(host, r); err == nil {
return nil, ErrIPv6Disabled return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
} }
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is6() {
return ip.AsSlice(), nil
}
}
ip := net.ParseIP(host)
if ip != nil {
if strings.Contains(host, ":") {
return ip, nil
}
return nil, ErrIPVersion
}
if r != nil {
return r.ResolveIPv6(host)
}
if DefaultResolver == nil {
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs[rand.Intn(len(ipAddrs))], nil
}
return nil, ErrIPNotFound
} }
// ResolveIPWithResolver same as ResolveIP, but with a resolver // ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) { func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil { if ips, err := ResolveAllIPPrimaryIPv4WithResolver(host, r); err == nil {
ip := node.Data return ips[rand.Intn(len(ips))], nil
return ip.Unmap().AsSlice(), nil } else {
return netip.Addr{}, err
} }
if r != nil {
if DisableIPv6 {
return r.ResolveIPv4(host)
}
return r.ResolveIP(host)
} else if DisableIPv6 {
return ResolveIPv4(host)
}
ip := net.ParseIP(host)
if ip != nil {
return ip, nil
}
if DefaultResolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
return ipAddr.IP, nil
}
return nil, ErrIPNotFound
} }
// ResolveIP with a host, return ip // ResolveIP with a host, return ip
func ResolveIP(host string) (net.IP, error) { func ResolveIP(host string) (netip.Addr, error) {
return ResolveIPWithResolver(host, DefaultResolver) return ResolveIPWithResolver(host, DefaultResolver)
} }
// ResolveIPv4ProxyServerHost proxies server host only // ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPv4ProxyServerHost(host string) (net.IP, error) { func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPv4WithResolver(host, ProxyServerHostResolver) return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
} }
@ -173,7 +95,7 @@ func ResolveIPv4ProxyServerHost(host string) (net.IP, error) {
} }
// ResolveIPv6ProxyServerHost proxies server host only // ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(host string) (net.IP, error) { func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPv6WithResolver(host, ProxyServerHostResolver) return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
} }
@ -181,9 +103,188 @@ func ResolveIPv6ProxyServerHost(host string) (net.IP, error) {
} }
// ResolveProxyServerHost proxies server host only // ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(host string) (net.IP, error) { func ResolveProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveIPWithResolver(host, ProxyServerHostResolver) return ResolveIPWithResolver(host, ProxyServerHostResolver)
} }
return ResolveIP(host) return ResolveIP(host)
} }
func ResolveAllIPv6WithResolver(host string, r Resolver) ([]netip.Addr, error) {
if DisableIPv6 {
return []netip.Addr{}, ErrIPv6Disabled
}
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is6() {
return []netip.Addr{ip}, nil
}
}
ip, err := netip.ParseAddr(host)
if err == nil {
if ip.Is6() {
return []netip.Addr{ip}, nil
}
return []netip.Addr{}, ErrIPVersion
}
if r != nil {
return r.ResolveAllIPv6(host)
}
if DefaultResolver == nil {
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip6", host)
if err != nil {
return []netip.Addr{}, err
} else if len(ipAddrs) == 0 {
return []netip.Addr{}, ErrIPNotFound
}
return []netip.Addr{netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))]))}, nil
}
return []netip.Addr{}, ErrIPNotFound
}
func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is4() {
return []netip.Addr{node.Data}, nil
}
}
ip, err := netip.ParseAddr(host)
if err == nil {
if ip.Is4() {
return []netip.Addr{ip}, nil
}
return []netip.Addr{}, ErrIPVersion
}
if r != nil {
return r.ResolveAllIPv4(host)
}
if DefaultResolver == nil {
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil {
return []netip.Addr{}, err
} else if len(ipAddrs) == 0 {
return []netip.Addr{}, ErrIPNotFound
}
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4()
if ip == nil {
return []netip.Addr{}, ErrIPVersion
}
return []netip.Addr{netip.AddrFrom4(*(*[4]byte)(ip))}, nil
}
return []netip.Addr{}, ErrIPNotFound
}
func ResolveAllIPWithResolver(host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil {
return []netip.Addr{node.Data}, nil
}
if r != nil {
if DisableIPv6 {
return r.ResolveAllIPv4(host)
}
return r.ResolveAllIP(host)
} else if DisableIPv6 {
return ResolveAllIPv4(host)
}
ip, err := netip.ParseAddr(host)
if err == nil {
return []netip.Addr{ip}, nil
}
if DefaultResolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return []netip.Addr{}, err
}
return []netip.Addr{nnip.IpToAddr(ipAddr.IP)}, nil
}
return []netip.Addr{}, ErrIPNotFound
}
func ResolveAllIPPrimaryIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil {
return []netip.Addr{node.Data}, nil
}
if r != nil {
if DisableIPv6 {
return r.ResolveAllIPv4(host)
}
return r.ResolveAllIPPrimaryIPv4(host)
} else if DisableIPv6 {
return ResolveAllIPv4(host)
}
ip, err := netip.ParseAddr(host)
if err == nil {
return []netip.Addr{ip}, nil
}
if DefaultResolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return []netip.Addr{}, err
}
return []netip.Addr{nnip.IpToAddr(ipAddr.IP)}, nil
}
return []netip.Addr{}, ErrIPNotFound
}
func ResolveAllIP(host string) ([]netip.Addr, error) {
return ResolveAllIPWithResolver(host, DefaultResolver)
}
func ResolveAllIPv4(host string) ([]netip.Addr, error) {
return ResolveAllIPv4WithResolver(host, DefaultResolver)
}
func ResolveAllIPv6(host string) ([]netip.Addr, error) {
return ResolveAllIPv6WithResolver(host, DefaultResolver)
}
func ResolveAllIPv6ProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveAllIPv6WithResolver(host, ProxyServerHostResolver)
}
return ResolveAllIPv6(host)
}
func ResolveAllIPv4ProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveAllIPv4WithResolver(host, ProxyServerHostResolver)
}
return ResolveAllIPv4(host)
}
func ResolveAllIPProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveAllIPWithResolver(host, ProxyServerHostResolver)
}
return ResolveAllIP(host)
}

View File

@ -2,10 +2,16 @@ package sniffer
import ( import (
"errors" "errors"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/constant/sniffer"
"net" "net"
"net/netip"
"strconv"
"time"
"github.com/Dreamacro/clash/component/trie"
CN "github.com/Dreamacro/clash/common/net" CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -14,18 +20,22 @@ import (
var ( var (
ErrorUnsupportedSniffer = errors.New("unsupported sniffer") ErrorUnsupportedSniffer = errors.New("unsupported sniffer")
ErrorSniffFailed = errors.New("all sniffer failed") ErrorSniffFailed = errors.New("all sniffer failed")
ErrNoClue = errors.New("not enough information for making a decision")
) )
var Dispatcher SnifferDispatcher var Dispatcher SnifferDispatcher
type SnifferDispatcher struct { type (
enable bool SnifferDispatcher struct {
enable bool
sniffers []C.Sniffer sniffers []sniffer.Sniffer
foreDomain *trie.DomainTrie[bool] foreDomain *trie.DomainTrie[bool]
skipSNI *trie.DomainTrie[bool] skipSNI *trie.DomainTrie[bool]
} portRanges *[]utils.Range[uint16]
}
)
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
bufConn, ok := conn.(*CN.BufferedConn) bufConn, ok := conn.(*CN.BufferedConn)
@ -34,6 +44,24 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
} }
if metadata.Host == "" || sd.foreDomain.Search(metadata.Host) != nil { if metadata.Host == "" || sd.foreDomain.Search(metadata.Host) != nil {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil {
log.Debugln("[Sniffer] Dst port is error")
return
}
inWhitelist := false
for _, portRange := range *sd.portRanges {
if portRange.Contains(uint16(port)) {
inWhitelist = true
break
}
}
if !inWhitelist {
return
}
if host, err := sd.sniffDomain(bufConn, metadata); err != nil { if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return return
@ -58,7 +86,6 @@ func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) {
metadata.Host = host metadata.Host = host
metadata.DNSMode = C.DNSMapping metadata.DNSMode = C.DNSMapping
resolver.InsertHostByIP(metadata.DstIP, host) resolver.InsertHostByIP(metadata.DstIP, host)
metadata.DstIP = nil
} }
func (sd *SnifferDispatcher) Enable() bool { func (sd *SnifferDispatcher) Enable() bool {
@ -68,8 +95,16 @@ func (sd *SnifferDispatcher) Enable() bool {
func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) { func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) {
for _, sniffer := range sd.sniffers { for _, sniffer := range sd.sniffers {
if sniffer.SupportNetwork() == C.TCP { if sniffer.SupportNetwork() == C.TCP {
_ = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
_, err := conn.Peek(1) _, err := conn.Peek(1)
_ = conn.SetReadDeadline(time.Time{})
if err != nil { if err != nil {
_, ok := err.(*net.OpError)
if ok {
log.Errorln("[Sniffer] [%s] may not have any sent data, Consider adding skip", metadata.DstIP.String())
_ = conn.Close()
}
return "", err return "", err
} }
@ -86,6 +121,12 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta
continue continue
} }
_, err = netip.ParseAddr(host)
if err == nil {
log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
continue
}
return host, nil return host, nil
} }
} }
@ -101,11 +142,13 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil return &dispatcher, nil
} }
func NewSnifferDispatcher(needSniffer []C.SnifferType, forceDomain *trie.DomainTrie[bool], skipSNI *trie.DomainTrie[bool]) (*SnifferDispatcher, error) { func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[bool],
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{ dispatcher := SnifferDispatcher{
enable: true, enable: true,
foreDomain: forceDomain, foreDomain: forceDomain,
skipSNI: skipSNI, skipSNI: skipSNI,
portRanges: ports,
} }
for _, snifferName := range needSniffer { for _, snifferName := range needSniffer {
@ -121,10 +164,12 @@ func NewSnifferDispatcher(needSniffer []C.SnifferType, forceDomain *trie.DomainT
return &dispatcher, nil return &dispatcher, nil
} }
func NewSniffer(name C.SnifferType) (C.Sniffer, error) { func NewSniffer(name sniffer.Type) (sniffer.Sniffer, error) {
switch name { switch name {
case C.TLS: case sniffer.TLS:
return &TLSSniffer{}, nil return &TLSSniffer{}, nil
case sniffer.HTTP:
return &HTTPSniffer{}, nil
default: default:
return nil, ErrorUnsupportedSniffer return nil, ErrorUnsupportedSniffer
} }

View File

@ -0,0 +1,100 @@
package sniffer
import (
"bytes"
"errors"
C "github.com/Dreamacro/clash/constant"
"net"
"strings"
)
var (
// refer to https://pkg.go.dev/net/http@master#pkg-constants
methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect", "patch", "trace"}
errNotHTTPMethod = errors.New("not an HTTP method")
)
type version byte
const (
HTTP1 version = iota
HTTP2
)
type HTTPSniffer struct {
version version
host string
}
func (http *HTTPSniffer) Protocol() string {
switch http.version {
case HTTP1:
return "http1"
case HTTP2:
return "http2"
default:
return "unknown"
}
}
func (http *HTTPSniffer) SupportNetwork() C.NetWork {
return C.TCP
}
func (http *HTTPSniffer) SniffTCP(bytes []byte) (string, error) {
domain, err := SniffHTTP(bytes)
if err == nil {
return *domain, nil
} else {
return "", err
}
}
func beginWithHTTPMethod(b []byte) error {
for _, m := range &methods {
if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) {
return nil
}
if len(b) < len(m) {
return ErrNoClue
}
}
return errNotHTTPMethod
}
func SniffHTTP(b []byte) (*string, error) {
if err := beginWithHTTPMethod(b); err != nil {
return nil, err
}
_ = &HTTPSniffer{
version: HTTP1,
}
headers := bytes.Split(b, []byte{'\n'})
for i := 1; i < len(headers); i++ {
header := headers[i]
if len(header) == 0 {
break
}
parts := bytes.SplitN(header, []byte{':'}, 2)
if len(parts) != 2 {
continue
}
key := strings.ToLower(string(parts[0]))
if key == "host" {
rawHost := strings.ToLower(string(bytes.TrimSpace(parts[1])))
host, _, err := net.SplitHostPort(rawHost)
if err != nil {
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
host = rawHost
} else {
return nil, err
}
}
return &host, nil
}
}
return nil, ErrNoClue
}

View File

@ -0,0 +1,3 @@
package sniffer
//TODO

View File

@ -11,7 +11,6 @@ import (
var ( var (
errNotTLS = errors.New("not TLS header") errNotTLS = errors.New("not TLS header")
errNotClientHello = errors.New("not client hello") errNotClientHello = errors.New("not client hello")
ErrNoClue = errors.New("not enough information for making a decision")
) )
type TLSSniffer struct { type TLSSniffer struct {

View File

@ -4,16 +4,21 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
R "github.com/Dreamacro/clash/rule" "github.com/Dreamacro/clash/constant/sniffer"
RP "github.com/Dreamacro/clash/rule/provider" "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/utils"
R "github.com/Dreamacro/clash/rule"
RP "github.com/Dreamacro/clash/rule/provider"
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
@ -26,8 +31,8 @@ import (
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider" providerTypes "github.com/Dreamacro/clash/constant/provider"
snifferTypes "github.com/Dreamacro/clash/constant/sniffer"
"github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
@ -46,6 +51,8 @@ type General struct {
RoutingMark int `json:"-"` RoutingMark int `json:"-"`
GeodataMode bool `json:"geodata-mode"` GeodataMode bool `json:"geodata-mode"`
GeodataLoader string `json:"geodata-loader"` GeodataLoader string `json:"geodata-loader"`
TCPConcurrent bool `json:"tcp-concurrent"`
Tun Tun `json:"tun"`
} }
// Inbound config // Inbound config
@ -87,7 +94,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 []*net.IPNet `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"`
} }
@ -106,11 +113,12 @@ type Profile struct {
// Tun config // Tun config
type Tun struct { type Tun struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"` Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"` Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"` DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoRoute bool `yaml:"auto-route" json:"auto-route"`
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
} }
// IPTables config // IPTables config
@ -123,10 +131,11 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Force bool Force bool
Sniffers []C.SnifferType Sniffers []sniffer.Type
Reverses *trie.DomainTrie[bool] Reverses *trie.DomainTrie[bool]
ForceDomain *trie.DomainTrie[bool] ForceDomain *trie.DomainTrie[bool]
SkipSNI *trie.DomainTrie[bool] SkipDomain *trie.DomainTrie[bool]
Ports *[]utils.Range[uint16]
} }
// Experimental config // Experimental config
@ -145,7 +154,7 @@ type Config struct {
Users []auth.AuthUser Users []auth.AuthUser
Proxies map[string]C.Proxy Proxies map[string]C.Proxy
Providers map[string]providerTypes.ProxyProvider Providers map[string]providerTypes.ProxyProvider
RuleProviders map[string]*providerTypes.RuleProvider RuleProviders map[string]providerTypes.RuleProvider
Sniffer *Sniffer Sniffer *Sniffer
} }
@ -202,6 +211,7 @@ type RawConfig struct {
RoutingMark int `yaml:"routing-mark"` RoutingMark int `yaml:"routing-mark"`
GeodataMode bool `yaml:"geodata-mode"` GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"` GeodataLoader string `yaml:"geodata-loader"`
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
Sniffer SnifferRaw `yaml:"sniffer"` Sniffer SnifferRaw `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
@ -223,7 +233,9 @@ type SnifferRaw struct {
Force bool `yaml:"force" json:"force"` Force bool `yaml:"force" json:"force"`
Reverse []string `yaml:"reverses" json:"reverses"` Reverse []string `yaml:"reverses" json:"reverses"`
ForceDomain []string `yaml:"force-domain" json:"force-domain"` ForceDomain []string `yaml:"force-domain" json:"force-domain"`
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
SkipSNI []string `yaml:"skip-sni" json:"skip-sni"` SkipSNI []string `yaml:"skip-sni" json:"skip-sni"`
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
} }
// Parse config // Parse config
@ -251,13 +263,14 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Rule: []string{}, Rule: []string{},
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
Device: "", Device: "",
AutoDetectInterface: true,
Stack: C.TunGvisor, Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true, AutoRoute: false,
AutoDetectInterface: false,
}, },
IPTables: IPTables{ IPTables: IPTables{
Enable: false, Enable: false,
@ -297,7 +310,8 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Sniffing: []string{}, Sniffing: []string{},
Reverse: []string{}, Reverse: []string{},
ForceDomain: []string{}, ForceDomain: []string{},
SkipSNI: []string{}, SkipDomain: []string{},
Ports: []string{},
}, },
Profile: Profile{ Profile: Profile{
StoreSelected: true, StoreSelected: true,
@ -406,6 +420,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
RoutingMark: cfg.RoutingMark, RoutingMark: cfg.RoutingMark,
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent,
}, nil }, nil
} }
@ -512,8 +527,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return proxies, providersMap, nil return proxies, providersMap, nil
} }
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]*providerTypes.RuleProvider, error) { func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]providerTypes.RuleProvider, error) {
ruleProviders := map[string]*providerTypes.RuleProvider{} ruleProviders := map[string]providerTypes.RuleProvider{}
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
// parse rule provider // parse rule provider
for name, mapping := range cfg.RuleProvider { for name, mapping := range cfg.RuleProvider {
@ -522,13 +537,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
return nil, nil, err return nil, nil, err
} }
ruleProviders[name] = &rp ruleProviders[name] = rp
RP.SetRuleProvider(rp) RP.SetRuleProvider(rp)
} }
var rules []C.Rule var rules []C.Rule
rulesConfig := cfg.Rule rulesConfig := cfg.Rule
mode := cfg.Mode
// parse rules // parse rules
for idx, line := range rulesConfig { for idx, line := range rulesConfig {
@ -540,10 +554,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
ruleName = strings.ToUpper(rule[0]) ruleName = strings.ToUpper(rule[0])
) )
if mode == T.Script && ruleName != "GEOSITE" {
continue
}
l := len(rule) l := len(rule)
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" { if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" {
@ -678,7 +688,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyAdapter: u.Fragment, ProxyAdapter: u.Fragment,
Interface: dialer.DefaultInterface.Load(), Interface: dialer.DefaultInterface,
}, },
) )
} }
@ -702,15 +712,15 @@ func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServe
return policy, nil return policy, nil
} }
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
var ipNets []*net.IPNet var ipNets []*netip.Prefix
for idx, ip := range ips { for idx, ip := range ips {
_, ipnet, err := net.ParseCIDR(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
@ -763,7 +773,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode, EnhancedMode: cfg.EnhancedMode,
FallbackFilter: FallbackFilter{ FallbackFilter: FallbackFilter{
IPCIDR: []*net.IPNet{}, IPCIDR: []*netip.Prefix{},
GeoSite: []*router.DomainMatcher{}, GeoSite: []*router.DomainMatcher{},
}, },
} }
@ -875,7 +885,7 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
func parseTun(rawTun RawTun, general *General) (*Tun, error) { func parseTun(rawTun RawTun, general *General) (*Tun, error) {
if rawTun.Enable && rawTun.AutoDetectInterface { if rawTun.Enable && rawTun.AutoDetectInterface {
autoDetectInterfaceName, err := commons.GetAutoDetectInterface() autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
if err != nil || autoDetectInterfaceName == "" { if err != nil {
log.Warnln("Can not find auto detect interface.[%s]", err) log.Warnln("Can not find auto detect interface.[%s]", err)
} else { } else {
log.Warnln("Auto detect interface: %s", autoDetectInterfaceName) log.Warnln("Auto detect interface: %s", autoDetectInterfaceName)
@ -890,7 +900,7 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
if _, after, ok := strings.Cut(d, "://"); ok { if _, after, ok := strings.Cut(d, "://"); ok {
d = after d = after
} }
d = strings.Replace(d, "any", "0.0.0.0", 1)
addrPort, err := netip.ParseAddrPort(d) addrPort, err := netip.ParseAddrPort(d)
if err != nil { if err != nil {
return nil, fmt.Errorf("parse dns-hijack url error: %w", err) return nil, fmt.Errorf("parse dns-hijack url error: %w", err)
@ -900,11 +910,12 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
} }
return &Tun{ return &Tun{
Enable: rawTun.Enable, Enable: rawTun.Enable,
Device: rawTun.Device, Device: rawTun.Device,
Stack: rawTun.Stack, Stack: rawTun.Stack,
DNSHijack: dnsHijack, DNSHijack: dnsHijack,
AutoRoute: rawTun.AutoRoute, AutoRoute: rawTun.AutoRoute,
AutoDetectInterface: rawTun.AutoDetectInterface,
}, nil }, nil
} }
@ -914,11 +925,39 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
Force: snifferRaw.Force, Force: snifferRaw.Force,
} }
loadSniffer := make(map[C.SnifferType]struct{}) var ports []utils.Range[uint16]
if len(snifferRaw.Ports) == 0 {
ports = append(ports, *utils.NewRange[uint16](0, 65535))
} else {
for _, portRange := range snifferRaw.Ports {
portRaws := strings.Split(portRange, "-")
p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
start := uint16(p)
if len(portRaws) > 1 {
p, err = strconv.ParseUint(portRaws[1], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
end := uint16(p)
ports = append(ports, *utils.NewRange(start, end))
} else {
ports = append(ports, *utils.NewRange(start, start))
}
}
}
sniffer.Ports = &ports
loadSniffer := make(map[snifferTypes.Type]struct{})
for _, snifferName := range snifferRaw.Sniffing { for _, snifferName := range snifferRaw.Sniffing {
find := false find := false
for _, snifferType := range C.SnifferList { for _, snifferType := range snifferTypes.List {
if snifferType.String() == strings.ToUpper(snifferName) { if snifferType.String() == strings.ToUpper(snifferName) {
find = true find = true
loadSniffer[snifferType] = struct{}{} loadSniffer[snifferType] = struct{}{}
@ -933,7 +972,6 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
for st := range loadSniffer { for st := range loadSniffer {
sniffer.Sniffers = append(sniffer.Sniffers, st) sniffer.Sniffers = append(sniffer.Sniffers, st)
} }
sniffer.ForceDomain = trie.New[bool]() sniffer.ForceDomain = trie.New[bool]()
for _, domain := range snifferRaw.ForceDomain { for _, domain := range snifferRaw.ForceDomain {
err := sniffer.ForceDomain.Insert(domain, true) err := sniffer.ForceDomain.Insert(domain, true)
@ -941,10 +979,13 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err) return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
} }
} }
if snifferRaw.SkipSNI != nil {
sniffer.SkipSNI = trie.New[bool]() log.Warnln("Sniffer param skip-sni renamed to ship-domain, old param will be removed in the release version")
for _, domain := range snifferRaw.SkipSNI { snifferRaw.SkipDomain = snifferRaw.SkipSNI
err := sniffer.SkipSNI.Insert(domain, true) }
sniffer.SkipDomain = trie.New[bool]()
for _, domain := range snifferRaw.SkipDomain {
err := sniffer.SkipDomain.Insert(domain, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err) return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
} }
@ -957,7 +998,7 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
// match all domain // match all domain
sniffer.ForceDomain.Insert("+", true) sniffer.ForceDomain.Insert("+", true)
for _, domain := range snifferRaw.Reverse { for _, domain := range snifferRaw.Reverse {
err := sniffer.SkipSNI.Insert(domain, true) err := sniffer.SkipDomain.Insert(domain, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err) return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
} }

View File

@ -99,6 +99,10 @@ type ProxyAdapter interface {
DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error) DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error)
ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error) ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error)
// SupportUOT return UDP over TCP support
SupportUOT() bool
ListenPacketOnStreamConn(c net.Conn, metadata *Metadata) (PacketConn, error)
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
Unwrap(metadata *Metadata) Proxy Unwrap(metadata *Metadata) Proxy
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"net/netip"
"strconv" "strconv"
) )
@ -73,22 +74,26 @@ func (t Type) MarshalJSON() ([]byte, error) {
// Metadata is used to store connection address // Metadata is used to store connection address
type Metadata struct { type Metadata struct {
NetWork NetWork `json:"network"` NetWork NetWork `json:"network"`
Type Type `json:"type"` Type Type `json:"type"`
SrcIP net.IP `json:"sourceIP"` SrcIP netip.Addr `json:"sourceIP"`
DstIP net.IP `json:"destinationIP"` DstIP netip.Addr `json:"destinationIP"`
SrcPort string `json:"sourcePort"` SrcPort string `json:"sourcePort"`
DstPort string `json:"destinationPort"` DstPort string `json:"destinationPort"`
AddrType int `json:"-"` AddrType int `json:"-"`
Host string `json:"host"` Host string `json:"host"`
DNSMode DNSMode `json:"dnsMode"` DNSMode DNSMode `json:"dnsMode"`
Process string `json:"process"` Uid *int32 `json:"uid"`
ProcessPath string `json:"processPath"` Process string `json:"process"`
ProcessPath string `json:"processPath"`
RemoteDst string `json:"remoteDestination"`
} }
func (m *Metadata) RemoteAddress() string { // avoid stack overflow
return net.JoinHostPort(m.String(), m.DstPort) type jsonMetadata Metadata
func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), m.DstPort)
} }
func (m *Metadata) SourceAddress() string { func (m *Metadata) SourceAddress() string {
@ -96,45 +101,49 @@ func (m *Metadata) SourceAddress() string {
} }
func (m *Metadata) SourceDetail() string { func (m *Metadata) SourceDetail() string {
if m.Process != "" { if m.Type == INNER {
return fmt.Sprintf("[%s]", ClashName)
}
if m.Process != "" && m.Uid != nil {
return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, *m.Uid)
} else if m.Uid != nil {
return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), *m.Uid)
} else if m.Process != "" {
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process) return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
} else { } else {
if m.Type == INNER {
return fmt.Sprintf("[Clash]")
}
return fmt.Sprintf("%s", m.SourceAddress()) return fmt.Sprintf("%s", m.SourceAddress())
} }
} }
func (m *Metadata) Resolved() bool { func (m *Metadata) Resolved() bool {
return m.DstIP != nil return m.DstIP.IsValid()
} }
// Pure is used to solve unexpected behavior // Pure is used to solve unexpected behavior
// when dialing proxy connection in DNSMapping mode. // when dialing proxy connection in DNSMapping mode.
func (m *Metadata) Pure() *Metadata { func (m *Metadata) Pure() *Metadata {
if m.DNSMode == DNSMapping && m.DstIP != nil { if m.DNSMode == DNSMapping && m.DstIP.IsValid() {
copy := *m copyM := *m
copy.Host = "" copyM.Host = ""
if copy.DstIP.To4() != nil { if copyM.DstIP.Is4() {
copy.AddrType = AtypIPv4 copyM.AddrType = AtypIPv4
} else { } else {
copy.AddrType = AtypIPv6 copyM.AddrType = AtypIPv6
} }
return &copy return &copyM
} }
return m return m
} }
func (m *Metadata) UDPAddr() *net.UDPAddr { func (m *Metadata) UDPAddr() *net.UDPAddr {
if m.NetWork != UDP || m.DstIP == nil { if m.NetWork != UDP || !m.DstIP.IsValid() {
return nil return nil
} }
port, _ := strconv.ParseUint(m.DstPort, 10, 16) port, _ := strconv.ParseUint(m.DstPort, 10, 16)
return &net.UDPAddr{ return &net.UDPAddr{
IP: m.DstIP, IP: m.DstIP.AsSlice(),
Port: int(port), Port: int(port),
} }
} }
@ -142,7 +151,7 @@ func (m *Metadata) UDPAddr() *net.UDPAddr {
func (m *Metadata) String() string { func (m *Metadata) String() string {
if m.Host != "" { if m.Host != "" {
return m.Host return m.Host
} else if m.DstIP != nil { } else if m.DstIP.IsValid() {
return m.DstIP.String() return m.DstIP.String()
} else { } else {
return "<nil>" return "<nil>"
@ -150,5 +159,5 @@ func (m *Metadata) String() string {
} }
func (m *Metadata) Valid() bool { func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP != nil return m.Host != "" || m.DstIP.IsValid()
} }

View File

@ -29,7 +29,6 @@ var Path = func() *path {
type path struct { type path struct {
homeDir string homeDir string
configFile string configFile string
scriptDir string
} }
// SetHomeDir is used to set the configuration path // SetHomeDir is used to set the configuration path
@ -123,23 +122,6 @@ func (p *path) GeoSite() string {
return P.Join(p.homeDir, "GeoSite.dat") return P.Join(p.homeDir, "GeoSite.dat")
} }
func (p *path) ScriptDir() string {
if len(p.scriptDir) != 0 {
return p.scriptDir
}
if dir, err := os.MkdirTemp("", Name+"-"); err == nil {
p.scriptDir = dir
} else {
p.scriptDir = P.Join(os.TempDir(), Name)
_ = os.MkdirAll(p.scriptDir, 0o644)
}
return p.scriptDir
}
func (p *path) Script() string {
return P.Join(p.ScriptDir(), "clash_script.py")
}
func (p *path) GetAssetLocation(file string) string { func (p *path) GetAssetLocation(file string) string {
return P.Join(p.homeDir, file) return P.Join(p.homeDir, file)
} }

View File

@ -70,6 +70,7 @@ type ProxyProvider interface {
// Commonly used in DialContext and DialPacketConn // Commonly used in DialContext and DialPacketConn
ProxiesWithTouch() []constant.Proxy ProxiesWithTouch() []constant.Proxy
HealthCheck() HealthCheck()
Version() uint
} }
// Rule Type // Rule Type

View File

@ -13,9 +13,9 @@ const (
DstPort DstPort
Process Process
ProcessPath ProcessPath
Script
RuleSet RuleSet
Network Network
Uid
MATCH MATCH
AND AND
OR OR
@ -48,14 +48,14 @@ func (rt RuleType) String() string {
return "Process" return "Process"
case ProcessPath: case ProcessPath:
return "ProcessPath" return "ProcessPath"
case Script:
return "Script"
case MATCH: case MATCH:
return "Match" return "Match"
case RuleSet: case RuleSet:
return "RuleSet" return "RuleSet"
case Network: case Network:
return "Network" return "Network"
case Uid:
return "Uid"
case AND: case AND:
return "AND" return "AND"
case OR: case OR:

View File

@ -1,7 +1,7 @@
package constant package constant
import ( import (
"net" "net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@ -9,7 +9,7 @@ import (
type RuleExtra struct { type RuleExtra struct {
Network NetWork Network NetWork
SourceIPs []*net.IPNet SourceIPs []*netip.Prefix
ProcessNames []string ProcessNames []string
} }
@ -17,7 +17,7 @@ func (re *RuleExtra) NotMatchNetwork(network NetWork) bool {
return re.Network != ALLNet && re.Network != network return re.Network != ALLNet && re.Network != network
} }
func (re *RuleExtra) NotMatchSourceIP(srcIP net.IP) bool { func (re *RuleExtra) NotMatchSourceIP(srcIP netip.Addr) bool {
if re.SourceIPs == nil { if re.SourceIPs == nil {
return false return false
} }

View File

@ -1,26 +0,0 @@
package constant
type Sniffer interface {
SupportNetwork() NetWork
SniffTCP(bytes []byte) (string, error)
Protocol() string
}
const (
TLS SnifferType = iota
)
var (
SnifferList = []SnifferType{TLS}
)
type SnifferType int
func (rt SnifferType) String() string {
switch rt {
case TLS:
return "TLS"
default:
return "Unknown"
}
}

View File

@ -0,0 +1,31 @@
package sniffer
import "github.com/Dreamacro/clash/constant"
type Sniffer interface {
SupportNetwork() constant.NetWork
SniffTCP(bytes []byte) (string, error)
Protocol() string
}
const (
TLS Type = iota
HTTP
)
var (
List = []Type{TLS, HTTP}
)
type Type int
func (rt Type) String() string {
switch rt {
case TLS:
return "TLS"
case HTTP:
return "HTTP"
default:
return "Unknown"
}
}

View File

@ -4,7 +4,9 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"go.uber.org/atomic"
"net" "net"
"net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@ -18,7 +20,7 @@ type client struct {
r *Resolver r *Resolver
port string port string
host string host string
iface string iface *atomic.String
proxyAdapter string proxyAdapter string
} }
@ -28,10 +30,10 @@ func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
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 net.IP ip netip.Addr
err error err error
) )
if ip = net.ParseIP(c.host); ip == nil { if ip, err = netip.ParseAddr(c.host); err != nil {
if c.r == nil { if c.r == nil {
return nil, fmt.Errorf("dns %s not a valid ip", c.host) return nil, fmt.Errorf("dns %s not a valid ip", c.host)
} else { } else {
@ -48,8 +50,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
} }
options := []dialer.Option{} options := []dialer.Option{}
if c.iface != "" { if c.iface != nil && c.iface.Load() != "" {
options = append(options, dialer.WithInterface(c.iface)) options = append(options, dialer.WithInterface(c.iface.Load()))
} }
var conn net.Conn var conn net.Conn
@ -62,7 +64,9 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer conn.Close() defer func() {
_ = conn.Close()
}()
// miekg/dns ExchangeContext doesn't respond to context cancel. // miekg/dns ExchangeContext doesn't respond to context cancel.
// this is a workaround // this is a workaround

View File

@ -1,9 +1,10 @@
package dns package dns
import ( import (
"bytes"
"context" "context"
"go.uber.org/atomic"
"net" "net"
"net/netip"
"sync" "sync"
"time" "time"
@ -27,7 +28,7 @@ type dhcpClient struct {
ifaceInvalidate time.Time ifaceInvalidate time.Time
dnsInvalidate time.Time dnsInvalidate time.Time
ifaceAddr *net.IPNet ifaceAddr *netip.Prefix
done chan struct{} done chan struct{}
resolver *Resolver resolver *Resolver
err error err error
@ -71,7 +72,7 @@ func (d *dhcpClient) resolve(ctx context.Context) (*Resolver, 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: d.ifaceName, Interface: atomic.NewString(d.ifaceName),
}) })
} }
@ -127,12 +128,12 @@ func (d *dhcpClient) invalidate() (bool, error) {
return false, err return false, err
} }
addr, err := ifaceObj.PickIPv4Addr(nil) addr, err := ifaceObj.PickIPv4Addr(netip.Addr{})
if err != nil { if err != nil {
return false, err return false, err
} }
if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) { if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr == addr {
return false, nil return false, nil
} }

View File

@ -5,6 +5,10 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"net"
"strconv"
"sync" "sync"
"time" "time"
@ -19,10 +23,20 @@ var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
type quicClient struct { type quicClient struct {
addr string addr string
r *Resolver
session quic.Connection session quic.Connection
proxyAdapter string
sync.RWMutex // protects session and bytesPool sync.RWMutex // protects session and bytesPool
} }
func newDOQ(r *Resolver, addr, proxyAdapter string) *quicClient {
return &quicClient{
addr: addr,
r: r,
proxyAdapter: proxyAdapter,
}
}
func (dc *quicClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (dc *quicClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return dc.ExchangeContext(context.Background(), m) return dc.ExchangeContext(context.Background(), m)
} }
@ -124,10 +138,49 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
quicConfig := &quic.Config{ quicConfig := &quic.Config{
ConnectionIDLength: 12, ConnectionIDLength: 12,
HandshakeIdleTimeout: time.Second * 8, HandshakeIdleTimeout: time.Second * 8,
MaxIncomingStreams: 4,
MaxIdleTimeout: time.Second * 45,
} }
log.Debugln("opening session to %s", dc.addr) log.Debugln("opening session to %s", dc.addr)
session, err := quic.DialAddrContext(context.Background(), dc.addr, tlsConfig, quicConfig) var (
udp net.PacketConn
err error
)
host, port, err := net.SplitHostPort(dc.addr)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveIPv4WithResolver(host, dc.r)
if err != nil {
return nil, err
}
p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: ip.AsSlice(), Port: p}
if dc.proxyAdapter == "" {
udp, err = dialer.ListenPacket(context.Background(), "udp", "")
if err != nil {
return nil, err
}
} else {
conn, err := dialContextWithProxyAdapter(context.Background(), dc.proxyAdapter, "udp", ip, port)
if err != nil {
return nil, err
}
wrapConn, ok := conn.(*wrapPacketConn)
if !ok {
return nil, fmt.Errorf("quio create packet failed")
}
udp = wrapConn.PacketConn
}
session, err := quic.Dial(udp, &udpAddr, host, tlsConfig, quicConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open QUIC session: %w", err) return nil, fmt.Errorf("failed to open QUIC session: %w", err)
} }

View File

@ -1,7 +1,6 @@
package dns package dns
import ( import (
"net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
@ -23,54 +22,51 @@ func (h *ResolverEnhancer) MappingEnabled() bool {
return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping
} }
func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool { func (h *ResolverEnhancer) IsExistFakeIP(ip netip.Addr) bool {
if !h.FakeIPEnabled() { if !h.FakeIPEnabled() {
return false return false
} }
if pool := h.fakePool; pool != nil { if pool := h.fakePool; pool != nil {
return pool.Exist(ipToAddr(ip)) return pool.Exist(ip)
} }
return false return false
} }
func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { func (h *ResolverEnhancer) IsFakeIP(ip netip.Addr) bool {
if !h.FakeIPEnabled() {
return false
}
addr := ipToAddr(ip)
if pool := h.fakePool; pool != nil {
return pool.IPNet().Contains(addr) && addr != pool.Gateway() && addr != pool.Broadcast()
}
return false
}
func (h *ResolverEnhancer) IsFakeBroadcastIP(ip net.IP) bool {
if !h.FakeIPEnabled() { if !h.FakeIPEnabled() {
return false return false
} }
if pool := h.fakePool; pool != nil { if pool := h.fakePool; pool != nil {
return pool.Broadcast() == ipToAddr(ip) return pool.IPNet().Contains(ip) && ip != pool.Gateway() && ip != pool.Broadcast()
} }
return false return false
} }
func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { func (h *ResolverEnhancer) IsFakeBroadcastIP(ip netip.Addr) bool {
addr := ipToAddr(ip) if !h.FakeIPEnabled() {
return false
}
if pool := h.fakePool; pool != nil { if pool := h.fakePool; pool != nil {
if host, existed := pool.LookBack(addr); existed { return pool.Broadcast() == ip
}
return false
}
func (h *ResolverEnhancer) FindHostByIP(ip netip.Addr) (string, bool) {
if pool := h.fakePool; pool != nil {
if host, existed := pool.LookBack(ip); existed {
return host, true return host, true
} }
} }
if mapping := h.mapping; mapping != nil { if mapping := h.mapping; mapping != nil {
if host, existed := h.mapping.Get(addr); existed { if host, existed := h.mapping.Get(ip); existed {
return host, true return host, true
} }
} }
@ -78,12 +74,19 @@ func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) {
return "", false return "", false
} }
func (h *ResolverEnhancer) InsertHostByIP(ip net.IP, host string) { func (h *ResolverEnhancer) InsertHostByIP(ip netip.Addr, host string) {
if mapping := h.mapping; mapping != nil { if mapping := h.mapping; mapping != nil {
h.mapping.Set(ipToAddr(ip), host) h.mapping.Set(ip, host)
} }
} }
func (h *ResolverEnhancer) FlushFakeIP() error {
if h.fakePool != nil {
return h.fakePool.FlushFakeIP()
}
return nil
}
func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) {
if h.mapping != nil && o.mapping != nil { if h.mapping != nil && o.mapping != nil {
o.mapping.CloneTo(h.mapping) o.mapping.CloneTo(h.mapping)
@ -94,11 +97,10 @@ func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) {
} }
} }
func (h *ResolverEnhancer) FlushFakeIP() error { func (h *ResolverEnhancer) StoreFakePoolState() {
if h.fakePool != nil { if h.fakePool != nil {
return h.fakePool.FlushFakeIP() h.fakePool.StoreState()
} }
return nil
} }
func NewEnhancer(cfg Config) *ResolverEnhancer { func NewEnhancer(cfg Config) *ResolverEnhancer {

View File

@ -1,18 +1,19 @@
package dns package dns
import ( import (
"net/netip"
"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"
"github.com/Dreamacro/clash/component/mmdb" "github.com/Dreamacro/clash/component/mmdb"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"net"
"strings" "strings"
) )
type fallbackIPFilter interface { type fallbackIPFilter interface {
Match(net.IP) bool Match(netip.Addr) bool
} }
type geoipFilter struct { type geoipFilter struct {
@ -21,9 +22,9 @@ type geoipFilter struct {
var geoIPMatcher *router.GeoIPMatcher var geoIPMatcher *router.GeoIPMatcher
func (gf *geoipFilter) Match(ip net.IP) bool { func (gf *geoipFilter) Match(ip netip.Addr) bool {
if !C.GeodataMode { if !C.GeodataMode {
record, _ := mmdb.Instance().Country(ip) record, _ := mmdb.Instance().Country(ip.AsSlice())
return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate() return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
} }
@ -54,14 +55,14 @@ func (gf *geoipFilter) Match(ip net.IP) bool {
return false return false
} }
} }
return !geoIPMatcher.Match(ip) return !geoIPMatcher.Match(ip.AsSlice())
} }
type ipnetFilter struct { type ipnetFilter struct {
ipnet *net.IPNet ipnet *netip.Prefix
} }
func (inf *ipnetFilter) Match(ip net.IP) bool { func (inf *ipnetFilter) Match(ip netip.Addr) bool {
return inf.ipnet.Contains(ip) return inf.ipnet.Contains(ip)
} }
@ -76,7 +77,7 @@ type domainFilter struct {
func NewDomainFilter(domains []string) *domainFilter { func NewDomainFilter(domains []string) *domainFilter {
df := domainFilter{tree: trie.New[bool]()} df := domainFilter{tree: trie.New[bool]()}
for _, domain := range domains { for _, domain := range domains {
df.tree.Insert(domain, true) _ = df.tree.Insert(domain, true)
} }
return &df return &df
} }

View File

@ -1,12 +1,12 @@
package dns package dns
import ( import (
"net"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -87,21 +87,21 @@ func withMapping(mapping *cache.LruCache[netip.Addr, string]) middleware {
host := strings.TrimRight(q.Name, ".") host := strings.TrimRight(q.Name, ".")
for _, ans := range msg.Answer { for _, ans := range msg.Answer {
var ip net.IP var ip netip.Addr
var ttl uint32 var ttl uint32
switch a := ans.(type) { switch a := ans.(type) {
case *D.A: case *D.A:
ip = a.A ip = nnip.IpToAddr(a.A)
ttl = a.Hdr.Ttl ttl = a.Hdr.Ttl
case *D.AAAA: case *D.AAAA:
ip = a.AAAA ip = nnip.IpToAddr(a.AAAA)
ttl = a.Hdr.Ttl ttl = a.Hdr.Ttl
default: default:
continue continue
} }
mapping.SetWithExpire(ipToAddr(ip), host, time.Now().Add(time.Second*time.Duration(ttl))) mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*time.Duration(ttl)))
} }
return msg, nil return msg, nil

View File

@ -4,8 +4,8 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.uber.org/atomic"
"math/rand" "math/rand"
"net"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
@ -45,9 +45,8 @@ type Resolver struct {
proxyServer []dnsClient proxyServer []dnsClient
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA func (r *Resolver) ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err error) {
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { ch := make(chan []netip.Addr, 1)
ch := make(chan net.IP, 1)
go func() { go func() {
defer close(ch) defer close(ch)
ip, err := r.resolveIP(host, D.TypeAAAA) ip, err := r.resolveIP(host, D.TypeAAAA)
@ -57,7 +56,7 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
ch <- ip ch <- ip
}() }()
ip, err = r.resolveIP(host, D.TypeA) ips, err = r.resolveIP(host, D.TypeA)
if err == nil { if err == nil {
return return
} }
@ -70,17 +69,65 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
return ip, nil return ip, nil
} }
// ResolveIPv4 request with TypeA func (r *Resolver) ResolveAllIP(host string) (ips []netip.Addr, err error) {
func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) { ch := make(chan []netip.Addr, 1)
go func() {
defer close(ch)
ip, err := r.resolveIP(host, D.TypeAAAA)
if err != nil {
return
}
ch <- ip
}()
ips, err = r.resolveIP(host, D.TypeA)
ipv6s, open := <-ch
if !open && err != nil {
return nil, resolver.ErrIPNotFound
}
ips = append(ips, ipv6s...)
return ips, nil
}
func (r *Resolver) ResolveAllIPv4(host string) (ips []netip.Addr, err error) {
return r.resolveIP(host, D.TypeA) return r.resolveIP(host, D.TypeA)
} }
// ResolveIPv6 request with TypeAAAA func (r *Resolver) ResolveAllIPv6(host string) (ips []netip.Addr, err error) {
func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) {
return r.resolveIP(host, D.TypeAAAA) return r.resolveIP(host, D.TypeAAAA)
} }
func (r *Resolver) shouldIPFallback(ip net.IP) bool { // ResolveIP request with TypeA and TypeAAAA, priority return TypeA
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
if ips, err := r.ResolveAllIPPrimaryIPv4(host); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
}
// ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) {
if ips, err := r.ResolveAllIPv4(host); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
}
// ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) {
if ips, err := r.ResolveAllIPv6(host); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
}
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
for _, filter := range r.fallbackIPFilters { for _, filter := range r.fallbackIPFilters {
if filter.Match(ip) { if filter.Match(ip) {
return true return true
@ -101,10 +148,10 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
} }
q := m.Question[0] q := m.Question[0]
cache, expireTime, hit := r.lruCache.GetWithExpire(q.String()) cacheM, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if hit { if hit {
now := time.Now() now := time.Now()
msg = cache.Copy() msg = cacheM.Copy()
if expireTime.Before(now) { if expireTime.Before(now) {
setMsgTTL(msg, uint32(1)) // Continue fetch setMsgTTL(msg, uint32(1)) // Continue fetch
go r.exchangeWithoutCache(ctx, m) go r.exchangeWithoutCache(ctx, m)
@ -153,10 +200,10 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
} }
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout) fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
for _, client := range clients { for _, client := range clients {
r := client r := client
fast.Go(func() (any, error) { fast.Go(func() (*D.Msg, error) {
m, err := r.ExchangeContext(ctx, m) m, err := r.ExchangeContext(ctx, m)
if err != nil { if err != nil {
return nil, err return nil, err
@ -176,7 +223,7 @@ func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.
return nil, err return nil, err
} }
msg = elm.(*D.Msg) msg = elm
return return
} }
@ -255,16 +302,16 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
return return
} }
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16) (ips []netip.Addr, err error) {
ip = net.ParseIP(host) ip, err := netip.ParseAddr(host)
if ip != nil { if err == nil {
isIPv4 := ip.To4() != nil isIPv4 := ip.Is4()
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return ip, nil return []netip.Addr{ip}, nil
} else if dnsType == D.TypeA && isIPv4 { } else if dnsType == D.TypeA && isIPv4 {
return ip, nil return []netip.Addr{ip}, nil
} else { } else {
return nil, resolver.ErrIPVersion return []netip.Addr{}, resolver.ErrIPVersion
} }
} }
@ -273,16 +320,15 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
msg, err := r.Exchange(query) msg, err := r.Exchange(query)
if err != nil { if err != nil {
return nil, err return []netip.Addr{}, err
} }
ips := msgToIP(msg) ips = msgToIP(msg)
ipLength := len(ips) ipLength := len(ips)
if ipLength == 0 { if ipLength == 0 {
return nil, resolver.ErrIPNotFound return []netip.Addr{}, resolver.ErrIPNotFound
} }
ip = ips[rand.Intn(ipLength)]
return return
} }
@ -311,14 +357,14 @@ func (r *Resolver) HasProxyServer() bool {
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
Interface string Interface *atomic.String
ProxyAdapter string ProxyAdapter string
} }
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool GeoIP bool
GeoIPCode string GeoIPCode string
IPCIDR []*net.IPNet IPCIDR []*netip.Prefix
Domain []string Domain []string
GeoSite []*router.DomainMatcher GeoSite []*router.DomainMatcher
} }
@ -359,7 +405,7 @@ func NewResolver(config Config) *Resolver {
if len(config.Policy) != 0 { if len(config.Policy) != 0 {
r.policy = trie.New[*Policy]() r.policy = trie.New[*Policy]()
for domain, nameserver := range config.Policy { for domain, nameserver := range config.Policy {
r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver))) _ = r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver)))
} }
} }

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -63,7 +64,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))
continue continue
case "quic": case "quic":
ret = append(ret, &quicClient{addr: s.Addr}) ret = append(ret, newDOQ(resolver, s.Addr, s.ProxyAdapter))
continue continue
} }
@ -100,37 +101,21 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
return msg return msg
} }
func msgToIP(msg *D.Msg) []net.IP { func msgToIP(msg *D.Msg) []netip.Addr {
ips := []net.IP{} ips := []netip.Addr{}
for _, answer := range msg.Answer { for _, answer := range msg.Answer {
switch ans := answer.(type) { switch ans := answer.(type) {
case *D.AAAA: case *D.AAAA:
ips = append(ips, ans.AAAA) ips = append(ips, nnip.IpToAddr(ans.AAAA))
case *D.A: case *D.A:
ips = append(ips, ans.A) ips = append(ips, nnip.IpToAddr(ans.A))
} }
} }
return ips return ips
} }
func ipToAddr(ip net.IP) netip.Addr {
if ip == nil {
return netip.Addr{}
}
l := len(ip)
if l == 4 {
return netip.AddrFrom4(*(*[4]byte)(ip))
} else if l == 16 {
return netip.AddrFrom16(*(*[16]byte)(ip))
} else {
return netip.Addr{}
}
}
type wrapPacketConn struct { type wrapPacketConn struct {
net.PacketConn net.PacketConn
rAddr net.Addr rAddr net.Addr
@ -149,7 +134,7 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
return wpc.rAddr return wpc.rAddr
} }
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP net.IP, port string, opts ...dialer.Option) (net.Conn, error) { func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
adapter, ok := tunnel.Proxies()[adapterName] adapter, ok := tunnel.Proxies()[adapterName]
if !ok { if !ok {
return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName) return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName)
@ -164,7 +149,7 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ
} }
addrType := C.AtypIPv4 addrType := C.AtypIPv4
if dstIP.To4() == nil { if dstIP.Is6() {
addrType = C.AtypIPv6 addrType = C.AtypIPv6
} }

15
go.mod
View File

@ -6,11 +6,11 @@ require (
github.com/Dreamacro/go-shadowsocks2 v0.1.8 github.com/Dreamacro/go-shadowsocks2 v0.1.8
github.com/dlclark/regexp2 v1.4.0 github.com/dlclark/regexp2 v1.4.0
github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.0 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.2.0+incompatible github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
github.com/lucas-clemente/quic-go v0.27.0 github.com/lucas-clemente/quic-go v0.27.0
github.com/miekg/dns v1.1.48 github.com/miekg/dns v1.1.48
github.com/oschwald/geoip2-golang v1.7.0 github.com/oschwald/geoip2-golang v1.7.0
@ -20,22 +20,23 @@ require (
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.9.0 go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.5.1 go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 golang.org/x/exp v0.0.0-20220428152302-39d4317da171
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220412071739-889880a91fd5 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
golang.org/x/time v0.0.0-20220411224347-583f2d630306 golang.org/x/time v0.0.0-20220411224347-583f2d630306
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150
) )
require ( require (
github.com/cheekybits/genny v1.0.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/kr/pretty v0.2.1 // indirect github.com/kr/pretty v0.2.1 // indirect

31
go.sum
View File

@ -31,14 +31,14 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@ -88,8 +88,8 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I= github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo=
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
@ -221,9 +221,11 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -255,8 +257,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -302,8 +304,9 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412071739-889880a91fd5 h1:NubxfvTRuNb4RVzWrIDAUzUvREH1HkCD4JjyQTSG9As= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412071739-889880a91fd5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -382,8 +385,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b h1:JW1pUBe6A3H+b0B9DwEOcfK+TLS/04A3A9cettPpfV0= gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150 h1:bspdBY1iCLtW6JXold8yhXHkAiE9UoWfmHShNkTc9JA=
gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,5 @@
//go:build !386 && !amd64 && !arm64 && !arm64be && !mipsle && !mips
package executor
const concurrentCount = 5

View File

@ -0,0 +1,5 @@
//go:build mips || mipsle
package executor
const concurrentCount = 1

View File

@ -0,0 +1,7 @@
//go:build 386 || amd64 || arm64 || arm64be
package executor
import "math"
const concurrentCount = math.MaxInt

View File

@ -2,11 +2,10 @@ package executor
import ( import (
"fmt" "fmt"
"net" "github.com/Dreamacro/clash/listener/inner"
"net/netip" "net/netip"
"os" "os"
"runtime" "runtime"
"strconv"
"sync" "sync"
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
@ -78,18 +77,24 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateProxies(cfg.Proxies, cfg.Providers) updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules, cfg.RuleProviders) updateRules(cfg.Rules, cfg.RuleProviders)
updateSniffer(cfg.Sniffer) updateSniffer(cfg.Sniffer)
updateDNS(cfg.DNS, cfg.Tun) updateHosts(cfg.Hosts)
initInnerTcp()
updateDNS(cfg.DNS)
loadProxyProvider(cfg.Providers)
updateProfile(cfg)
loadRuleProvider(cfg.RuleProviders)
updateGeneral(cfg.General, force) updateGeneral(cfg.General, force)
updateIPTables(cfg) updateIPTables(cfg)
updateTun(cfg.Tun, cfg.DNS) updateTun(cfg.Tun, cfg.DNS)
updateExperimental(cfg) updateExperimental(cfg)
updateHosts(cfg.Hosts)
loadProvider(cfg.RuleProviders, cfg.Providers)
updateProfile(cfg)
log.SetLevel(cfg.General.LogLevel) log.SetLevel(cfg.General.LogLevel)
} }
func initInnerTcp() {
inner.New(tunnel.TCPIn())
}
func GetGeneral() *config.General { func GetGeneral() *config.General {
ports := P.GetPorts() ports := P.GetPorts()
var authenticator []string var authenticator []string
@ -112,6 +117,7 @@ func GetGeneral() *config.General {
LogLevel: log.Level(), LogLevel: log.Level(),
IPv6: !resolver.DisableIPv6, IPv6: !resolver.DisableIPv6,
GeodataLoader: G.LoaderName(), GeodataLoader: G.LoaderName(),
Tun: P.GetTunConf(),
} }
return general return general
@ -119,7 +125,15 @@ func GetGeneral() *config.General {
func updateExperimental(c *config.Config) {} func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS, t *config.Tun) { func updateDNS(c *config.DNS) {
if !c.Enable {
resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil
resolver.DefaultLocalServer = nil
dns.ReCreateServer("", nil, nil)
return
}
cfg := dns.Config{ cfg := dns.Config{
Main: c.NameServer, Main: c.NameServer,
Fallback: c.Fallback, Fallback: c.Fallback,
@ -139,6 +153,8 @@ func updateDNS(c *config.DNS, t *config.Tun) {
ProxyServer: c.ProxyServerNameserver, ProxyServer: c.ProxyServerNameserver,
} }
resolver.DisableIPv6 = !cfg.IPv6
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
pr := dns.NewProxyServerHostResolver(r) pr := dns.NewProxyServerHostResolver(r)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)
@ -150,26 +166,13 @@ func updateDNS(c *config.DNS, t *config.Tun) {
resolver.DefaultResolver = r resolver.DefaultResolver = r
resolver.DefaultHostMapper = m resolver.DefaultHostMapper = m
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
if pr.HasProxyServer() { if pr.HasProxyServer() {
resolver.ProxyServerHostResolver = pr resolver.ProxyServerHostResolver = pr
} }
if t.Enable { dns.ReCreateServer(c.Listen, r, m)
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
}
if c.Enable {
dns.ReCreateServer(c.Listen, r, m)
} else {
if !t.Enable {
resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil
resolver.DefaultLocalServer = nil
resolver.ProxyServerHostResolver = nil
}
dns.ReCreateServer("", nil, nil)
}
} }
func updateHosts(tree *trie.DomainTrie[netip.Addr]) { func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
@ -180,49 +183,77 @@ func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.Pro
tunnel.UpdateProxies(proxies, providers) tunnel.UpdateProxies(proxies, providers)
} }
func updateRules(rules []C.Rule, ruleProviders map[string]*provider.RuleProvider) { func updateRules(rules []C.Rule, ruleProviders map[string]provider.RuleProvider) {
tunnel.UpdateRules(rules, ruleProviders) tunnel.UpdateRules(rules, ruleProviders)
} }
func loadProvider(ruleProviders map[string]*provider.RuleProvider, proxyProviders map[string]provider.ProxyProvider) { func loadProvider(pv provider.Provider) {
load := func(pv provider.Provider) { if pv.VehicleType() == provider.Compatible {
if pv.VehicleType() == provider.Compatible { log.Infoln("Start initial compatible provider %s", pv.Name())
log.Infoln("Start initial compatible provider %s", pv.Name()) } else {
} else { log.Infoln("Start initial provider %s", (pv).Name())
log.Infoln("Start initial provider %s", (pv).Name()) }
}
if err := (pv).Initial(); err != nil {
switch pv.Type() {
case provider.Proxy:
{
log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err)
}
case provider.Rule:
{
log.Warnln("initial rule provider %s error: %v", (pv).Name(), err)
}
if err := pv.Initial(); err != nil {
switch pv.Type() {
case provider.Proxy:
{
log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err)
} }
case provider.Rule:
{
log.Warnln("initial rule provider %s error: %v", (pv).Name(), err)
}
} }
} }
}
for _, proxyProvider := range proxyProviders {
load(proxyProvider) func loadRuleProvider(ruleProviders map[string]provider.RuleProvider) {
} wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, ruleProvider := range ruleProviders { for _, ruleProvider := range ruleProviders {
load(*ruleProvider) ruleProvider := ruleProvider
} wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
loadProvider(ruleProvider)
}()
}
wg.Wait()
}
func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) {
// limit concurrent size
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
loadProvider(proxyProvider)
}()
}
wg.Wait()
} }
func updateTun(tun *config.Tun, dns *config.DNS) { func updateTun(tun *config.Tun, dns *config.DNS) {
P.ReCreateTun(tun, dns, tunnel.TCPIn(), tunnel.UDPIn()) var tunAddressPrefix *netip.Prefix
if dns.FakeIPRange != nil {
tunAddressPrefix = dns.FakeIPRange.IPNet()
}
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
} }
func updateSniffer(sniffer *config.Sniffer) { func updateSniffer(sniffer *config.Sniffer) {
if sniffer.Enable { if sniffer.Enable {
dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipSNI) dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports)
if err != nil { if err != nil {
log.Warnln("initial sniffer failed, err:%v", err) log.Warnln("initial sniffer failed, err:%v", err)
} }
@ -243,10 +274,19 @@ func updateSniffer(sniffer *config.Sniffer) {
func updateGeneral(general *config.General, force bool) { func updateGeneral(general *config.General, force bool) {
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
resolver.DisableIPv6 = !general.IPv6 dialer.DisableIPv6 = !general.IPv6
if !dialer.DisableIPv6 {
log.Infoln("Use IPv6")
} else {
resolver.DisableIPv6 = true
}
if general.TCPConcurrent {
dialer.SetDial(general.TCPConcurrent)
log.Infoln("Use tcp concurrent")
}
adapter.UnifiedDelay.Store(general.UnifiedDelay) adapter.UnifiedDelay.Store(general.UnifiedDelay)
dialer.DefaultInterface.Store(general.Interface) dialer.DefaultInterface.Store(general.Interface)
if dialer.DefaultInterface.Load() != "" { if dialer.DefaultInterface.Load() != "" {
@ -364,13 +404,7 @@ func updateIPTables(cfg *config.Config) {
return return
} }
_, dnsPortStr, err := net.SplitHostPort(dnsCfg.Listen) dnsPort, err := netip.ParseAddrPort(dnsCfg.Listen)
if err != nil {
err = fmt.Errorf("DNS server must be correct")
return
}
dnsPort, err := strconv.ParseUint(dnsPortStr, 10, 16)
if err != nil { if err != nil {
err = fmt.Errorf("DNS server must be correct") err = fmt.Errorf("DNS server must be correct")
return return
@ -384,7 +418,7 @@ func updateIPTables(cfg *config.Config) {
dialer.DefaultRoutingMark.Store(2158) dialer.DefaultRoutingMark.Store(2158)
} }
err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), uint16(dnsPort)) err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), dnsPort.Port())
if err != nil { if err != nil {
return return
} }
@ -395,6 +429,7 @@ func updateIPTables(cfg *config.Config) {
func Shutdown() { func Shutdown() {
P.Cleanup() P.Cleanup()
tproxy.CleanupTProxyIPTables() tproxy.CleanupTProxyIPTables()
resolver.StoreFakePoolState()
log.Warnln("Clash shutting down") log.Warnln("Clash shutting down")
} }

Some files were not shown because too many files have changed in this diff Show More