Compare commits

..

1 Commits

Author SHA1 Message Date
be8eb7c17c feat: add port whitelist, empty is all port 2022-04-21 06:50:20 -07:00
149 changed files with 1575 additions and 3326 deletions

View File

@ -1,15 +1,8 @@
name: Prerelease name: Alpha
on: on: [push]
push:
branches:
- Alpha
- Beta
pull_request:
branches:
- Alpha
- Beta
jobs: jobs:
Build: Feature-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
@ -31,13 +24,9 @@ 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: |
- name: Test # go test ./...
if: ${{github.ref_name=='Beta'}}
run: |
go test ./...
- name: Build - name: Build
if: success() if: success()
env: env:
@ -49,22 +38,34 @@ 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: Prerelease-${{ github.ref_name }} tag: alpha
deleteOnlyFromDrafts: false deleteOnlyFromDrafts: false
- name: Tag Repo - name: Tag Repo
uses: richardsimko/update-tag@v1 uses: richardsimko/update-tag@v1
with: with:
tag_name: Prerelease-${{ github.ref_name }} tag_name: alpha
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: ${{ success() }} if: ${{ env.GIT_BRANCH != 'Meta' && success() }}
with: with:
tag: ${{ github.ref_name }} tag: ${{ github.ref }}
tag_name: Prerelease-${{ github.ref_name }} tag_name: alpha
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}}

View File

@ -1,61 +0,0 @@
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,44 +0,0 @@
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,26 +1,18 @@
FROM golang:alpine as builder FROM golang:alpine as builder
RUN apk add --no-cache make git && \ RUN apk add --no-cache make git && \
mkdir /clash-config && \ wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
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
WORKDIR /clash-src WORKDIR /clash-src
RUN go mod download &&\ COPY --from=tonistiigi/xx:golang / /
make docker &&\ COPY . /clash-src
mv ./bin/Clash.Meta-docker /clash RUN go mod download && \
make docker && \
mv ./bin/clash-docker /clash
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta" LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash"
RUN apk add --no-cache ca-certificates tzdata RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /Country.mmdb /root/.config/clash/
VOLUME ["/root/.config/clash/"] COPY --from=builder /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,16 +1,7 @@
NAME=Clash.Meta NAME=Clash.Meta
BINDIR=bin BINDIR=bin
BRANCH=$(shell git branch --show-current) BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
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)" \
@ -52,7 +43,7 @@ all:linux-amd64 linux-arm64\
windows-amd64 windows-arm64\ windows-amd64 windows-arm64\
docker: docker:
GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(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)-$@
@ -159,4 +150,4 @@ lint:
golangci-lint run ./... golangci-lint run ./...
clean: clean:
rm $(BINDIR)/* rm $(BINDIR)/*

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -65,9 +64,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 {
queueM := p.history.Copy() queue := p.history.Copy()
histories := []C.DelayHistory{} histories := []C.DelayHistory{}
for _, item := range queueM { for _, item := range queue {
histories = append(histories, item) histories = append(histories, item)
} }
return histories return histories
@ -96,7 +95,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()
@ -130,9 +129,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
if err != nil { if err != nil {
return return
} }
defer func() { defer instance.Close()
_ = instance.Close()
}()
req, err := http.NewRequest(http.MethodHead, url, nil) req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil { if err != nil {
@ -141,7 +138,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{
DialContext: func(context.Context, string, string) (net.Conn, error) { Dial: func(string, string) (net.Conn, error) {
return instance, nil return instance, nil
}, },
// from http.DefaultTransport // from http.DefaultTransport
@ -170,7 +167,8 @@ 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
} }
@ -201,7 +199,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: netip.Addr{}, DstIP: nil,
DstPort: port, DstPort: port,
} }
return return

View File

@ -2,7 +2,6 @@ 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"
@ -34,15 +33,14 @@ 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 h, port, err := net.SplitHostPort(dst); err == nil { if ip, port, err := parseAddr(dst); err == nil {
metadata.DstPort = port metadata.DstPort = port
if host == "" { if host == "" {
if ip, err := netip.ParseAddr(h); err == nil { metadata.DstIP = ip
metadata.DstIP = ip if ip.To4() == nil {
metadata.AddrType = C.AtypIPv6
} else {
metadata.AddrType = C.AtypIPv4 metadata.AddrType = C.AtypIPv4
if ip.Is6() {
metadata.AddrType = C.AtypIPv6
}
} }
} }
} }

View File

@ -3,11 +3,9 @@ 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"
) )
@ -23,10 +21,12 @@ 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:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len])) ip := 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:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv6len])) ip := 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: netip.Addr{}, DstIP: nil,
DstPort: port, DstPort: port,
} }
ip, err := netip.ParseAddr(host) ip := net.ParseIP(host)
if err == nil { if ip != nil {
switch { switch {
case ip.Is6(): case ip.To4() == nil:
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) (netip.Addr, string, error) { func parseAddr(addr string) (net.IP, string, error) {
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return netip.Addr{}, "", err return nil, "", err
} }
ip, err := netip.ParseAddr(host) ip := net.ParseIP(host)
return ip, port, err return ip, port, nil
} }

View File

@ -39,16 +39,6 @@ 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,3 +56,13 @@ func NewCompatible() *Direct {
}, },
} }
} }
func NewPass() *Direct {
return &Direct{
Base: &Base{
name: "PASS",
tp: C.Pass,
udp: true,
},
}
}

View File

@ -34,16 +34,6 @@ 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,12 +92,6 @@ 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
@ -109,14 +103,13 @@ 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 none or a supported stream cipher in ssr", cipher) return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher)
} }
ivSize = ciph.IVSize() ivSize = ciph.IVSize()
key = ciph.Key key = ciph.Key

View File

@ -53,10 +53,6 @@ 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
@ -108,17 +104,6 @@ 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,6 +13,8 @@ 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 {
@ -23,7 +25,7 @@ type Trojan struct {
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *gun.TransportWrap transport *http2.Transport
} }
type TrojanOption struct { type TrojanOption struct {
@ -88,10 +90,6 @@ 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
} }
@ -168,17 +166,6 @@ 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:
lenM := uint8(len(metadata.Host)) len := uint8(len(metadata.Host))
host := []byte(metadata.Host) host := []byte(metadata.Host)
buf = [][]byte{{aType, lenM}, host, port} buf = [][]byte{{aType, len}, host, port}
case socks5.AtypIPv4: case socks5.AtypIPv4:
host := metadata.DstIP.AsSlice() host := metadata.DstIP.To4()
buf = [][]byte{{aType}, host, port} buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6: case socks5.AtypIPv6:
host := metadata.DstIP.AsSlice() host := metadata.DstIP.To16()
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,6 +18,8 @@ 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 (
@ -33,7 +35,7 @@ type Vless struct {
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *gun.TransportWrap transport *http2.Transport
} }
type VlessOption struct { type VlessOption struct {
@ -247,36 +249,26 @@ 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 = vless.AtypIPv4 addrType = byte(vless.AtypIPv4)
addr = make([]byte, net.IPv4len) addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.AsSlice()) copy(addr[:], metadata.DstIP.To4())
case C.AtypIPv6: case C.AtypIPv6:
addrType = vless.AtypIPv6 addrType = byte(vless.AtypIPv6)
addr = make([]byte, net.IPv6len) addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice()) copy(addr[:], metadata.DstIP.To16())
case C.AtypDomainName: case C.AtypDomainName:
addrType = vless.AtypDomainName addrType = byte(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:], metadata.Host) copy(addr[1:], []byte(metadata.Host))
} }
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16) port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)

View File

@ -15,6 +15,8 @@ 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 {
@ -25,7 +27,7 @@ type Vmess struct {
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *gun.TransportWrap transport *http2.Transport
} }
type VmessOption struct { type VmessOption struct {
@ -258,19 +260,9 @@ 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{
@ -350,11 +342,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.AsSlice()) copy(addr[:], metadata.DstIP.To4())
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.AsSlice()) copy(addr[:], metadata.DstIP.To16())
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

@ -0,0 +1,55 @@
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,15 +3,25 @@ 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 {
*GroupBase *outbound.Base
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 {
@ -25,7 +35,8 @@ 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.onDialSuccess() f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} else { } else {
f.onDialFailed() f.onDialFailed()
} }
@ -39,7 +50,8 @@ 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.onDialSuccess() f.failedTimes.Store(-1)
f.failedTime.Store(-1)
} else { } else {
f.onDialFailed() f.onDialFailed()
} }
@ -47,6 +59,32 @@ 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 {
@ -60,7 +98,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.GetProxies(false) { for _, proxy := range f.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -76,8 +114,16 @@ 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.GetProxies(touch) proxies := f.proxies(touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if proxy.Alive() { if proxy.Alive() {
return proxy return proxy
@ -89,16 +135,17 @@ 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{
GroupBase: NewGroupBase(GroupBaseOption{ Base: outbound.NewBase(outbound.BaseOption{
outbound.BaseOption{ Name: option.Name,
Name: option.Name, Type: C.Fallback,
Type: C.Fallback, Interface: option.Interface,
Interface: option.Interface, RoutingMark: option.RoutingMark,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}), }),
disableUDP: option.DisableUDP, single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
disableUDP: option.DisableUDP,
filter: option.Filter,
failedTimes: atomic.NewInt32(-1),
failedTime: atomic.NewInt64(-1),
} }
} }

View File

@ -1,144 +0,0 @@
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,12 +5,11 @@ 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"
@ -21,8 +20,11 @@ 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 {
*GroupBase *outbound.Base
disableUDP bool disableUDP bool
single *singledo.Single
filter string
providers []provider.ProxyProvider
strategyFn strategyFn strategyFn strategyFn
} }
@ -38,10 +40,6 @@ 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 {
@ -53,7 +51,7 @@ func getKey(metadata *C.Metadata) string {
} }
} }
if !metadata.DstIP.IsValid() { if metadata.DstIP == nil {
return "" return ""
} }
@ -77,9 +75,6 @@ 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()
} }
}() }()
@ -139,70 +134,24 @@ func strategyConsistentHashing() strategyFn {
} }
} }
func strategyStickySessions() strategyFn {
timeout := int64(600)
type Session struct {
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
}
}
// Unwrap implements C.ProxyAdapter // Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
proxies := lb.GetProxies(true) proxies := lb.proxies(true)
return lb.strategyFn(proxies, metadata) return lb.strategyFn(proxies, metadata)
} }
func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
elm, _, _ := lb.single.Do(func() (any, error) {
return getProvidersProxies(lb.providers, touch, lb.filter), nil
})
return elm.([]C.Proxy)
}
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string all := []string{}
for _, proxy := range lb.GetProxies(false) { for _, proxy := range lb.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -218,23 +167,20 @@ 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{
GroupBase: NewGroupBase(GroupBaseOption{ Base: outbound.NewBase(outbound.BaseOption{
outbound.BaseOption{ Name: option.Name,
Name: option.Name, Type: C.LoadBalance,
Type: C.LoadBalance, Interface: option.Interface,
Interface: option.Interface, RoutingMark: option.RoutingMark,
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,18 +6,27 @@ 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 {
*GroupBase *outbound.Base
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) {
proxies, chainProxies := r.proxies(metadata, true) var proxies []C.Proxy
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:
@ -55,86 +64,13 @@ 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)
} }
conn := outbound.NewConn(c, last) return outbound.NewConn(c, r), nil
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.GetProxies(false) { for _, proxy := range r.rawProxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -143,49 +79,38 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
}) })
} }
func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) { func (r *Relay) rawProxies(touch bool) []C.Proxy {
rawProxies := r.GetProxies(touch) elm, _, _ := r.single.Do(func() (any, error) {
return getProvidersProxies(r.providers, touch, r.filter), nil
})
var proxies []C.Proxy return elm.([]C.Proxy)
var chainProxies []C.Proxy }
var targetProxies []C.Proxy
for n, proxy := range rawProxies { func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
proxies = append(proxies, proxy) proxies := r.rawProxies(touch)
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)
} }
} }
for _, proxy := range proxies { return 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{
GroupBase: NewGroupBase(GroupBaseOption{ Base: outbound.NewBase(outbound.BaseOption{
outbound.BaseOption{ Name: option.Name,
Name: option.Name, Type: C.Relay,
Type: C.Relay, Interface: option.Interface,
Interface: option.Interface, RoutingMark: option.RoutingMark,
RoutingMark: option.RoutingMark,
},
"",
providers,
}), }),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
filter: option.Filter,
} }
} }

View File

@ -6,15 +6,19 @@ 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 {
*GroupBase *outbound.Base
disableUDP bool disableUDP bool
single *singledo.Single
selected string selected string
filter string
providers []provider.ProxyProvider
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -47,7 +51,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 s.GetProxies(false) { for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
@ -63,9 +67,10 @@ func (s *Selector) Now() string {
} }
func (s *Selector) Set(name string) error { func (s *Selector) Set(name string) error {
for _, proxy := range s.GetProxies(false) { for _, proxy := range getProvidersProxies(s.providers, false, s.filter) {
if proxy.Name() == name { if proxy.Name() == name {
s.selected = name s.selected = name
s.single.Reset()
return nil return nil
} }
} }
@ -79,29 +84,32 @@ func (s *Selector) Unwrap(*C.Metadata) C.Proxy {
} }
func (s *Selector) selectedProxy(touch bool) C.Proxy { func (s *Selector) selectedProxy(touch bool) C.Proxy {
proxies := s.GetProxies(touch) elm, _, _ := s.single.Do(func() (any, error) {
for _, proxy := range proxies { proxies := getProvidersProxies(s.providers, touch, s.filter)
if proxy.Name() == s.selected { for _, proxy := range proxies {
return proxy if proxy.Name() == s.selected {
return proxy, nil
}
} }
}
return proxies[0] return proxies[0], nil
})
return elm.(C.Proxy)
} }
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
return &Selector{ return &Selector{
GroupBase: NewGroupBase(GroupBaseOption{ Base: outbound.NewBase(outbound.BaseOption{
outbound.BaseOption{ Name: option.Name,
Name: option.Name, Type: C.Selector,
Type: C.Selector, Interface: option.Interface,
Interface: option.Interface, RoutingMark: option.RoutingMark,
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,6 +3,8 @@ 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"
@ -21,11 +23,16 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
} }
type URLTest struct { type URLTest struct {
*GroupBase *outbound.Base
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
fastNode C.Proxy fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy] filter string
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 {
@ -50,11 +57,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.onDialSuccess() u.failedTimes.Store(-1)
u.failedTime.Store(-1)
} else { } else {
u.onDialFailed() u.onDialFailed()
} }
return pc, err return pc, err
} }
@ -63,9 +70,17 @@ 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() (C.Proxy, error) { elm, _, _ := u.fastSingle.Do(func() (any, error) {
proxies := u.GetProxies(touch) proxies := u.proxies(touch)
fast := proxies[0] fast := proxies[0]
min := fast.LastDelay() min := fast.LastDelay()
fastNotExist := true fastNotExist := true
@ -94,7 +109,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
return u.fastNode, nil return u.fastNode, nil
}) })
return elm return elm.(C.Proxy)
} }
// SupportUDP implements C.ProxyAdapter // SupportUDP implements C.ProxyAdapter
@ -109,7 +124,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.GetProxies(false) { for _, proxy := range u.proxies(false) {
all = append(all, proxy.Name()) all = append(all, proxy.Name())
} }
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
@ -119,6 +134,32 @@ 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{}
@ -134,19 +175,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{
GroupBase: NewGroupBase(GroupBaseOption{ Base: outbound.NewBase(outbound.BaseOption{
outbound.BaseOption{ Name: option.Name,
Name: option.Name, Type: C.URLTest,
Type: C.URLTest, Interface: option.Interface,
Interface: option.Interface, RoutingMark: option.RoutingMark,
RoutingMark: option.RoutingMark,
},
option.Filter,
providers,
}), }),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), single: singledo.NewSingle(defaultGetProxiesDuration),
disableUDP: option.DisableUDP, fastSingle: singledo.NewSingle(time.Second * 10),
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,7 +3,6 @@ package outboundgroup
import ( import (
"fmt" "fmt"
"net" "net"
"net/netip"
"time" "time"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -16,21 +15,20 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
return return
} }
ip, err := netip.ParseAddr(host) ip := net.ParseIP(host)
if err != nil { if ip == nil {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypDomainName, AddrType: C.AtypDomainName,
Host: host, Host: host,
DstIP: netip.Addr{}, DstIP: nil,
DstPort: port, DstPort: port,
} }
err = nil
return return
} else if ip.Is4() { } else if ip4 := ip.To4(); ip4 != nil {
addr = &C.Metadata{ addr = &C.Metadata{
AddrType: C.AtypIPv4, AddrType: C.AtypIPv4,
Host: "", Host: "",
DstIP: ip, DstIP: ip4,
DstPort: port, DstPort: port,
} }
return return
@ -47,7 +45,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,14 +43,6 @@ 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()
@ -92,6 +84,11 @@ 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[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {
p := proxy p := proxy
b.Go(p.Name(), func() (bool, error) { b.Go(p.Name(), func() (any, 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 false, nil return nil, nil
}) })
} }
b.Wait() b.Wait()

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"math"
"runtime" "runtime"
"time" "time"
@ -33,11 +32,6 @@ 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) {
@ -100,7 +94,6 @@ 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) {
@ -123,11 +116,6 @@ 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) {
@ -182,11 +170,6 @@ 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,6 +2,7 @@ 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"
@ -73,7 +74,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
transport := &http.Transport{ transport := &http.Transport{
// from http.DefaultTransport // from http.DefaultTransport
MaxIdleConns: 100, MaxIdleConns: 100,
IdleConnTimeout: 30 * time.Second, IdleConnTimeout: 90 * 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) {
@ -85,6 +86,10 @@ 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
} }

View File

@ -1,28 +0,0 @@
#!/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[T any] func(b *Batch[T]) type Option = func(b *Batch)
type Result[T any] struct { type Result struct {
Value T Value any
Err error Err error
} }
@ -17,8 +17,8 @@ type Error struct {
Err error Err error
} }
func WithConcurrencyNum[T any](n int) Option[T] { func WithConcurrencyNum(n int) Option {
return func(b *Batch[T]) { return func(b *Batch) {
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[T any](n int) Option[T] {
} }
// 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[T any] struct { type Batch struct {
result map[string]Result[T] result map[string]Result
queue chan struct{} queue chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
mux sync.Mutex mux sync.Mutex
@ -38,7 +38,7 @@ type Batch[T any] struct {
cancel func() cancel func()
} }
func (b *Batch[T]) Go(key string, fn func() (T, error)) { func (b *Batch) Go(key string, fn func() (any, 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[T]) Go(key string, fn func() (T, error)) {
}) })
} }
ret := Result[T]{value, err} ret := Result{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[T]) Wait() *Error { func (b *Batch) 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[T]) Wait() *Error {
return b.err return b.err
} }
func (b *Batch[T]) WaitAndGetResult() (map[string]Result[T], *Error) { func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) {
err := b.Wait() err := b.Wait()
return b.Result(), err return b.Result(), err
} }
func (b *Batch[T]) Result() map[string]Result[T] { func (b *Batch) Result() map[string]Result {
b.mux.Lock() b.mux.Lock()
defer b.mux.Unlock() defer b.mux.Unlock()
copyM := map[string]Result[T]{} copy := map[string]Result{}
for k, v := range b.result { for k, v := range b.result {
copyM[k] = v copy[k] = v
} }
return copyM return copy
} }
func New[T any](ctx context.Context, opts ...Option[T]) (*Batch[T], context.Context) { func New(ctx context.Context, opts ...Option) (*Batch, context.Context) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
b := &Batch[T]{ b := &Batch{
result: map[string]Result[T]{}, result: map[string]Result{},
} }
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[string](context.Background()) b, _ := New(context.Background())
now := time.Now() now := time.Now()
b.Go("foo", func() (string, error) { b.Go("foo", func() (any, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return "foo", nil return "foo", nil
}) })
b.Go("bar", func() (string, error) { b.Go("bar", func() (any, 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) assert.Equal(t, k, v.Value.(string))
} }
} }
func TestBatchWithConcurrencyNum(t *testing.T) { func TestBatchWithConcurrencyNum(t *testing.T) {
b, _ := New[string]( b, _ := New(
context.Background(), context.Background(),
WithConcurrencyNum[string](3), WithConcurrencyNum(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() (string, error) { b.Go(strconv.Itoa(idx), func() (any, 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) assert.Equal(t, k, v.Value.(string))
} }
} }
func TestBatchContext(t *testing.T) { func TestBatchContext(t *testing.T) {
b, ctx := New[string](context.Background()) b, ctx := New(context.Background())
b.Go("error", func() (string, error) { b.Go("error", func() (any, error) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
return "", errors.New("test error") return nil, errors.New("test error")
}) })
b.Go("ctx", func() (string, error) { b.Go("ctx", func() (any, error) {
<-ctx.Done() <-ctx.Done()
return "", ctx.Err() return nil, ctx.Err()
}) })
result, err := b.WaitAndGetResult() result, err := b.WaitAndGetResult()

View File

@ -3,20 +3,19 @@ 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[K comparable, V any] func(key K, value V) type EvictCallback = func(key any, value any)
// WithEvict set the evict callback // WithEvict set the evict callback
func WithEvict[K comparable, V any](cb EvictCallback[K, V]) Option[K, V] { func WithEvict[K comparable, V any](cb EvictCallback) Option[K, V] {
return func(l *LruCache[K, V]) { return func(l *LruCache[K, V]) {
l.onEvict = cb l.onEvict = cb
} }
@ -58,18 +57,18 @@ type LruCache[K comparable, V any] struct {
maxAge int64 maxAge int64
maxSize int maxSize int
mu sync.Mutex mu sync.Mutex
cache map[K]*list.Element[*entry[K, V]] cache map[any]*list.Element
lru *list.List[*entry[K, V]] // Front is least-recent lru *list.List // Front is least-recent
updateAgeOnGet bool updateAgeOnGet bool
staleReturn bool staleReturn bool
onEvict EvictCallback[K, V] onEvict EvictCallback
} }
// 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[*entry[K, V]](), lru: list.New(),
cache: make(map[K]*list.Element[*entry[K, V]]), cache: make(map[any]*list.Element),
} }
for _, option := range options { for _, option := range options {
@ -130,7 +129,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 e := le.Value.(*entry[K, V])
e.value = value e.value = value
e.expires = expires.Unix() e.expires = expires.Unix()
} else { } else {
@ -155,11 +154,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[*entry[K, V]]() n.lru = list.New()
n.cache = make(map[K]*list.Element[*entry[K, V]]) n.cache = make(map[any]*list.Element)
for e := c.lru.Front(); e != nil; e = e.Next() { for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value elm := e.Value.(*entry[K, V])
n.cache[elm.key] = n.lru.PushBack(elm) n.cache[elm.key] = n.lru.PushBack(elm)
} }
} }
@ -173,7 +172,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
return nil return nil
} }
if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() { if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry[K, V]).expires <= time.Now().Unix() {
c.deleteElement(le) c.deleteElement(le)
c.maybeDeleteOldest() c.maybeDeleteOldest()
@ -181,7 +180,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
} }
c.lru.MoveToBack(le) c.lru.MoveToBack(le)
el := le.Value el := le.Value.(*entry[K, V])
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
} }
@ -202,15 +201,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.expires <= now; le = c.lru.Front() { for le := c.lru.Front(); le != nil && le.Value.(*entry[K, V]).expires <= now; le = c.lru.Front() {
c.deleteElement(le) c.deleteElement(le)
} }
} }
} }
func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) { func (c *LruCache[K, V]) deleteElement(le *list.Element) {
c.lru.Remove(le) c.lru.Remove(le)
e := le.Value e := le.Value.(*entry[K, V])
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)
@ -220,7 +219,7 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
func (c *LruCache[K, V]) Clear() error { func (c *LruCache[K, V]) Clear() error {
c.mu.Lock() c.mu.Lock()
c.cache = make(map[K]*list.Element[*entry[K, V]]) c.cache = make(map[any]*list.Element)
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.expires = now c.lru.Back().Value.(*entry[string, string]).expires = now
// Reset // Reset
c.Set("foo", "bar") c.Set("foo", "bar")
e := c.lru.Back().Value e := c.lru.Back().Value.(*entry[string, string])
assert.True(t, e.expires >= now) assert.True(t, e.expires >= now)
c.lru.Back().Value.expires = now c.lru.Back().Value.(*entry[string, string]).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 e := c.lru.Back().Value.(*entry[string, string])
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.expires = now le.Value.(*entry[string, string]).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.expires = expires c.lru.Back().Value.(*entry[string, string]).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.expires > expires) assert.True(t, c.lru.Back().Value.(*entry[string, string]).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 int, value int) { evict := func(key any, value any) {
temp = key + value temp = key.(int) + value.(int)
} }
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

@ -1,235 +0,0 @@
// 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)
}
}

View File

@ -1,30 +0,0 @@
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
}

View File

@ -1,53 +0,0 @@
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[T any] <-chan T type Iterable <-chan any

View File

@ -5,14 +5,14 @@ import (
"sync" "sync"
) )
type Observable[T any] struct { type Observable struct {
iterable Iterable[T] iterable Iterable
listener map[Subscription[T]]*Subscriber[T] listener map[Subscription]*Subscriber
mux sync.Mutex mux sync.Mutex
done bool done bool
} }
func (o *Observable[T]) process() { func (o *Observable) 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[T]) process() {
o.close() o.close()
} }
func (o *Observable[T]) close() { func (o *Observable) close() {
o.mux.Lock() o.mux.Lock()
defer o.mux.Unlock() defer o.mux.Unlock()
@ -33,18 +33,18 @@ func (o *Observable[T]) close() {
} }
} }
func (o *Observable[T]) Subscribe() (Subscription[T], error) { func (o *Observable) Subscribe() (Subscription, 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[T]() subscriber := newSubscriber()
o.listener[subscriber.Out()] = subscriber o.listener[subscriber.Out()] = subscriber
return subscriber.Out(), nil return subscriber.Out(), nil
} }
func (o *Observable[T]) UnSubscribe(sub Subscription[T]) { func (o *Observable) UnSubscribe(sub Subscription) {
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[T]) UnSubscribe(sub Subscription[T]) {
subscriber.Close() subscriber.Close()
} }
func NewObservable[T any](iter Iterable[T]) *Observable[T] { func NewObservable(any Iterable) *Observable {
observable := &Observable[T]{ observable := &Observable{
iterable: iter, iterable: any,
listener: map[Subscription[T]]*Subscriber[T]{}, listener: map[Subscription]*Subscriber{},
} }
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[T any](item []T) chan T { func iterator(item []any) chan any {
ch := make(chan T) ch := make(chan any)
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[T any](item []T) chan T {
} }
func TestObservable(t *testing.T) { func TestObservable(t *testing.T) {
iter := iterator[int]([]int{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable[int](iter) src := NewObservable(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[int]([]int{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable[int](iter) src := NewObservable(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 int) { waitCh := func(ch <-chan any) {
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[int]([]int{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable[int](iter) src := NewObservable(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[int]([]int{1}) iter := iterator([]any{1})
src := NewObservable[int](iter) src := NewObservable(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[int](make(chan int)) sub := Subscription(make(chan any))
iter := iterator[int]([]int{1}) iter := iterator([]any{1})
src := NewObservable[int](iter) src := NewObservable(iter)
src.UnSubscribe(sub) src.UnSubscribe(sub)
} }
func TestObservable_SubscribeGoroutineLeak(t *testing.T) { func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
iter := iterator[int]([]int{1, 2, 3, 4, 5}) iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable[int](iter) src := NewObservable(iter)
max := 100 max := 100
var list []Subscription[int] var list []Subscription
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 int) { waitCh := func(ch <-chan any) {
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 int) ch := make(chan any)
o := NewObservable[int](ch) o := NewObservable(ch)
num := 1000 num := 1000
subs := []Subscription[int]{} subs := []Subscription{}
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[int]) { go func(s Subscription) {
for range s { for range s {
} }
wg.Done() wg.Done()

View File

@ -4,30 +4,30 @@ import (
"sync" "sync"
) )
type Subscription[T any] <-chan T type Subscription <-chan any
type Subscriber[T any] struct { type Subscriber struct {
buffer chan T buffer chan any
once sync.Once once sync.Once
} }
func (s *Subscriber[T]) Emit(item T) { func (s *Subscriber) Emit(item any) {
s.buffer <- item s.buffer <- item
} }
func (s *Subscriber[T]) Out() Subscription[T] { func (s *Subscriber) Out() Subscription {
return s.buffer return s.buffer
} }
func (s *Subscriber[T]) Close() { func (s *Subscriber) Close() {
s.once.Do(func() { s.once.Do(func() {
close(s.buffer) close(s.buffer)
}) })
} }
func newSubscriber[T any]() *Subscriber[T] { func newSubscriber() *Subscriber {
sub := &Subscriber[T]{ sub := &Subscriber{
buffer: make(chan T, 200), buffer: make(chan any, 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[T any] struct { type Picker struct {
ctx context.Context ctx context.Context
cancel func() cancel func()
@ -17,12 +17,12 @@ type Picker[T any] struct {
once sync.Once once sync.Once
errOnce sync.Once errOnce sync.Once
result T result any
err error err error
} }
func newPicker[T any](ctx context.Context, cancel func()) *Picker[T] { func newPicker(ctx context.Context, cancel func()) *Picker {
return &Picker[T]{ return &Picker{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
} }
@ -30,20 +30,20 @@ func newPicker[T any](ctx context.Context, cancel func()) *Picker[T] {
// 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[T any](ctx context.Context) (*Picker[T], context.Context) { func WithContext(ctx context.Context) (*Picker, context.Context) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
return newPicker[T](ctx, cancel), ctx return newPicker(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[T any](ctx context.Context, timeout time.Duration) (*Picker[T], context.Context) { func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) {
ctx, cancel := context.WithTimeout(ctx, timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
return newPicker[T](ctx, cancel), ctx return newPicker(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[T]) Wait() T { func (p *Picker) Wait() any {
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[T]) Wait() T {
} }
// Error return the first error (if all success return nil) // Error return the first error (if all success return nil)
func (p *Picker[T]) Error() error { func (p *Picker) 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[T]) Go(f func() (T, error)) { func (p *Picker) Go(f func() (any, error)) {
p.wg.Add(1) p.wg.Add(1)
go func() { go func() {

View File

@ -8,38 +8,33 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, error) { func sleepAndSend(ctx context.Context, delay int, input any) func() (any, error) {
return func() (T, error) { return func() (any, 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 getZero[T](), ctx.Err() return nil, ctx.Err()
} }
} }
} }
func TestPicker_Basic(t *testing.T) { func TestPicker_Basic(t *testing.T) {
picker, ctx := WithContext[int](context.Background()) picker, ctx := WithContext(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, 1) assert.Equal(t, number.(int), 1)
} }
func TestPicker_Timeout(t *testing.T) { func TestPicker_Timeout(t *testing.T) {
picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5) picker, ctx := WithTimeout(context.Background(), time.Millisecond*5)
picker.Go(sleepAndSend(ctx, 20, 1)) picker.Go(sleepAndSend(ctx, 20, 1))
number := picker.Wait() number := picker.Wait()
assert.Equal(t, number, getZero[int]()) assert.Nil(t, number)
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[T any] struct { type call struct {
wg sync.WaitGroup wg sync.WaitGroup
val T val any
err error err error
} }
type Single[T any] struct { type Single struct {
mux sync.Mutex mux sync.Mutex
last time.Time last time.Time
wait time.Duration wait time.Duration
call *call[T] call *call
result *Result[T] result *Result
} }
type Result[T any] struct { type Result struct {
Val T Val any
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[T]) Do(fn func() (T, error)) (v T, err error, shared bool) { func (s *Single) Do(fn func() (any, error)) (v any, 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[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
return s.result.Val, s.result.Err, true return s.result.Val, s.result.Err, true
} }
if callM := s.call; callM != nil { if call := s.call; call != nil {
s.mux.Unlock() s.mux.Unlock()
callM.wg.Wait() call.wg.Wait()
return callM.val, callM.err, true return call.val, call.err, true
} }
callM := &call[T]{} call := &call{}
callM.wg.Add(1) call.wg.Add(1)
s.call = callM s.call = call
s.mux.Unlock() s.mux.Unlock()
callM.val, callM.err = fn() call.val, call.err = fn()
callM.wg.Done() call.wg.Done()
s.mux.Lock() s.mux.Lock()
s.call = nil s.call = nil
s.result = &Result[T]{callM.val, callM.err} s.result = &Result{call.val, call.err}
s.last = now s.last = now
s.mux.Unlock() s.mux.Unlock()
return callM.val, callM.err, false return call.val, call.err, false
} }
func (s *Single[T]) Reset() { func (s *Single) Reset() {
s.last = time.Time{} s.last = time.Time{}
} }
func NewSingle[T any](wait time.Duration) *Single[T] { func NewSingle(wait time.Duration) *Single {
return &Single[T]{wait: wait} return &Single{wait: wait}
} }

View File

@ -10,13 +10,13 @@ import (
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
single := NewSingle[int](time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
shardCount := atomic.NewInt32(0) shardCount := atomic.NewInt32(0)
call := func() (int, error) { call := func() (any, error) {
foo++ foo++
time.Sleep(time.Millisecond * 5) time.Sleep(time.Millisecond * 5)
return 0, nil return nil, 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[int](time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
callM := func() (int, error) { call := func() (any, error) {
foo++ foo++
return 0, nil return nil, nil
} }
_, _, _ = single.Do(callM) single.Do(call)
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
_, _, shard := single.Do(callM) _, _, shard := single.Do(call)
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[int](time.Millisecond * 30) single := NewSingle(time.Millisecond * 30)
foo := 0 foo := 0
callM := func() (int, error) { call := func() (any, error) {
foo++ foo++
return 0, nil return nil, nil
} }
_, _, _ = single.Do(callM) single.Do(call)
single.Reset() single.Reset()
_, _, _ = single.Do(callM) single.Do(call)
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,19 +159,9 @@ 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))
} }
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) currentField := valSlice.Index(i)
fieldName := fmt.Sprintf("%s[%d]", name, i)
if err := d.decode(fieldName, currentData, currentField); err != nil { if err := d.decode(fieldName, currentData, currentField); err != nil {
return err return err
} }
@ -301,7 +291,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
field reflect.StructField field reflect.StructField
val reflect.Value val reflect.Value
} }
var fields []field fields := []field{}
for len(structs) > 0 { for len(structs) > 0 {
structVal := structs[0] structVal := structs[0]
structs = structs[1:] structs = structs[1:]

View File

@ -1,16 +0,0 @@
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
}

View File

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

View File

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

View File

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

View File

@ -4,28 +4,27 @@ 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 netip.Addr, port int) (net.Addr, error) { func lookupLocalAddr(ifaceName string, network string, destination net.IP, 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 *netip.Prefix var addr *net.IPNet
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.IsValid() { if destination != nil {
if destination.Is4() { if destination.To4() != nil {
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
} else { } else {
addr, err = ifaceObj.PickIPv6Addr(destination) addr, err = ifaceObj.PickIPv6Addr(destination)
@ -40,12 +39,12 @@ func lookupLocalAddr(ifaceName string, network string, destination netip.Addr, p
if strings.HasPrefix(network, "tcp") { if strings.HasPrefix(network, "tcp") {
return &net.TCPAddr{ return &net.TCPAddr{
IP: addr.Addr().AsSlice(), IP: addr.IP,
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.Addr().AsSlice(), IP: addr.IP,
Port: port, Port: port,
}, nil }, nil
} }
@ -53,7 +52,7 @@ func lookupLocalAddr(ifaceName string, network string, destination netip.Addr, p
return nil, iface.ErrAddrNotFound return nil, iface.ErrAddrNotFound
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
if !destination.IsGlobalUnicast() { if !destination.IsGlobalUnicast() {
return nil return nil
} }
@ -84,7 +83,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, netip.Addr{}, int(local)) addr, err := lookupLocalAddr(ifaceName, network, nil, int(local))
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -3,21 +3,11 @@ 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(),
@ -34,9 +24,33 @@ func DialContext(ctx context.Context, network, address string, options ...Option
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
return actualSingleDialContext(ctx, network, address, opt) host, port, err := net.SplitHostPort(address)
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 actualDualStackDialContext(ctx, network, address, opt) return dualStackDialContext(ctx, network, address, opt)
default: default:
return nil, errors.New("network invalid") return nil, errors.New("network invalid")
} }
@ -74,20 +88,7 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
return lc.ListenPacket(ctx, network, address) return lc.ListenPacket(ctx, network, address)
} }
func SetDial(concurrent bool) { func dialContext(ctx context.Context, network string, destination net.IP, port string, opt *option) (net.Conn, error) {
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 {
@ -98,10 +99,6 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
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))
} }
@ -131,12 +128,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 netip.Addr var ip net.IP
if ipv6 { if ipv6 {
if !direct { if !direct {
ip, result.error = resolver.ResolveIPv6ProxyServerHost(host) ip, result.error = resolver.ResolveIPv6ProxyServerHost(host)
@ -185,130 +182,3 @@ 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,15 +4,14 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"syscall" "syscall"
) )
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) { func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
dialer.Control = bindMarkToControl(mark, dialer.Control) dialer.Control = bindMarkToControl(mark, dialer.Control)
} }
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) { func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
lc.Control = bindMarkToControl(mark, lc.Control) lc.Control = bindMarkToControl(mark, lc.Control)
} }
@ -24,17 +23,20 @@ func bindMarkToControl(mark int, chain controlFn) controlFn {
} }
}() }()
addrPort, err := netip.ParseAddrPort(address) ipStr, _, err := net.SplitHostPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() { if err == nil {
return ip := net.ParseIP(ipStr)
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,7 +4,6 @@ package dialer
import ( import (
"net" "net"
"net/netip"
"sync" "sync"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -18,10 +17,10 @@ func printMarkWarn() {
}) })
} }
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) { func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ net.IP) {
printMarkWarn() printMarkWarn()
} }
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) { func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, address string) {
printMarkWarn() printMarkWarn()
} }

View File

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

View File

@ -1,19 +1,20 @@
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"
) )
const ( type uint128 struct {
offsetKey = "key-offset-fake-ip" hi uint64
cycleKey = "key-cycle-fake-ip" lo uint64
) }
type store interface { type store interface {
GetByHost(host string) (netip.Addr, bool) GetByHost(host string) (netip.Addr, bool)
@ -97,15 +98,22 @@ func (p *Pool) CloneFrom(o *Pool) {
} }
func (p *Pool) get(host string) netip.Addr { func (p *Pool) get(host string) netip.Addr {
p.offset = p.offset.Next() for {
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 || p.store.Exist(p.offset) { if p.cycle {
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)
@ -113,39 +121,7 @@ func (p *Pool) get(host string) netip.Addr {
} }
func (p *Pool) FlushFakeIP() error { func (p *Pool) FlushFakeIP() error {
err := p.store.FlushFakeIP() return 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 {
@ -167,10 +143,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 = nnip.UnMasked(*options.IPNet) last = add(hostAddr, 1<<uint64(hostAddr.BitLen()-options.IPNet.Bits())-1)
) )
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) { if !options.IPNet.IsValid() || !first.Less(last) || !options.IPNet.Contains(last) {
return nil, errors.New("ipnet don't have valid ip") return nil, errors.New("ipnet don't have valid ip")
} }
@ -191,7 +167,31 @@ 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,15 +240,13 @@ func TestPool_FlushFileCache(t *testing.T) {
err = pool.FlushFakeIP() err = pool.FlushFakeIP()
assert.Nil(t, err) assert.Nil(t, err)
next := pool.Lookup("baz.com")
baz := pool.Lookup("foo.com") baz := pool.Lookup("foo.com")
next := pool.Lookup("baz.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)
} }
@ -269,15 +267,13 @@ func TestPool_FlushMemoryCache(t *testing.T) {
err := pool.FlushFakeIP() err := pool.FlushFakeIP()
assert.Nil(t, err) assert.Nil(t, err)
next := pool.Lookup("baz.com")
baz := pool.Lookup("foo.com") baz := pool.Lookup("foo.com")
next := pool.Lookup("baz.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 (
"github.com/Dreamacro/clash/common/generics/list" "container/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[Edge]() queue := list.New()
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.nextNode node := front.Value.(Edge).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,7 +3,6 @@ package iface
import ( import (
"errors" "errors"
"net" "net"
"net/netip"
"time" "time"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
@ -12,7 +11,7 @@ import (
type Interface struct { type Interface struct {
Index int Index int
Name string Name string
Addrs []*netip.Prefix Addrs []*net.IPNet
HardwareAddr net.HardwareAddr HardwareAddr net.HardwareAddr
} }
@ -21,10 +20,10 @@ var (
ErrAddrNotFound = errors.New("addr not found") ErrAddrNotFound = errors.New("addr not found")
) )
var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20) var interfaces = singledo.NewSingle(time.Second * 20)
func ResolveInterface(name string) (*Interface, error) { func ResolveInterface(name string) (*Interface, error) {
value, err, _ := interfaces.Do(func() (map[string]*Interface, error) { value, err, _ := interfaces.Do(func() (any, error) {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return nil, err return nil, err
@ -38,18 +37,14 @@ func ResolveInterface(name string) (*Interface, error) {
continue continue
} }
ipNets := make([]*netip.Prefix, 0, len(addrs)) ipNets := make([]*net.IPNet, 0, len(addrs))
for _, addr := range addrs { for _, addr := range addrs {
ipNet := addr.(*net.IPNet) ipNet := addr.(*net.IPNet)
ip, _ := netip.AddrFromSlice(ipNet.IP) if v4 := ipNet.IP.To4(); v4 != nil {
ipNet.IP = v4
ones, bits := ipNet.Mask.Size()
if bits == 32 {
ip = ip.Unmap()
} }
pf := netip.PrefixFrom(ip, ones) ipNets = append(ipNets, ipNet)
ipNets = append(ipNets, &pf)
} }
r[iface.Name] = &Interface{ r[iface.Name] = &Interface{
@ -66,7 +61,7 @@ func ResolveInterface(name string) (*Interface, error) {
return nil, err return nil, err
} }
ifaces := value ifaces := value.(map[string]*Interface)
iface, ok := ifaces[name] iface, ok := ifaces[name]
if !ok { if !ok {
return nil, ErrIfaceNotFound return nil, ErrIfaceNotFound
@ -79,35 +74,35 @@ func FlushCache() {
interfaces.Reset() interfaces.Reset()
} }
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) { func (iface *Interface) PickIPv4Addr(destination net.IP) (*net.IPNet, error) {
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool { return iface.pickIPAddr(destination, func(addr *net.IPNet) bool {
return addr.Addr().Is4() return addr.IP.To4() != nil
}) })
} }
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) { func (iface *Interface) PickIPv6Addr(destination net.IP) (*net.IPNet, error) {
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool { return iface.pickIPAddr(destination, func(addr *net.IPNet) bool {
return addr.Addr().Is6() return addr.IP.To4() == nil
}) })
} }
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) { func (iface *Interface) pickIPAddr(destination net.IP, accept func(addr *net.IPNet) bool) (*net.IPNet, error) {
var fallback *netip.Prefix var fallback *net.IPNet
for _, addr := range iface.Addrs { for _, addr := range iface.Addrs {
if !accept(addr) { if !accept(addr) {
continue continue
} }
if fallback == nil && !addr.Addr().IsLinkLocalUnicast() { if fallback == nil && !addr.IP.IsLinkLocalUnicast() {
fallback = addr fallback = addr
if !destination.IsValid() { if destination == nil {
break break
} }
} }
if destination.IsValid() && addr.Contains(destination) { if destination != nil && addr.Contains(destination) {
return addr, nil return addr, nil
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -3,14 +3,13 @@ package process
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net/netip" "net"
"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"
) )
@ -21,11 +20,7 @@ var (
once sync.Once once sync.Once
) )
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (int32, int32, error) { func findProcessName(network string, ip net.IP, srcPort int) (string, 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())
@ -107,7 +102,7 @@ type searcher struct {
pid int pid int
} }
func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (uint32, error) { func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) {
var itemSize int var itemSize int
var inpOffset int var inpOffset int
@ -121,7 +116,7 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
inpOffset = s.udpInpOffset inpOffset = s.udpInpOffset
} }
isIPv4 := ip.Is4() isIPv4 := ip.To4() != nil
// 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
@ -135,19 +130,19 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
// xinpcb.inp_vflag // xinpcb.inp_vflag
flag := buf[inp+s.vflag] flag := buf[inp+s.vflag]
var srcIP netip.Addr var srcIP net.IP
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4]) srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4]) srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4])
default: default:
continue continue
} }
if ip != srcIP { if !ip.Equal(srcIP) {
continue continue
} }

View File

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

View File

@ -2,12 +2,8 @@
package process package process
import "net/netip" import "net"
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { func findProcessName(network string, ip net.IP, 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,12 +2,11 @@ package process
import ( import (
"fmt" "fmt"
"net/netip" "net"
"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"
@ -29,10 +28,6 @@ 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 {
@ -62,7 +57,7 @@ func initWin32API() error {
return nil return nil
} }
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) { func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@ -72,7 +67,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (string, error)
} }
}) })
family := windows.AF_INET family := windows.AF_INET
if ip.Is6() { if ip.To4() == nil {
family = windows.AF_INET6 family = windows.AF_INET6
} }
@ -112,7 +107,7 @@ type searcher struct {
tcpState int tcpState int
} }
func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) { func (s *searcher) Search(b []byte, ip net.IP, 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++ {
@ -136,9 +131,9 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
continue continue
} }
srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize]) srcIP := net.IP(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 != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) { if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue
} }
@ -219,7 +214,8 @@ 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

@ -1,12 +0,0 @@
//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,19 +1,20 @@
package resolver package resolver
import "net/netip" import (
"net"
)
var DefaultHostMapper Enhancer var DefaultHostMapper Enhancer
type Enhancer interface { type Enhancer interface {
FakeIPEnabled() bool FakeIPEnabled() bool
MappingEnabled() bool MappingEnabled() bool
IsFakeIP(netip.Addr) bool IsFakeIP(net.IP) bool
IsFakeBroadcastIP(netip.Addr) bool IsFakeBroadcastIP(net.IP) bool
IsExistFakeIP(netip.Addr) bool IsExistFakeIP(net.IP) bool
FindHostByIP(netip.Addr) (string, bool) FindHostByIP(net.IP) (string, bool)
FlushFakeIP() error FlushFakeIP() error
InsertHostByIP(netip.Addr, string) InsertHostByIP(net.IP, string)
StoreFakePoolState()
} }
func FakeIPEnabled() bool { func FakeIPEnabled() bool {
@ -32,7 +33,7 @@ func MappingEnabled() bool {
return false return false
} }
func IsFakeIP(ip netip.Addr) bool { func IsFakeIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsFakeIP(ip) return mapper.IsFakeIP(ip)
} }
@ -40,7 +41,7 @@ func IsFakeIP(ip netip.Addr) bool {
return false return false
} }
func IsFakeBroadcastIP(ip netip.Addr) bool { func IsFakeBroadcastIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsFakeBroadcastIP(ip) return mapper.IsFakeBroadcastIP(ip)
} }
@ -48,7 +49,7 @@ func IsFakeBroadcastIP(ip netip.Addr) bool {
return false return false
} }
func IsExistFakeIP(ip netip.Addr) bool { func IsExistFakeIP(ip net.IP) bool {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.IsExistFakeIP(ip) return mapper.IsExistFakeIP(ip)
} }
@ -56,13 +57,13 @@ func IsExistFakeIP(ip netip.Addr) bool {
return false return false
} }
func InsertHostByIP(ip netip.Addr, host string) { func InsertHostByIP(ip net.IP, host string) {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
mapper.InsertHostByIP(ip, host) mapper.InsertHostByIP(ip, host)
} }
} }
func FindHostByIP(ip netip.Addr) (string, bool) { func FindHostByIP(ip net.IP) (string, bool) {
if mapper := DefaultHostMapper; mapper != nil { if mapper := DefaultHostMapper; mapper != nil {
return mapper.FindHostByIP(ip) return mapper.FindHostByIP(ip)
} }
@ -76,9 +77,3 @@ 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,33 @@ var (
) )
type Resolver interface { type Resolver interface {
ResolveIP(host string) (ip netip.Addr, err error) ResolveIP(host string) (ip net.IP, err error)
ResolveIPv4(host string) (ip netip.Addr, err error) ResolveIPv4(host string) (ip net.IP, err error)
ResolveIPv6(host string) (ip netip.Addr, err error) ResolveIPv6(host string) (ip net.IP, 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) (netip.Addr, error) { func ResolveIPv4(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, DefaultResolver) return ResolveIPv4WithResolver(host, DefaultResolver)
} }
func ResolveIPv4WithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
if ips, err := ResolveAllIPv4WithResolver(host, r); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, nil
}
}
// ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (netip.Addr, error) {
return ResolveIPv6WithResolver(host, DefaultResolver)
}
func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) {
if ips, err := ResolveAllIPv6WithResolver(host, r); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
}
// ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
if ips, err := ResolveAllIPPrimaryIPv4WithResolver(host, r); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
}
// ResolveIP with a host, return ip
func ResolveIP(host string) (netip.Addr, error) {
return ResolveIPWithResolver(host, DefaultResolver)
}
// ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
}
return ResolveIPv4(host)
}
// ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
}
return ResolveIPv6(host)
}
// ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveIPWithResolver(host, ProxyServerHostResolver)
}
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 node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is4() { if ip := node.Data; ip.Is4() {
return []netip.Addr{node.Data}, nil return ip.AsSlice(), nil
} }
} }
ip, err := netip.ParseAddr(host) ip := net.ParseIP(host)
if err == nil { if ip != nil {
if ip.Is4() { if !strings.Contains(host, ":") {
return []netip.Addr{ip}, nil return ip, nil
} }
return []netip.Addr{}, ErrIPVersion return nil, ErrIPVersion
} }
if r != nil { if r != nil {
return r.ResolveAllIPv4(host) return r.ResolveIPv4(host)
} }
if DefaultResolver == nil { if DefaultResolver == nil {
@ -173,118 +71,119 @@ func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
defer cancel() defer cancel()
ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host) ipAddrs, err := net.DefaultResolver.LookupIP(ctx, "ip4", host)
if err != nil { if err != nil {
return []netip.Addr{}, err return nil, err
} else if len(ipAddrs) == 0 { } else if len(ipAddrs) == 0 {
return []netip.Addr{}, ErrIPNotFound return nil, ErrIPNotFound
} }
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4() return ipAddrs[rand.Intn(len(ipAddrs))], nil
if ip == nil {
return []netip.Addr{}, ErrIPVersion
}
return []netip.Addr{netip.AddrFrom4(*(*[4]byte)(ip))}, nil
} }
return []netip.Addr{}, ErrIPNotFound return nil, ErrIPNotFound
} }
func ResolveAllIPWithResolver(host string, r Resolver) ([]netip.Addr, error) { // ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, DefaultResolver)
}
func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
if DisableIPv6 {
return nil, ErrIPv6Disabled
}
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
return []netip.Addr{node.Data}, 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
func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil {
ip := node.Data
return ip.Unmap().AsSlice(), nil
} }
if r != nil { if r != nil {
if DisableIPv6 { if DisableIPv6 {
return r.ResolveAllIPv4(host) return r.ResolveIPv4(host)
} }
return r.ResolveIP(host)
return r.ResolveAllIP(host)
} else if DisableIPv6 { } else if DisableIPv6 {
return ResolveAllIPv4(host) return ResolveIPv4(host)
} }
ip, err := netip.ParseAddr(host) ip := net.ParseIP(host)
if err == nil { if ip != nil {
return []netip.Addr{ip}, nil return ip, nil
} }
if DefaultResolver == nil { if DefaultResolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host) ipAddr, err := net.ResolveIPAddr("ip", host)
if err != nil { if err != nil {
return []netip.Addr{}, err return nil, err
} }
return []netip.Addr{nnip.IpToAddr(ipAddr.IP)}, nil return ipAddr.IP, nil
} }
return []netip.Addr{}, ErrIPNotFound return nil, ErrIPNotFound
} }
func ResolveAllIPPrimaryIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) { // ResolveIP with a host, return ip
if node := DefaultHosts.Search(host); node != nil { func ResolveIP(host string) (net.IP, error) {
return []netip.Addr{node.Data}, nil return ResolveIPWithResolver(host, DefaultResolver)
}
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) { // ResolveIPv4ProxyServerHost proxies server host only
return ResolveAllIPWithResolver(host, DefaultResolver) func ResolveIPv4ProxyServerHost(host string) (net.IP, error) {
}
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 { if ProxyServerHostResolver != nil {
return ResolveAllIPv6WithResolver(host, ProxyServerHostResolver) return ResolveIPv4WithResolver(host, ProxyServerHostResolver)
} }
return ResolveIPv4(host)
return ResolveAllIPv6(host)
} }
func ResolveAllIPv4ProxyServerHost(host string) ([]netip.Addr, error) { // ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(host string) (net.IP, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveAllIPv4WithResolver(host, ProxyServerHostResolver) return ResolveIPv6WithResolver(host, ProxyServerHostResolver)
} }
return ResolveIPv6(host)
return ResolveAllIPv4(host)
} }
func ResolveAllIPProxyServerHost(host string) ([]netip.Addr, error) { // ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(host string) (net.IP, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveAllIPWithResolver(host, ProxyServerHostResolver) return ResolveIPWithResolver(host, ProxyServerHostResolver)
} }
return ResolveIP(host)
return ResolveAllIP(host)
} }

View File

@ -2,11 +2,8 @@ package sniffer
import ( import (
"errors" "errors"
"github.com/Dreamacro/clash/constant/sniffer"
"net" "net"
"net/netip"
"strconv" "strconv"
"time"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
@ -20,22 +17,19 @@ 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 ( type SnifferDispatcher struct {
SnifferDispatcher struct { enable bool
enable bool
sniffers []sniffer.Sniffer sniffers []C.Sniffer
foreDomain *trie.DomainTrie[bool] foreDomain *trie.DomainTrie[bool]
skipSNI *trie.DomainTrie[bool] skipSNI *trie.DomainTrie[bool]
portRanges *[]utils.Range[uint16] 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)
@ -50,18 +44,12 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
return return
} }
inWhitelist := false
for _, portRange := range *sd.portRanges { for _, portRange := range *sd.portRanges {
if portRange.Contains(uint16(port)) { if !portRange.Contains(uint16(port)) {
inWhitelist = true return
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
@ -86,6 +74,7 @@ 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 {
@ -95,16 +84,8 @@ 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
} }
@ -121,12 +102,6 @@ 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
} }
} }
@ -142,7 +117,7 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil return &dispatcher, nil
} }
func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[bool], func NewSnifferDispatcher(needSniffer []C.SnifferType, forceDomain *trie.DomainTrie[bool],
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) { skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16]) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{ dispatcher := SnifferDispatcher{
enable: true, enable: true,
@ -164,12 +139,10 @@ func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTr
return &dispatcher, nil return &dispatcher, nil
} }
func NewSniffer(name sniffer.Type) (sniffer.Sniffer, error) { func NewSniffer(name C.SnifferType) (C.Sniffer, error) {
switch name { switch name {
case sniffer.TLS: case C.TLS:
return &TLSSniffer{}, nil return &TLSSniffer{}, nil
case sniffer.HTTP:
return &HTTPSniffer{}, nil
default: default:
return nil, ErrorUnsupportedSniffer return nil, ErrorUnsupportedSniffer
} }

View File

@ -1,100 +0,0 @@
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

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

View File

@ -11,6 +11,7 @@ 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,8 +4,6 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/constant/sniffer"
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
@ -31,8 +29,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"
@ -51,8 +49,6 @@ 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
@ -94,7 +90,7 @@ type DNS struct {
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"` GeoIPCode string `yaml:"geoip-code"`
IPCIDR []*netip.Prefix `yaml:"ipcidr"` IPCIDR []*net.IPNet `yaml:"ipcidr"`
Domain []string `yaml:"domain"` Domain []string `yaml:"domain"`
GeoSite []*router.DomainMatcher `yaml:"geosite"` GeoSite []*router.DomainMatcher `yaml:"geosite"`
} }
@ -113,12 +109,11 @@ 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
@ -131,10 +126,10 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Force bool Force bool
Sniffers []sniffer.Type Sniffers []C.SnifferType
Reverses *trie.DomainTrie[bool] Reverses *trie.DomainTrie[bool]
ForceDomain *trie.DomainTrie[bool] ForceDomain *trie.DomainTrie[bool]
SkipDomain *trie.DomainTrie[bool] SkipSNI *trie.DomainTrie[bool]
Ports *[]utils.Range[uint16] Ports *[]utils.Range[uint16]
} }
@ -154,7 +149,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
} }
@ -211,7 +206,6 @@ 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"`
@ -233,7 +227,6 @@ 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"` Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
} }
@ -263,14 +256,13 @@ 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: false, AutoRoute: true,
AutoDetectInterface: false,
}, },
IPTables: IPTables{ IPTables: IPTables{
Enable: false, Enable: false,
@ -310,7 +302,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Sniffing: []string{}, Sniffing: []string{},
Reverse: []string{}, Reverse: []string{},
ForceDomain: []string{}, ForceDomain: []string{},
SkipDomain: []string{}, SkipSNI: []string{},
Ports: []string{}, Ports: []string{},
}, },
Profile: Profile{ Profile: Profile{
@ -420,7 +412,6 @@ 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
} }
@ -527,8 +518,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 {
@ -537,12 +528,13 @@ 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 {
@ -554,6 +546,10 @@ 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" {
@ -688,7 +684,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, Interface: dialer.DefaultInterface.Load(),
}, },
) )
} }
@ -712,15 +708,15 @@ func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServe
return policy, nil return policy, nil
} }
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) { func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
var ipNets []*netip.Prefix var ipNets []*net.IPNet
for idx, ip := range ips { for idx, ip := range ips {
ipnet, err := netip.ParsePrefix(ip) _, ipnet, err := net.ParseCIDR(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
@ -773,7 +769,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: []*netip.Prefix{}, IPCIDR: []*net.IPNet{},
GeoSite: []*router.DomainMatcher{}, GeoSite: []*router.DomainMatcher{},
}, },
} }
@ -885,7 +881,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 { if err != nil || autoDetectInterfaceName == "" {
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)
@ -900,7 +896,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)
@ -910,12 +906,11 @@ 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
} }
@ -925,39 +920,38 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
Force: snifferRaw.Force, Force: snifferRaw.Force,
} }
var ports []utils.Range[uint16] ports := []utils.Range[uint16]{}
if len(snifferRaw.Ports) == 0 { if len(snifferRaw.Ports) == 0 {
ports = append(ports, *utils.NewRange[uint16](0, 65535)) ports = append(ports, *utils.NewRange[uint16](0, 65535))
} else { } else {
for _, portRange := range snifferRaw.Ports { for _, portRange := range snifferRaw.Ports {
portRaws := strings.Split(portRange, "-") 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 { if len(portRaws) > 1 {
p, err = strconv.ParseUint(portRaws[1], 10, 16) p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
start := uint16(p)
p, err = strconv.ParseUint(portRaws[0], 10, 16)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s format error", portRange) return nil, fmt.Errorf("%s format error", portRange)
} }
end := uint16(p) end := uint16(p)
ports = append(ports, *utils.NewRange(start, end)) ports = append(ports, *utils.NewRange(start, end))
} else {
ports = append(ports, *utils.NewRange(start, start))
} }
} }
} }
sniffer.Ports = &ports sniffer.Ports = &ports
loadSniffer := make(map[snifferTypes.Type]struct{}) loadSniffer := make(map[C.SnifferType]struct{})
for _, snifferName := range snifferRaw.Sniffing { for _, snifferName := range snifferRaw.Sniffing {
find := false find := false
for _, snifferType := range snifferTypes.List { for _, snifferType := range C.SnifferList {
if snifferType.String() == strings.ToUpper(snifferName) { if snifferType.String() == strings.ToUpper(snifferName) {
find = true find = true
loadSniffer[snifferType] = struct{}{} loadSniffer[snifferType] = struct{}{}
@ -972,6 +966,7 @@ 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)
@ -979,13 +974,10 @@ 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 {
log.Warnln("Sniffer param skip-sni renamed to ship-domain, old param will be removed in the release version") sniffer.SkipSNI = trie.New[bool]()
snifferRaw.SkipDomain = snifferRaw.SkipSNI for _, domain := range 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)
} }
@ -998,7 +990,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.SkipDomain.Insert(domain, true) err := sniffer.SkipSNI.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,10 +99,6 @@ 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,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"net/netip"
"strconv" "strconv"
) )
@ -74,26 +73,22 @@ 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 netip.Addr `json:"sourceIP"` SrcIP net.IP `json:"sourceIP"`
DstIP netip.Addr `json:"destinationIP"` DstIP net.IP `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"`
Uid *int32 `json:"uid"` Process string `json:"process"`
Process string `json:"process"` ProcessPath string `json:"processPath"`
ProcessPath string `json:"processPath"`
RemoteDst string `json:"remoteDestination"`
} }
// avoid stack overflow
type jsonMetadata Metadata
func (m *Metadata) RemoteAddress() string { func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), m.DstPort) return net.JoinHostPort(m.String(), m.DstPort)
} }
func (m *Metadata) SourceAddress() string { func (m *Metadata) SourceAddress() string {
@ -101,49 +96,45 @@ func (m *Metadata) SourceAddress() string {
} }
func (m *Metadata) SourceDetail() string { func (m *Metadata) SourceDetail() string {
if m.Type == INNER { if m.Process != "" {
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.IsValid() return m.DstIP != nil
} }
// 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.IsValid() { if m.DNSMode == DNSMapping && m.DstIP != nil {
copyM := *m copy := *m
copyM.Host = "" copy.Host = ""
if copyM.DstIP.Is4() { if copy.DstIP.To4() != nil {
copyM.AddrType = AtypIPv4 copy.AddrType = AtypIPv4
} else { } else {
copyM.AddrType = AtypIPv6 copy.AddrType = AtypIPv6
} }
return &copyM return &copy
} }
return m return m
} }
func (m *Metadata) UDPAddr() *net.UDPAddr { func (m *Metadata) UDPAddr() *net.UDPAddr {
if m.NetWork != UDP || !m.DstIP.IsValid() { if m.NetWork != UDP || m.DstIP == nil {
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.AsSlice(), IP: m.DstIP,
Port: int(port), Port: int(port),
} }
} }
@ -151,7 +142,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.IsValid() { } else if m.DstIP != nil {
return m.DstIP.String() return m.DstIP.String()
} else { } else {
return "<nil>" return "<nil>"
@ -159,5 +150,5 @@ func (m *Metadata) String() string {
} }
func (m *Metadata) Valid() bool { func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP.IsValid() return m.Host != "" || m.DstIP != nil
} }

View File

@ -29,6 +29,7 @@ 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
@ -122,6 +123,23 @@ 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,7 +70,6 @@ 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/netip" "net"
"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 []*netip.Prefix SourceIPs []*net.IPNet
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 netip.Addr) bool { func (re *RuleExtra) NotMatchSourceIP(srcIP net.IP) bool {
if re.SourceIPs == nil { if re.SourceIPs == nil {
return false return false
} }

26
constant/sniffer.go Normal file
View File

@ -0,0 +1,26 @@
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

@ -1,31 +0,0 @@
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,9 +4,7 @@ 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"
@ -20,7 +18,7 @@ type client struct {
r *Resolver r *Resolver
port string port string
host string host string
iface *atomic.String iface string
proxyAdapter string proxyAdapter string
} }
@ -30,10 +28,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 netip.Addr ip net.IP
err error err error
) )
if ip, err = netip.ParseAddr(c.host); err != nil { if ip = net.ParseIP(c.host); ip == 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 {
@ -50,8 +48,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
} }
options := []dialer.Option{} options := []dialer.Option{}
if c.iface != nil && c.iface.Load() != "" { if c.iface != "" {
options = append(options, dialer.WithInterface(c.iface.Load())) options = append(options, dialer.WithInterface(c.iface))
} }
var conn net.Conn var conn net.Conn
@ -64,9 +62,7 @@ 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 func() { defer conn.Close()
_ = 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,10 +1,9 @@
package dns package dns
import ( import (
"bytes"
"context" "context"
"go.uber.org/atomic"
"net" "net"
"net/netip"
"sync" "sync"
"time" "time"
@ -28,7 +27,7 @@ type dhcpClient struct {
ifaceInvalidate time.Time ifaceInvalidate time.Time
dnsInvalidate time.Time dnsInvalidate time.Time
ifaceAddr *netip.Prefix ifaceAddr *net.IPNet
done chan struct{} done chan struct{}
resolver *Resolver resolver *Resolver
err error err error
@ -72,7 +71,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: atomic.NewString(d.ifaceName), Interface: d.ifaceName,
}) })
} }
@ -128,12 +127,12 @@ func (d *dhcpClient) invalidate() (bool, error) {
return false, err return false, err
} }
addr, err := ifaceObj.PickIPv4Addr(netip.Addr{}) addr, err := ifaceObj.PickIPv4Addr(nil)
if err != nil { if err != nil {
return false, err return false, err
} }
if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr == addr { if time.Now().Before(d.dnsInvalidate) && d.ifaceAddr.IP.Equal(addr.IP) && bytes.Equal(d.ifaceAddr.Mask, addr.Mask) {
return false, nil return false, nil
} }

View File

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

View File

@ -1,19 +1,18 @@
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(netip.Addr) bool Match(net.IP) bool
} }
type geoipFilter struct { type geoipFilter struct {
@ -22,9 +21,9 @@ type geoipFilter struct {
var geoIPMatcher *router.GeoIPMatcher var geoIPMatcher *router.GeoIPMatcher
func (gf *geoipFilter) Match(ip netip.Addr) bool { func (gf *geoipFilter) Match(ip net.IP) bool {
if !C.GeodataMode { if !C.GeodataMode {
record, _ := mmdb.Instance().Country(ip.AsSlice()) record, _ := mmdb.Instance().Country(ip)
return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate() return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
} }
@ -55,14 +54,14 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
return false return false
} }
} }
return !geoIPMatcher.Match(ip.AsSlice()) return !geoIPMatcher.Match(ip)
} }
type ipnetFilter struct { type ipnetFilter struct {
ipnet *netip.Prefix ipnet *net.IPNet
} }
func (inf *ipnetFilter) Match(ip netip.Addr) bool { func (inf *ipnetFilter) Match(ip net.IP) bool {
return inf.ipnet.Contains(ip) return inf.ipnet.Contains(ip)
} }
@ -77,7 +76,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 netip.Addr var ip net.IP
var ttl uint32 var ttl uint32
switch a := ans.(type) { switch a := ans.(type) {
case *D.A: case *D.A:
ip = nnip.IpToAddr(a.A) ip = a.A
ttl = a.Hdr.Ttl ttl = a.Hdr.Ttl
case *D.AAAA: case *D.AAAA:
ip = nnip.IpToAddr(a.AAAA) ip = a.AAAA
ttl = a.Hdr.Ttl ttl = a.Hdr.Ttl
default: default:
continue continue
} }
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*time.Duration(ttl))) mapping.SetWithExpire(ipToAddr(ip), host, time.Now().Add(time.Second*time.Duration(ttl)))
} }
return msg, nil return msg, nil

View File

@ -5,10 +5,6 @@ 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"
@ -23,20 +19,10 @@ 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)
} }
@ -138,49 +124,10 @@ 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)
var ( session, err := quic.DialAddrContext(context.Background(), dc.addr, tlsConfig, quicConfig)
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

@ -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,8 +45,9 @@ type Resolver struct {
proxyServer []dnsClient proxyServer []dnsClient
} }
func (r *Resolver) ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err error) { // ResolveIP request with TypeA and TypeAAAA, priority return TypeA
ch := make(chan []netip.Addr, 1) func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
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)
@ -56,7 +57,7 @@ func (r *Resolver) ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err e
ch <- ip ch <- ip
}() }()
ips, err = r.resolveIP(host, D.TypeA) ip, err = r.resolveIP(host, D.TypeA)
if err == nil { if err == nil {
return return
} }
@ -69,65 +70,17 @@ func (r *Resolver) ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err e
return ip, nil return ip, nil
} }
func (r *Resolver) ResolveAllIP(host string) (ips []netip.Addr, err error) { // ResolveIPv4 request with TypeA
ch := make(chan []netip.Addr, 1) func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) {
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)
} }
func (r *Resolver) ResolveAllIPv6(host string) (ips []netip.Addr, err error) { // ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) {
return r.resolveIP(host, D.TypeAAAA) return r.resolveIP(host, D.TypeAAAA)
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA func (r *Resolver) shouldIPFallback(ip net.IP) bool {
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
@ -148,10 +101,10 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
} }
q := m.Question[0] q := m.Question[0]
cacheM, expireTime, hit := r.lruCache.GetWithExpire(q.String()) cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if hit { if hit {
now := time.Now() now := time.Now()
msg = cacheM.Copy() msg = cache.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)
@ -200,10 +153,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[*D.Msg](ctx, resolver.DefaultDNSTimeout) fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
for _, client := range clients { for _, client := range clients {
r := client r := client
fast.Go(func() (*D.Msg, error) { fast.Go(func() (any, 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
@ -223,7 +176,7 @@ func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.
return nil, err return nil, err
} }
msg = elm msg = elm.(*D.Msg)
return return
} }
@ -302,16 +255,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) (ips []netip.Addr, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
ip, err := netip.ParseAddr(host) ip = net.ParseIP(host)
if err == nil { if ip != nil {
isIPv4 := ip.Is4() isIPv4 := ip.To4() != nil
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return []netip.Addr{ip}, nil return ip, nil
} else if dnsType == D.TypeA && isIPv4 { } else if dnsType == D.TypeA && isIPv4 {
return []netip.Addr{ip}, nil return ip, nil
} else { } else {
return []netip.Addr{}, resolver.ErrIPVersion return nil, resolver.ErrIPVersion
} }
} }
@ -320,15 +273,16 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ips []netip.Addr, err
msg, err := r.Exchange(query) msg, err := r.Exchange(query)
if err != nil { if err != nil {
return []netip.Addr{}, err return nil, err
} }
ips = msgToIP(msg) ips := msgToIP(msg)
ipLength := len(ips) ipLength := len(ips)
if ipLength == 0 { if ipLength == 0 {
return []netip.Addr{}, resolver.ErrIPNotFound return nil, resolver.ErrIPNotFound
} }
ip = ips[rand.Intn(ipLength)]
return return
} }
@ -357,14 +311,14 @@ func (r *Resolver) HasProxyServer() bool {
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
Interface *atomic.String Interface string
ProxyAdapter string ProxyAdapter string
} }
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool GeoIP bool
GeoIPCode string GeoIPCode string
IPCIDR []*netip.Prefix IPCIDR []*net.IPNet
Domain []string Domain []string
GeoSite []*router.DomainMatcher GeoSite []*router.DomainMatcher
} }
@ -405,7 +359,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,7 +9,6 @@ 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"
@ -64,7 +63,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, newDOQ(resolver, s.Addr, s.ProxyAdapter)) ret = append(ret, &quicClient{addr: s.Addr})
continue continue
} }
@ -101,21 +100,37 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
return msg return msg
} }
func msgToIP(msg *D.Msg) []netip.Addr { func msgToIP(msg *D.Msg) []net.IP {
ips := []netip.Addr{} ips := []net.IP{}
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, nnip.IpToAddr(ans.AAAA)) ips = append(ips, ans.AAAA)
case *D.A: case *D.A:
ips = append(ips, nnip.IpToAddr(ans.A)) ips = append(ips, 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
@ -134,7 +149,7 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
return wpc.rAddr return wpc.rAddr
} }
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) { func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP net.IP, 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)
@ -149,7 +164,7 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ
} }
addrType := C.AtypIPv4 addrType := C.AtypIPv4
if dstIP.Is6() { if dstIP.To4() == nil {
addrType = C.AtypIPv6 addrType = C.AtypIPv6
} }

16
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.1 github.com/go-chi/cors v1.2.0
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-20220504074936-1ca156eafb9f github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
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,23 +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-20220507011949-2cf3adece122 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 golang.org/x/net v0.0.0-20220412020605-290c469a71a5
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-20220503163025-988cb79eb6c6 golang.org/x/sys v0.0.0-20220412071739-889880a91fd5
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-20220506231117-8ef340c14150 gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b
) )
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.4 // indirect github.com/fsnotify/fsnotify v1.5.1 // 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.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
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.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.0/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-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I=
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/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,11 +221,9 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/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=
@ -257,8 +255,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/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=
@ -304,9 +302,8 @@ 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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412071739-889880a91fd5 h1:NubxfvTRuNb4RVzWrIDAUzUvREH1HkCD4JjyQTSG9As=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220412071739-889880a91fd5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
@ -385,8 +382,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-20220506231117-8ef340c14150 h1:bspdBY1iCLtW6JXold8yhXHkAiE9UoWfmHShNkTc9JA= gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b h1:JW1pUBe6A3H+b0B9DwEOcfK+TLS/04A3A9cettPpfV0=
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= gvisor.dev/gvisor v0.0.0-20220412020520-6917e582612b/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

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

View File

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

View File

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

View File

@ -2,10 +2,11 @@ package executor
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/listener/inner" "net"
"net/netip" "net/netip"
"os" "os"
"runtime" "runtime"
"strconv"
"sync" "sync"
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
@ -77,24 +78,18 @@ 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)
updateHosts(cfg.Hosts) updateDNS(cfg.DNS, cfg.Tun)
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
@ -117,7 +112,6 @@ 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
@ -125,15 +119,7 @@ func GetGeneral() *config.General {
func updateExperimental(c *config.Config) {} func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS) { func updateDNS(c *config.DNS, t *config.Tun) {
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,
@ -153,8 +139,6 @@ func updateDNS(c *config.DNS) {
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)
@ -166,13 +150,26 @@ func updateDNS(c *config.DNS) {
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
} }
dns.ReCreateServer(c.Listen, r, m) if t.Enable {
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]) {
@ -183,77 +180,49 @@ 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(pv provider.Provider) { func loadProvider(ruleProviders map[string]*provider.RuleProvider, proxyProviders map[string]provider.ProxyProvider) {
if pv.VehicleType() == provider.Compatible { load := func(pv provider.Provider) {
log.Infoln("Start initial compatible provider %s", pv.Name()) if pv.VehicleType() == provider.Compatible {
} else { log.Infoln("Start initial compatible provider %s", pv.Name())
log.Infoln("Start initial provider %s", (pv).Name()) } else {
} log.Infoln("Start initial provider %s", (pv).Name())
}
if err := pv.Initial(); err != nil { if err := (pv).Initial(); err != nil {
switch pv.Type() { switch pv.Type() {
case provider.Proxy: case provider.Proxy:
{ {
log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err) log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err)
} }
case provider.Rule: case provider.Rule:
{ {
log.Warnln("initial rule provider %s error: %v", (pv).Name(), err) log.Warnln("initial rule provider %s error: %v", (pv).Name(), err)
} }
}
} }
} }
}
func loadRuleProvider(ruleProviders map[string]provider.RuleProvider) {
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, ruleProvider := range ruleProviders {
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 { for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider load(proxyProvider)
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
loadProvider(proxyProvider)
}()
} }
wg.Wait() for _, ruleProvider := range ruleProviders {
load(*ruleProvider)
}
} }
func updateTun(tun *config.Tun, dns *config.DNS) { func updateTun(tun *config.Tun, dns *config.DNS) {
var tunAddressPrefix *netip.Prefix P.ReCreateTun(tun, dns, tunnel.TCPIn(), tunnel.UDPIn())
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.SkipDomain, sniffer.Ports) dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipSNI, sniffer.Ports)
if err != nil { if err != nil {
log.Warnln("initial sniffer failed, err:%v", err) log.Warnln("initial sniffer failed, err:%v", err)
} }
@ -274,19 +243,10 @@ 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)
dialer.DisableIPv6 = !general.IPv6 resolver.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() != "" {
@ -404,7 +364,13 @@ func updateIPTables(cfg *config.Config) {
return return
} }
dnsPort, err := netip.ParseAddrPort(dnsCfg.Listen) _, dnsPortStr, err := net.SplitHostPort(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
@ -418,7 +384,7 @@ func updateIPTables(cfg *config.Config) {
dialer.DefaultRoutingMark.Store(2158) dialer.DefaultRoutingMark.Store(2158)
} }
err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), dnsPort.Port()) err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), uint16(dnsPort))
if err != nil { if err != nil {
return return
} }
@ -429,7 +395,6 @@ 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")
} }

View File

@ -94,8 +94,8 @@ func getRuleProviders(w http.ResponseWriter, r *http.Request) {
} }
func updateRuleProvider(w http.ResponseWriter, r *http.Request) { func updateRuleProvider(w http.ResponseWriter, r *http.Request) {
provider := r.Context().Value(CtxKeyProvider).(provider.RuleProvider) provider := r.Context().Value(CtxKeyProvider).(*provider.RuleProvider)
if err := provider.Update(); err != nil { if err := (*provider).Update(); err != nil {
render.Status(r, http.StatusServiceUnavailable) render.Status(r, http.StatusServiceUnavailable)
render.JSON(w, r, newError(err.Error())) render.JSON(w, r, newError(err.Error()))
} }

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