├── .github ├── ISSUE_TEMPLATE │ ├── bug_report_en.yml │ ├── bug_report_zh.yml │ ├── config.yml │ ├── feature_request_en.yml │ └── feature_request_zh.yml └── workflows │ ├── codeql-analysis.yml │ ├── deploy-docs.yml │ ├── docker.yml │ ├── linter.yml │ ├── release.yml │ └── stale.yml ├── .gitignore ├── .golangci.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── adapter ├── adapter.go ├── inbound │ ├── http.go │ ├── https.go │ ├── packet.go │ ├── socket.go │ └── util.go ├── outbound │ ├── base.go │ ├── direct.go │ ├── http.go │ ├── reject.go │ ├── shadowsocks.go │ ├── shadowsocksr.go │ ├── snell.go │ ├── socks5.go │ ├── trojan.go │ ├── util.go │ └── vmess.go ├── outboundgroup │ ├── common.go │ ├── fallback.go │ ├── loadbalance.go │ ├── parser.go │ ├── relay.go │ ├── selector.go │ ├── urltest.go │ └── util.go ├── parser.go └── provider │ ├── fetcher.go │ ├── healthcheck.go │ ├── parser.go │ ├── provider.go │ └── vehicle.go ├── common ├── batch │ ├── batch.go │ └── batch_test.go ├── cache │ ├── lrucache.go │ └── lrucache_test.go ├── murmur3 │ ├── murmur.go │ └── murmur32.go ├── net │ ├── bufconn.go │ ├── io.go │ └── relay.go ├── observable │ ├── iterable.go │ ├── observable.go │ ├── observable_test.go │ └── subscriber.go ├── picker │ ├── picker.go │ └── picker_test.go ├── pool │ ├── alloc.go │ ├── alloc_test.go │ ├── buffer.go │ └── pool.go ├── queue │ └── queue.go ├── singledo │ ├── singledo.go │ └── singledo_test.go ├── sockopt │ ├── reuseaddr_linux.go │ └── reuseaddr_other.go └── structure │ ├── structure.go │ └── structure_test.go ├── component ├── auth │ └── auth.go ├── dhcp │ ├── conn.go │ └── dhcp.go ├── dialer │ ├── bind_darwin.go │ ├── bind_linux.go │ ├── bind_others.go │ ├── bind_windows.go │ ├── dialer.go │ ├── fallbackbind.go │ ├── mark_linux.go │ ├── mark_nonlinux.go │ ├── options.go │ ├── reuse_others.go │ ├── reuse_unix.go │ └── reuse_windows.go ├── fakeip │ ├── cachefile.go │ ├── memory.go │ ├── pool.go │ └── pool_test.go ├── iface │ └── iface.go ├── ipset │ ├── ipset_linux.go │ └── ipset_others.go ├── mmdb │ └── mmdb.go ├── nat │ └── table.go ├── pool │ ├── pool.go │ └── pool_test.go ├── process │ ├── process.go │ ├── process_darwin.go │ ├── process_freebsd.go │ ├── process_freebsd_test.go │ ├── process_linux.go │ ├── process_other.go │ ├── process_test.go │ └── process_windows.go ├── profile │ ├── cachefile │ │ └── cache.go │ └── profile.go ├── resolver │ ├── defaults.go │ ├── enhancer.go │ └── resolver.go └── trie │ ├── domain.go │ ├── domain_test.go │ └── node.go ├── config ├── config.go ├── initial.go └── utils.go ├── constant ├── adapters.go ├── context.go ├── dns.go ├── listener.go ├── metadata.go ├── path.go ├── provider │ └── interface.go ├── rule.go └── version.go ├── context ├── conn.go ├── dns.go └── packetconn.go ├── dns ├── client.go ├── dhcp.go ├── doh.go ├── enhancer.go ├── filters.go ├── middleware.go ├── resolver.go ├── server.go └── util.go ├── docs ├── .vitepress │ └── config.ts ├── advanced-usages │ ├── golang-api.md │ ├── openconnect.md │ └── wireguard.md ├── assets │ └── connection-flow.png ├── configuration │ ├── configuration-reference.md │ ├── dns.md │ ├── getting-started.md │ ├── inbound.md │ ├── introduction.md │ ├── outbound.md │ └── rules.md ├── index.md ├── introduction │ ├── _dummy-index.md │ ├── faq.md │ ├── getting-started.md │ └── service.md ├── logo.png ├── package.json ├── premium │ ├── ebpf.md │ ├── experimental-features.md │ ├── introduction.md │ ├── rule-providers.md │ ├── script-shortcuts.md │ ├── script.md │ ├── the-profiling-engine.md │ ├── tun-device.md │ └── userspace-wireguard.md ├── public │ └── logo.png └── runtime │ └── external-controller.md ├── go.mod ├── go.sum ├── hub ├── executor │ └── executor.go ├── hub.go └── route │ ├── common.go │ ├── configs.go │ ├── connections.go │ ├── ctxkeys.go │ ├── dns.go │ ├── errors.go │ ├── provider.go │ ├── proxies.go │ ├── rules.go │ └── server.go ├── listener ├── auth │ └── auth.go ├── http │ ├── client.go │ ├── hack.go │ ├── proxy.go │ ├── server.go │ ├── upgrade.go │ └── utils.go ├── listener.go ├── mixed │ └── mixed.go ├── redir │ ├── tcp.go │ ├── tcp_darwin.go │ ├── tcp_freebsd.go │ ├── tcp_linux.go │ ├── tcp_linux_386.go │ ├── tcp_linux_other.go │ └── tcp_other.go ├── socks │ ├── tcp.go │ ├── udp.go │ └── utils.go ├── tproxy │ ├── packet.go │ ├── setsockopt_linux.go │ ├── setsockopt_other.go │ ├── tcp.go │ ├── udp.go │ ├── udp_linux.go │ └── udp_other.go ├── tun │ ├── dev │ │ ├── dev.go │ │ ├── dev_darwin.go │ │ ├── dev_linux.go │ │ └── dev_unsupport.go │ ├── tun.go │ ├── tundns.go │ ├── tunproxy.go │ └── utils.go └── tunnel │ ├── packet.go │ ├── tcp.go │ └── udp.go ├── log ├── level.go └── log.go ├── main.go ├── rule ├── base.go ├── domain.go ├── domain_keyword.go ├── domain_suffix.go ├── final.go ├── geoip.go ├── ipcidr.go ├── ipset.go ├── parser.go ├── port.go └── process.go ├── test ├── .golangci.yaml ├── Makefile ├── README.md ├── clash_test.go ├── config │ ├── example.org-key.pem │ ├── example.org.pem │ ├── snell-http.conf │ ├── snell-tls.conf │ ├── snell.conf │ ├── trojan-grpc.json │ ├── trojan-ws.json │ ├── trojan.json │ ├── vmess-grpc.json │ ├── vmess-http.json │ ├── vmess-http2.json │ ├── vmess-tls.json │ ├── vmess-ws-0rtt.json │ ├── vmess-ws-tls-zero.json │ ├── vmess-ws-tls.json │ ├── vmess-ws.json │ └── vmess.json ├── dns_test.go ├── docker_test.go ├── go.mod ├── go.sum ├── main.go ├── snell_test.go ├── ss_test.go ├── trojan_test.go ├── util.go ├── util_darwin_test.go ├── util_other_test.go └── vmess_test.go ├── transport ├── gun │ └── gun.go ├── shadowsocks │ ├── README.md │ ├── core │ │ └── cipher.go │ ├── shadowaead │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go │ └── shadowstream │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go ├── simple-obfs │ ├── http.go │ └── tls.go ├── snell │ ├── cipher.go │ ├── pool.go │ └── snell.go ├── socks4 │ └── socks4.go ├── socks5 │ └── socks5.go ├── ssr │ ├── obfs │ │ ├── base.go │ │ ├── http_post.go │ │ ├── http_simple.go │ │ ├── obfs.go │ │ ├── plain.go │ │ ├── random_head.go │ │ └── tls1.2_ticket_auth.go │ ├── protocol │ │ ├── auth_aes128_md5.go │ │ ├── auth_aes128_sha1.go │ │ ├── auth_chain_a.go │ │ ├── auth_chain_b.go │ │ ├── auth_sha1_v4.go │ │ ├── base.go │ │ ├── origin.go │ │ ├── packet.go │ │ ├── protocol.go │ │ └── stream.go │ └── tools │ │ ├── bufPool.go │ │ ├── crypto.go │ │ └── random.go ├── trojan │ └── trojan.go ├── v2ray-plugin │ ├── mux.go │ └── websocket.go └── vmess │ ├── aead.go │ ├── chunk.go │ ├── conn.go │ ├── h2.go │ ├── header.go │ ├── http.go │ ├── tls.go │ ├── user.go │ ├── vmess.go │ └── websocket.go └── tunnel ├── connection.go ├── mode.go ├── statistic ├── manager.go └── tracker.go └── tunnel.go /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: (中文)阅读 Wiki 5 | url: https://dreamacro.github.io/clash/ 6 | about: 如果你是新手,或者想要了解 Clash 的更多信息,请阅读我们撰写的官方 Wiki 7 | - name: (English) Read our Wiki page 8 | url: https://dreamacro.github.io/clash/ 9 | about: If you are new to Clash, or want to know more about Clash, please read our Wiki page 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_zh.yml: -------------------------------------------------------------------------------- 1 | name: (中文)建议一个新功能 2 | description: 在这里提供一个的想法或建议 3 | labels: 4 | - enhancement 5 | title: "[Feature] <标题>" 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: "## 欢迎来到 Clash 官方开源社区!" 10 | 11 | - type: markdown 12 | attributes: 13 | value: | 14 | 感谢你拨冗为 Clash 内核提供建议。在提交之前,请仔细阅读并遵守以下指引,以确保你的建议能够被顺利采纳。 15 | 带有星号(*)的选项为必填,其他可选填。**如果你填写的资料不符合规范,维护者可能不予回复,并直接关闭这个 issue。** 16 | 如果你可以自行添加这个功能,我们随时欢迎你提交 Pull Request,并将你的修改合并到上游。 17 | 18 | - type: checkboxes 19 | id: ensure 20 | attributes: 21 | label: 先决条件 22 | description: "若以下任意选项不适用,请勿提交这个 issue,因为我们会把它关闭" 23 | options: 24 | - label: "我了解这里是 Clash 官方仓库,并非 Clash.Meta / OpenClash / ClashX / Clash For Windows 或其他任何衍生版本" 25 | required: true 26 | - label: "我已经在[这里](https://github.com/Dreamacro/clash/issues?q=is%3Aissue+label%3Aenhancement)找过我要提出的建议,**并且没有找到相关问题**" 27 | required: true 28 | - label: "我已经仔细阅读 [官方 Wiki](https://dreamacro.github.io/clash/) " 29 | required: true 30 | 31 | - type: textarea 32 | attributes: 33 | label: 描述 34 | placeholder: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什么? 35 | validations: 36 | required: true 37 | 38 | - type: textarea 39 | attributes: 40 | label: 可能的解决方案 41 | placeholder: 此项非必须,但是如果你有想法的话欢迎提出。 42 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: [master, dev] 6 | 7 | jobs: 8 | analyze: 9 | name: Analyze 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | language: ['go'] 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | 21 | - name: Initialize CodeQL 22 | uses: github/codeql-action/init@v2 23 | with: 24 | languages: ${{ matrix.language }} 25 | 26 | - name: Autobuild 27 | uses: github/codeql-action/autobuild@v2 28 | 29 | - name: Perform CodeQL Analysis 30 | uses: github/codeql-action/analyze@v2 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [20] 13 | permissions: 14 | pages: write 15 | id-token: write 16 | environment: 17 | name: github-pages 18 | url: ${{ steps.deployment.outputs.page_url }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | - uses: pnpm/action-setup@v2 24 | with: 25 | version: latest 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - name: Install dependencies 31 | working-directory: docs 32 | run: pnpm install --frozen-lockfile=false 33 | - name: Build 34 | working-directory: docs 35 | run: pnpm run docs:build 36 | - uses: actions/configure-pages@v2 37 | - uses: actions/upload-pages-artifact@v1 38 | with: 39 | path: docs/.vitepress/dist 40 | - name: Deploy 41 | id: deployment 42 | uses: actions/deploy-pages@v2 43 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | 9 | - name: Setup Go 10 | uses: actions/setup-go@v4 11 | with: 12 | check-latest: true 13 | go-version: '1.20' 14 | 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v3 17 | with: 18 | version: latest 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Setup Go 8 | uses: actions/setup-go@v4 9 | with: 10 | check-latest: true 11 | go-version: '1.20' 12 | 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@v3 15 | 16 | - name: Cache go module 17 | uses: actions/cache@v3 18 | with: 19 | path: | 20 | ~/go/pkg/mod 21 | ~/.cache/go-build 22 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 23 | restore-keys: | 24 | ${{ runner.os }}-go- 25 | 26 | - name: Get dependencies, run test 27 | run: | 28 | go test ./... 29 | 30 | - name: Build 31 | if: startsWith(github.ref, 'refs/tags/') 32 | env: 33 | NAME: clash 34 | BINDIR: bin 35 | run: make -j $(go run ./test/main.go) releases 36 | 37 | - name: Upload Release 38 | uses: softprops/action-gh-release@v1 39 | if: startsWith(github.ref, 'refs/tags/') 40 | with: 41 | files: bin/* 42 | draft: true 43 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Mark stale issues and pull requests 3 | 4 | on: 5 | schedule: 6 | - cron: "30 1 * * *" 7 | 8 | jobs: 9 | stale: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/stale@v7 15 | with: 16 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' 17 | days-before-stale: 60 18 | days-before-close: 5 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # go mod vendor 16 | vendor 17 | 18 | # GoLand 19 | .idea/* 20 | 21 | # macOS file 22 | .DS_Store 23 | 24 | # test suite 25 | test/config/cache* 26 | 27 | # docs site generator 28 | node_modules 29 | package-lock.json 30 | pnpm-lock.yaml 31 | 32 | # docs site cache 33 | docs/.vitepress/cache 34 | 35 | # docs site build files 36 | docs/.vitepress/dist 37 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gci 5 | - gofumpt 6 | - gosimple 7 | - govet 8 | - ineffassign 9 | - misspell 10 | - staticcheck 11 | - unconvert 12 | - unused 13 | - usestdlibvars 14 | 15 | linters-settings: 16 | gci: 17 | custom-order: true 18 | sections: 19 | - standard 20 | - prefix(github.com/Dreamacro/clash) 21 | - default 22 | staticcheck: 23 | go: '1.20' 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM} golang:alpine as builder 2 | 3 | RUN apk add --no-cache make git ca-certificates tzdata && \ 4 | wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb 5 | WORKDIR /workdir 6 | COPY --from=tonistiigi/xx:golang / / 7 | ARG TARGETOS TARGETARCH TARGETVARIANT 8 | 9 | RUN --mount=target=. \ 10 | --mount=type=cache,target=/root/.cache/go-build \ 11 | --mount=type=cache,target=/go/pkg/mod \ 12 | make BINDIR= ${TARGETOS}-${TARGETARCH}${TARGETVARIANT} && \ 13 | mv /clash* /clash 14 | 15 | FROM alpine:latest 16 | LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash" 17 | 18 | COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo 19 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 20 | COPY --from=builder /Country.mmdb /root/.config/clash/ 21 | COPY --from=builder /clash / 22 | ENTRYPOINT ["/clash"] 23 | -------------------------------------------------------------------------------- /adapter/inbound/http.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/context" 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | ) 11 | 12 | // NewHTTP receive normal http request and return HTTPContext 13 | func NewHTTP(target socks5.Addr, source net.Addr, originTarget net.Addr, conn net.Conn) *context.ConnContext { 14 | metadata := parseSocksAddr(target) 15 | metadata.NetWork = C.TCP 16 | metadata.Type = C.HTTP 17 | if ip, port, err := parseAddr(source.String()); err == nil { 18 | metadata.SrcIP = ip 19 | metadata.SrcPort = port 20 | } 21 | if addrPort, err := netip.ParseAddrPort(originTarget.String()); err == nil { 22 | metadata.OriginDst = addrPort 23 | } 24 | return context.NewConnContext(conn, metadata) 25 | } 26 | -------------------------------------------------------------------------------- /adapter/inbound/https.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/netip" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | "github.com/Dreamacro/clash/context" 10 | ) 11 | 12 | // NewHTTPS receive CONNECT request and return ConnContext 13 | func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext { 14 | metadata := parseHTTPAddr(request) 15 | metadata.Type = C.HTTPCONNECT 16 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 17 | metadata.SrcIP = ip 18 | metadata.SrcPort = port 19 | } 20 | if addrPort, err := netip.ParseAddrPort(conn.LocalAddr().String()); err == nil { 21 | metadata.OriginDst = addrPort 22 | } 23 | return context.NewConnContext(conn, metadata) 24 | } 25 | -------------------------------------------------------------------------------- /adapter/inbound/packet.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | // PacketAdapter is a UDP Packet adapter for socks/redir/tun 12 | type PacketAdapter struct { 13 | C.UDPPacket 14 | metadata *C.Metadata 15 | } 16 | 17 | // Metadata returns destination metadata 18 | func (s *PacketAdapter) Metadata() *C.Metadata { 19 | return s.metadata 20 | } 21 | 22 | // NewPacket is PacketAdapter generator 23 | func NewPacket(target socks5.Addr, originTarget net.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter { 24 | metadata := parseSocksAddr(target) 25 | metadata.NetWork = C.UDP 26 | metadata.Type = source 27 | if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil { 28 | metadata.SrcIP = ip 29 | metadata.SrcPort = port 30 | } 31 | if addrPort, err := netip.ParseAddrPort(originTarget.String()); err == nil { 32 | metadata.OriginDst = addrPort 33 | } 34 | return &PacketAdapter{ 35 | UDPPacket: packet, 36 | metadata: metadata, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /adapter/inbound/socket.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/context" 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | ) 11 | 12 | // NewSocket receive TCP inbound and return ConnContext 13 | func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext { 14 | metadata := parseSocksAddr(target) 15 | metadata.NetWork = C.TCP 16 | metadata.Type = source 17 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 18 | metadata.SrcIP = ip 19 | metadata.SrcPort = port 20 | } 21 | if addrPort, err := netip.ParseAddrPort(conn.LocalAddr().String()); err == nil { 22 | metadata.OriginDst = addrPort 23 | } 24 | return context.NewConnContext(conn, metadata) 25 | } 26 | -------------------------------------------------------------------------------- /adapter/inbound/util.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/transport/socks5" 11 | ) 12 | 13 | func parseSocksAddr(target socks5.Addr) *C.Metadata { 14 | metadata := &C.Metadata{} 15 | 16 | switch target[0] { 17 | case socks5.AtypDomainName: 18 | // trim for FQDN 19 | metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") 20 | metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) 21 | case socks5.AtypIPv4: 22 | ip := net.IP(target[1 : 1+net.IPv4len]) 23 | metadata.DstIP = ip 24 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) 25 | case socks5.AtypIPv6: 26 | ip := net.IP(target[1 : 1+net.IPv6len]) 27 | metadata.DstIP = ip 28 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) 29 | } 30 | 31 | return metadata 32 | } 33 | 34 | func parseHTTPAddr(request *http.Request) *C.Metadata { 35 | host := request.URL.Hostname() 36 | port := request.URL.Port() 37 | if port == "" { 38 | port = "80" 39 | } 40 | 41 | // trim FQDN (#737) 42 | host = strings.TrimRight(host, ".") 43 | 44 | metadata := &C.Metadata{ 45 | NetWork: C.TCP, 46 | Host: host, 47 | DstIP: nil, 48 | DstPort: port, 49 | } 50 | 51 | if ip := net.ParseIP(host); ip != nil { 52 | metadata.DstIP = ip 53 | } 54 | 55 | return metadata 56 | } 57 | 58 | func parseAddr(addr string) (net.IP, string, error) { 59 | host, port, err := net.SplitHostPort(addr) 60 | if err != nil { 61 | return nil, "", err 62 | } 63 | 64 | ip := net.ParseIP(host) 65 | return ip, port, nil 66 | } 67 | -------------------------------------------------------------------------------- /adapter/outbound/direct.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/component/dialer" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type Direct struct { 12 | *Base 13 | } 14 | 15 | // DialContext implements C.ProxyAdapter 16 | func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 17 | c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) 18 | if err != nil { 19 | return nil, err 20 | } 21 | tcpKeepAlive(c) 22 | return NewConn(c, d), nil 23 | } 24 | 25 | // ListenPacketContext implements C.ProxyAdapter 26 | func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 27 | pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return newPacketConn(&directPacketConn{pc}, d), nil 32 | } 33 | 34 | type directPacketConn struct { 35 | net.PacketConn 36 | } 37 | 38 | func NewDirect() *Direct { 39 | return &Direct{ 40 | Base: &Base{ 41 | name: "DIRECT", 42 | tp: C.Direct, 43 | udp: true, 44 | }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /adapter/outbound/reject.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "time" 8 | 9 | "github.com/Dreamacro/clash/component/dialer" 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | type Reject struct { 14 | *Base 15 | } 16 | 17 | // DialContext implements C.ProxyAdapter 18 | func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 19 | return NewConn(&nopConn{}, r), nil 20 | } 21 | 22 | // ListenPacketContext implements C.ProxyAdapter 23 | func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 24 | return newPacketConn(&nopPacketConn{}, r), nil 25 | } 26 | 27 | func NewReject() *Reject { 28 | return &Reject{ 29 | Base: &Base{ 30 | name: "REJECT", 31 | tp: C.Reject, 32 | udp: true, 33 | }, 34 | } 35 | } 36 | 37 | type nopConn struct{} 38 | 39 | func (rw *nopConn) Read(b []byte) (int, error) { 40 | return 0, io.EOF 41 | } 42 | 43 | func (rw *nopConn) Write(b []byte) (int, error) { 44 | return 0, io.EOF 45 | } 46 | 47 | func (rw *nopConn) Close() error { return nil } 48 | func (rw *nopConn) LocalAddr() net.Addr { return nil } 49 | func (rw *nopConn) RemoteAddr() net.Addr { return nil } 50 | func (rw *nopConn) SetDeadline(time.Time) error { return nil } 51 | func (rw *nopConn) SetReadDeadline(time.Time) error { return nil } 52 | func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil } 53 | 54 | type nopPacketConn struct{} 55 | 56 | func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil } 57 | func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF } 58 | func (npc *nopPacketConn) Close() error { return nil } 59 | func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} } 60 | func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil } 61 | func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil } 62 | func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil } 63 | -------------------------------------------------------------------------------- /adapter/outbound/util.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/component/resolver" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/transport/socks5" 11 | 12 | "github.com/Dreamacro/protobytes" 13 | ) 14 | 15 | func tcpKeepAlive(c net.Conn) { 16 | if tcp, ok := c.(*net.TCPConn); ok { 17 | tcp.SetKeepAlive(true) 18 | tcp.SetKeepAlivePeriod(30 * time.Second) 19 | } 20 | } 21 | 22 | func serializesSocksAddr(metadata *C.Metadata) []byte { 23 | buf := protobytes.BytesWriter{} 24 | 25 | addrType := metadata.AddrType() 26 | buf.PutUint8(uint8(addrType)) 27 | 28 | p, _ := strconv.ParseUint(metadata.DstPort, 10, 16) 29 | switch addrType { 30 | case socks5.AtypDomainName: 31 | buf.PutUint8(uint8(len(metadata.Host))) 32 | buf.PutString(metadata.Host) 33 | case socks5.AtypIPv4: 34 | buf.PutSlice(metadata.DstIP.To4()) 35 | case socks5.AtypIPv6: 36 | buf.PutSlice(metadata.DstIP.To16()) 37 | } 38 | 39 | buf.PutUint16be(uint16(p)) 40 | return buf.Bytes() 41 | } 42 | 43 | func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { 44 | host, port, err := net.SplitHostPort(address) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | ip, err := resolver.ResolveIP(host) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 54 | } 55 | 56 | func safeConnClose(c net.Conn, err error) { 57 | if err != nil { 58 | c.Close() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /adapter/outboundgroup/common.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "time" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/constant/provider" 8 | ) 9 | 10 | const ( 11 | defaultGetProxiesDuration = time.Second * 5 12 | ) 13 | 14 | func touchProviders(providers []provider.ProxyProvider) { 15 | for _, provider := range providers { 16 | provider.Touch() 17 | } 18 | } 19 | 20 | func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { 21 | proxies := []C.Proxy{} 22 | for _, provider := range providers { 23 | if touch { 24 | provider.Touch() 25 | } 26 | proxies = append(proxies, provider.Proxies()...) 27 | } 28 | return proxies 29 | } 30 | -------------------------------------------------------------------------------- /adapter/outboundgroup/util.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { 12 | host, port, err := net.SplitHostPort(rawAddress) 13 | if err != nil { 14 | err = fmt.Errorf("addrToMetadata failed: %w", err) 15 | return 16 | } 17 | 18 | ip := net.ParseIP(host) 19 | if ip == nil { 20 | addr = &C.Metadata{ 21 | Host: host, 22 | DstIP: nil, 23 | DstPort: port, 24 | } 25 | return 26 | } else if ip4 := ip.To4(); ip4 != nil { 27 | addr = &C.Metadata{ 28 | Host: "", 29 | DstIP: ip4, 30 | DstPort: port, 31 | } 32 | return 33 | } 34 | 35 | addr = &C.Metadata{ 36 | Host: "", 37 | DstIP: ip, 38 | DstPort: port, 39 | } 40 | return 41 | } 42 | 43 | func tcpKeepAlive(c net.Conn) { 44 | if tcp, ok := c.(*net.TCPConn); ok { 45 | tcp.SetKeepAlive(true) 46 | tcp.SetKeepAlivePeriod(30 * time.Second) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /adapter/provider/healthcheck.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/common/batch" 8 | C "github.com/Dreamacro/clash/constant" 9 | 10 | "go.uber.org/atomic" 11 | ) 12 | 13 | const ( 14 | defaultURLTestTimeout = time.Second * 5 15 | ) 16 | 17 | type HealthCheckOption struct { 18 | URL string 19 | Interval uint 20 | } 21 | 22 | type HealthCheck struct { 23 | url string 24 | proxies []C.Proxy 25 | interval uint 26 | lazy bool 27 | lastTouch *atomic.Int64 28 | done chan struct{} 29 | } 30 | 31 | func (hc *HealthCheck) process() { 32 | ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) 33 | 34 | go hc.check() 35 | for { 36 | select { 37 | case <-ticker.C: 38 | now := time.Now().Unix() 39 | if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { 40 | hc.check() 41 | } 42 | case <-hc.done: 43 | ticker.Stop() 44 | return 45 | } 46 | } 47 | } 48 | 49 | func (hc *HealthCheck) setProxy(proxies []C.Proxy) { 50 | hc.proxies = proxies 51 | } 52 | 53 | func (hc *HealthCheck) auto() bool { 54 | return hc.interval != 0 55 | } 56 | 57 | func (hc *HealthCheck) touch() { 58 | hc.lastTouch.Store(time.Now().Unix()) 59 | } 60 | 61 | func (hc *HealthCheck) check() { 62 | b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10)) 63 | for _, proxy := range hc.proxies { 64 | p := proxy 65 | b.Go(p.Name(), func() (any, error) { 66 | ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) 67 | defer cancel() 68 | p.URLTest(ctx, hc.url) 69 | return nil, nil 70 | }) 71 | } 72 | b.Wait() 73 | } 74 | 75 | func (hc *HealthCheck) close() { 76 | hc.done <- struct{}{} 77 | } 78 | 79 | func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck { 80 | return &HealthCheck{ 81 | proxies: proxies, 82 | url: url, 83 | interval: interval, 84 | lazy: lazy, 85 | lastTouch: atomic.NewInt64(0), 86 | done: make(chan struct{}, 1), 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /adapter/provider/parser.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/common/structure" 9 | C "github.com/Dreamacro/clash/constant" 10 | types "github.com/Dreamacro/clash/constant/provider" 11 | ) 12 | 13 | var ( 14 | errVehicleType = errors.New("unsupport vehicle type") 15 | errSubPath = errors.New("path is not subpath of home directory") 16 | ) 17 | 18 | type healthCheckSchema struct { 19 | Enable bool `provider:"enable"` 20 | URL string `provider:"url"` 21 | Interval int `provider:"interval"` 22 | Lazy bool `provider:"lazy,omitempty"` 23 | } 24 | 25 | type proxyProviderSchema struct { 26 | Type string `provider:"type"` 27 | Path string `provider:"path"` 28 | URL string `provider:"url,omitempty"` 29 | Interval int `provider:"interval,omitempty"` 30 | Filter string `provider:"filter,omitempty"` 31 | HealthCheck healthCheckSchema `provider:"health-check,omitempty"` 32 | } 33 | 34 | func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { 35 | decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) 36 | 37 | schema := &proxyProviderSchema{ 38 | HealthCheck: healthCheckSchema{ 39 | Lazy: true, 40 | }, 41 | } 42 | if err := decoder.Decode(mapping, schema); err != nil { 43 | return nil, err 44 | } 45 | 46 | var hcInterval uint 47 | if schema.HealthCheck.Enable { 48 | hcInterval = uint(schema.HealthCheck.Interval) 49 | } 50 | hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy) 51 | 52 | path := C.Path.Resolve(schema.Path) 53 | 54 | var vehicle types.Vehicle 55 | switch schema.Type { 56 | case "file": 57 | vehicle = NewFileVehicle(path) 58 | case "http": 59 | if !C.Path.IsSubPath(path) { 60 | return nil, fmt.Errorf("%w: %s", errSubPath, path) 61 | } 62 | vehicle = NewHTTPVehicle(schema.URL, path) 63 | default: 64 | return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) 65 | } 66 | 67 | interval := time.Duration(uint(schema.Interval)) * time.Second 68 | filter := schema.Filter 69 | return NewProxySetProvider(name, interval, filter, vehicle, hc) 70 | } 71 | -------------------------------------------------------------------------------- /adapter/provider/vehicle.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "time" 11 | 12 | "github.com/Dreamacro/clash/component/dialer" 13 | types "github.com/Dreamacro/clash/constant/provider" 14 | ) 15 | 16 | type FileVehicle struct { 17 | path string 18 | } 19 | 20 | func (f *FileVehicle) Type() types.VehicleType { 21 | return types.File 22 | } 23 | 24 | func (f *FileVehicle) Path() string { 25 | return f.path 26 | } 27 | 28 | func (f *FileVehicle) Read() ([]byte, error) { 29 | return os.ReadFile(f.path) 30 | } 31 | 32 | func NewFileVehicle(path string) *FileVehicle { 33 | return &FileVehicle{path: path} 34 | } 35 | 36 | type HTTPVehicle struct { 37 | url string 38 | path string 39 | } 40 | 41 | func (h *HTTPVehicle) Type() types.VehicleType { 42 | return types.HTTP 43 | } 44 | 45 | func (h *HTTPVehicle) Path() string { 46 | return h.path 47 | } 48 | 49 | func (h *HTTPVehicle) Read() ([]byte, error) { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 51 | defer cancel() 52 | 53 | uri, err := url.Parse(h.url) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | req, err := http.NewRequest(http.MethodGet, uri.String(), nil) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | if user := uri.User; user != nil { 64 | password, _ := user.Password() 65 | req.SetBasicAuth(user.Username(), password) 66 | } 67 | 68 | req = req.WithContext(ctx) 69 | 70 | transport := &http.Transport{ 71 | // from http.DefaultTransport 72 | MaxIdleConns: 100, 73 | IdleConnTimeout: 90 * time.Second, 74 | TLSHandshakeTimeout: 10 * time.Second, 75 | ExpectContinueTimeout: 1 * time.Second, 76 | DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { 77 | return dialer.DialContext(ctx, network, address) 78 | }, 79 | } 80 | 81 | client := http.Client{Transport: transport} 82 | resp, err := client.Do(req) 83 | if err != nil { 84 | return nil, err 85 | } 86 | defer resp.Body.Close() 87 | 88 | buf, err := io.ReadAll(resp.Body) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return buf, nil 94 | } 95 | 96 | func NewHTTPVehicle(url string, path string) *HTTPVehicle { 97 | return &HTTPVehicle{url, path} 98 | } 99 | -------------------------------------------------------------------------------- /common/batch/batch.go: -------------------------------------------------------------------------------- 1 | package batch 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | type Option = func(b *Batch) 9 | 10 | type Result struct { 11 | Value any 12 | Err error 13 | } 14 | 15 | type Error struct { 16 | Key string 17 | Err error 18 | } 19 | 20 | func WithConcurrencyNum(n int) Option { 21 | return func(b *Batch) { 22 | q := make(chan struct{}, n) 23 | for i := 0; i < n; i++ { 24 | q <- struct{}{} 25 | } 26 | b.queue = q 27 | } 28 | } 29 | 30 | // Batch similar to errgroup, but can control the maximum number of concurrent 31 | type Batch struct { 32 | result map[string]Result 33 | queue chan struct{} 34 | wg sync.WaitGroup 35 | mux sync.Mutex 36 | err *Error 37 | once sync.Once 38 | cancel func() 39 | } 40 | 41 | func (b *Batch) Go(key string, fn func() (any, error)) { 42 | b.wg.Add(1) 43 | go func() { 44 | defer b.wg.Done() 45 | if b.queue != nil { 46 | <-b.queue 47 | defer func() { 48 | b.queue <- struct{}{} 49 | }() 50 | } 51 | 52 | value, err := fn() 53 | if err != nil { 54 | b.once.Do(func() { 55 | b.err = &Error{key, err} 56 | if b.cancel != nil { 57 | b.cancel() 58 | } 59 | }) 60 | } 61 | 62 | ret := Result{value, err} 63 | b.mux.Lock() 64 | defer b.mux.Unlock() 65 | b.result[key] = ret 66 | }() 67 | } 68 | 69 | func (b *Batch) Wait() *Error { 70 | b.wg.Wait() 71 | if b.cancel != nil { 72 | b.cancel() 73 | } 74 | return b.err 75 | } 76 | 77 | func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) { 78 | err := b.Wait() 79 | return b.Result(), err 80 | } 81 | 82 | func (b *Batch) Result() map[string]Result { 83 | b.mux.Lock() 84 | defer b.mux.Unlock() 85 | copy := map[string]Result{} 86 | for k, v := range b.result { 87 | copy[k] = v 88 | } 89 | return copy 90 | } 91 | 92 | func New(ctx context.Context, opts ...Option) (*Batch, context.Context) { 93 | ctx, cancel := context.WithCancel(ctx) 94 | 95 | b := &Batch{ 96 | result: map[string]Result{}, 97 | } 98 | 99 | for _, o := range opts { 100 | o(b) 101 | } 102 | 103 | b.cancel = cancel 104 | return b, ctx 105 | } 106 | -------------------------------------------------------------------------------- /common/batch/batch_test.go: -------------------------------------------------------------------------------- 1 | package batch 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestBatch(t *testing.T) { 14 | b, _ := New(context.Background()) 15 | 16 | now := time.Now() 17 | b.Go("foo", func() (any, error) { 18 | time.Sleep(time.Millisecond * 100) 19 | return "foo", nil 20 | }) 21 | b.Go("bar", func() (any, error) { 22 | time.Sleep(time.Millisecond * 150) 23 | return "bar", nil 24 | }) 25 | result, err := b.WaitAndGetResult() 26 | 27 | assert.Nil(t, err) 28 | 29 | duration := time.Since(now) 30 | assert.Less(t, duration, time.Millisecond*200) 31 | assert.Equal(t, 2, len(result)) 32 | 33 | for k, v := range result { 34 | assert.NoError(t, v.Err) 35 | assert.Equal(t, k, v.Value.(string)) 36 | } 37 | } 38 | 39 | func TestBatchWithConcurrencyNum(t *testing.T) { 40 | b, _ := New( 41 | context.Background(), 42 | WithConcurrencyNum(3), 43 | ) 44 | 45 | now := time.Now() 46 | for i := 0; i < 7; i++ { 47 | idx := i 48 | b.Go(strconv.Itoa(idx), func() (any, error) { 49 | time.Sleep(time.Millisecond * 100) 50 | return strconv.Itoa(idx), nil 51 | }) 52 | } 53 | result, _ := b.WaitAndGetResult() 54 | duration := time.Since(now) 55 | assert.Greater(t, duration, time.Millisecond*260) 56 | assert.Equal(t, 7, len(result)) 57 | 58 | for k, v := range result { 59 | assert.NoError(t, v.Err) 60 | assert.Equal(t, k, v.Value.(string)) 61 | } 62 | } 63 | 64 | func TestBatchContext(t *testing.T) { 65 | b, ctx := New(context.Background()) 66 | 67 | b.Go("error", func() (any, error) { 68 | time.Sleep(time.Millisecond * 100) 69 | return nil, errors.New("test error") 70 | }) 71 | 72 | b.Go("ctx", func() (any, error) { 73 | <-ctx.Done() 74 | return nil, ctx.Err() 75 | }) 76 | 77 | result, err := b.WaitAndGetResult() 78 | 79 | assert.NotNil(t, err) 80 | assert.Equal(t, "error", err.Key) 81 | 82 | assert.Equal(t, ctx.Err(), result["ctx"].Err) 83 | } 84 | -------------------------------------------------------------------------------- /common/murmur3/murmur.go: -------------------------------------------------------------------------------- 1 | package murmur3 2 | 3 | type bmixer interface { 4 | bmix(p []byte) (tail []byte) 5 | Size() (n int) 6 | reset() 7 | } 8 | 9 | type digest struct { 10 | clen int // Digested input cumulative length. 11 | tail []byte // 0 to Size()-1 bytes view of `buf'. 12 | buf [16]byte // Expected (but not required) to be Size() large. 13 | seed uint32 // Seed for initializing the hash. 14 | bmixer 15 | } 16 | 17 | func (d *digest) BlockSize() int { return 1 } 18 | 19 | func (d *digest) Write(p []byte) (n int, err error) { 20 | n = len(p) 21 | d.clen += n 22 | 23 | if len(d.tail) > 0 { 24 | // Stick back pending bytes. 25 | nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. 26 | if nfree < len(p) { 27 | // One full block can be formed. 28 | block := append(d.tail, p[:nfree]...) 29 | p = p[nfree:] 30 | _ = d.bmix(block) // No tail. 31 | } else { 32 | // Tail's buf is large enough to prevent reallocs. 33 | p = append(d.tail, p...) 34 | } 35 | } 36 | 37 | d.tail = d.bmix(p) 38 | 39 | // Keep own copy of the 0 to Size()-1 pending bytes. 40 | nn := copy(d.buf[:], d.tail) 41 | d.tail = d.buf[:nn] 42 | 43 | return n, nil 44 | } 45 | 46 | func (d *digest) Reset() { 47 | d.clen = 0 48 | d.tail = nil 49 | d.bmixer.reset() 50 | } 51 | -------------------------------------------------------------------------------- /common/net/bufconn.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | ) 7 | 8 | type BufferedConn struct { 9 | r *bufio.Reader 10 | net.Conn 11 | } 12 | 13 | func NewBufferedConn(c net.Conn) *BufferedConn { 14 | if bc, ok := c.(*BufferedConn); ok { 15 | return bc 16 | } 17 | return &BufferedConn{bufio.NewReader(c), c} 18 | } 19 | 20 | // Reader returns the internal bufio.Reader. 21 | func (c *BufferedConn) Reader() *bufio.Reader { 22 | return c.r 23 | } 24 | 25 | // Peek returns the next n bytes without advancing the reader. 26 | func (c *BufferedConn) Peek(n int) ([]byte, error) { 27 | return c.r.Peek(n) 28 | } 29 | 30 | func (c *BufferedConn) Read(p []byte) (int, error) { 31 | return c.r.Read(p) 32 | } 33 | 34 | func (c *BufferedConn) ReadByte() (byte, error) { 35 | return c.r.ReadByte() 36 | } 37 | 38 | func (c *BufferedConn) UnreadByte() error { 39 | return c.r.UnreadByte() 40 | } 41 | 42 | func (c *BufferedConn) Buffered() int { 43 | return c.r.Buffered() 44 | } 45 | -------------------------------------------------------------------------------- /common/net/io.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "io" 4 | 5 | type ReadOnlyReader struct { 6 | io.Reader 7 | } 8 | 9 | type WriteOnlyWriter struct { 10 | io.Writer 11 | } 12 | -------------------------------------------------------------------------------- /common/net/relay.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | ) 8 | 9 | // Relay copies between left and right bidirectionally. 10 | func Relay(leftConn, rightConn net.Conn) { 11 | ch := make(chan error) 12 | 13 | go func() { 14 | // Wrapping to avoid using *net.TCPConn.(ReadFrom) 15 | // See also https://github.com/Dreamacro/clash/pull/1209 16 | _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) 17 | leftConn.SetReadDeadline(time.Now()) 18 | ch <- err 19 | }() 20 | 21 | io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}) 22 | rightConn.SetReadDeadline(time.Now()) 23 | <-ch 24 | } 25 | -------------------------------------------------------------------------------- /common/observable/iterable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | type Iterable <-chan any 4 | -------------------------------------------------------------------------------- /common/observable/observable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | type Observable struct { 9 | iterable Iterable 10 | listener map[Subscription]*Subscriber 11 | mux sync.Mutex 12 | done bool 13 | } 14 | 15 | func (o *Observable) process() { 16 | for item := range o.iterable { 17 | o.mux.Lock() 18 | for _, sub := range o.listener { 19 | sub.Emit(item) 20 | } 21 | o.mux.Unlock() 22 | } 23 | o.close() 24 | } 25 | 26 | func (o *Observable) close() { 27 | o.mux.Lock() 28 | defer o.mux.Unlock() 29 | 30 | o.done = true 31 | for _, sub := range o.listener { 32 | sub.Close() 33 | } 34 | } 35 | 36 | func (o *Observable) Subscribe() (Subscription, error) { 37 | o.mux.Lock() 38 | defer o.mux.Unlock() 39 | if o.done { 40 | return nil, errors.New("Observable is closed") 41 | } 42 | subscriber := newSubscriber() 43 | o.listener[subscriber.Out()] = subscriber 44 | return subscriber.Out(), nil 45 | } 46 | 47 | func (o *Observable) UnSubscribe(sub Subscription) { 48 | o.mux.Lock() 49 | defer o.mux.Unlock() 50 | subscriber, exist := o.listener[sub] 51 | if !exist { 52 | return 53 | } 54 | delete(o.listener, sub) 55 | subscriber.Close() 56 | } 57 | 58 | func NewObservable(any Iterable) *Observable { 59 | observable := &Observable{ 60 | iterable: any, 61 | listener: map[Subscription]*Subscriber{}, 62 | } 63 | go observable.process() 64 | return observable 65 | } 66 | -------------------------------------------------------------------------------- /common/observable/subscriber.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Subscription <-chan any 8 | 9 | type Subscriber struct { 10 | buffer chan any 11 | once sync.Once 12 | } 13 | 14 | func (s *Subscriber) Emit(item any) { 15 | s.buffer <- item 16 | } 17 | 18 | func (s *Subscriber) Out() Subscription { 19 | return s.buffer 20 | } 21 | 22 | func (s *Subscriber) Close() { 23 | s.once.Do(func() { 24 | close(s.buffer) 25 | }) 26 | } 27 | 28 | func newSubscriber() *Subscriber { 29 | sub := &Subscriber{ 30 | buffer: make(chan any, 200), 31 | } 32 | return sub 33 | } 34 | -------------------------------------------------------------------------------- /common/picker/picker.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Picker provides synchronization, and Context cancelation 10 | // for groups of goroutines working on subtasks of a common task. 11 | // Inspired by errGroup 12 | type Picker struct { 13 | ctx context.Context 14 | cancel func() 15 | 16 | wg sync.WaitGroup 17 | 18 | once sync.Once 19 | errOnce sync.Once 20 | result any 21 | err error 22 | } 23 | 24 | func newPicker(ctx context.Context, cancel func()) *Picker { 25 | return &Picker{ 26 | ctx: ctx, 27 | cancel: cancel, 28 | } 29 | } 30 | 31 | // WithContext returns a new Picker and an associated Context derived from ctx. 32 | // and cancel when first element return. 33 | func WithContext(ctx context.Context) (*Picker, context.Context) { 34 | ctx, cancel := context.WithCancel(ctx) 35 | return newPicker(ctx, cancel), ctx 36 | } 37 | 38 | // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. 39 | func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { 40 | ctx, cancel := context.WithTimeout(ctx, timeout) 41 | return newPicker(ctx, cancel), ctx 42 | } 43 | 44 | // Wait blocks until all function calls from the Go method have returned, 45 | // then returns the first nil error result (if any) from them. 46 | func (p *Picker) Wait() any { 47 | p.wg.Wait() 48 | if p.cancel != nil { 49 | p.cancel() 50 | } 51 | return p.result 52 | } 53 | 54 | // Error return the first error (if all success return nil) 55 | func (p *Picker) Error() error { 56 | return p.err 57 | } 58 | 59 | // Go calls the given function in a new goroutine. 60 | // The first call to return a nil error cancels the group; its result will be returned by Wait. 61 | func (p *Picker) Go(f func() (any, error)) { 62 | p.wg.Add(1) 63 | 64 | go func() { 65 | defer p.wg.Done() 66 | 67 | if ret, err := f(); err == nil { 68 | p.once.Do(func() { 69 | p.result = ret 70 | if p.cancel != nil { 71 | p.cancel() 72 | } 73 | }) 74 | } else { 75 | p.errOnce.Do(func() { 76 | p.err = err 77 | }) 78 | } 79 | }() 80 | } 81 | -------------------------------------------------------------------------------- /common/picker/picker_test.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func sleepAndSend(ctx context.Context, delay int, input any) func() (any, error) { 12 | return func() (any, error) { 13 | timer := time.NewTimer(time.Millisecond * time.Duration(delay)) 14 | select { 15 | case <-timer.C: 16 | return input, nil 17 | case <-ctx.Done(): 18 | return nil, ctx.Err() 19 | } 20 | } 21 | } 22 | 23 | func TestPicker_Basic(t *testing.T) { 24 | picker, ctx := WithContext(context.Background()) 25 | picker.Go(sleepAndSend(ctx, 30, 2)) 26 | picker.Go(sleepAndSend(ctx, 20, 1)) 27 | 28 | number := picker.Wait() 29 | assert.NotNil(t, number) 30 | assert.Equal(t, number.(int), 1) 31 | } 32 | 33 | func TestPicker_Timeout(t *testing.T) { 34 | picker, ctx := WithTimeout(context.Background(), time.Millisecond*5) 35 | picker.Go(sleepAndSend(ctx, 20, 1)) 36 | 37 | number := picker.Wait() 38 | assert.Nil(t, number) 39 | assert.NotNil(t, picker.Error()) 40 | } 41 | -------------------------------------------------------------------------------- /common/pool/alloc.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | // Inspired by https://github.com/xtaci/smux/blob/master/alloc.go 4 | 5 | import ( 6 | "errors" 7 | "math/bits" 8 | "sync" 9 | ) 10 | 11 | var defaultAllocator = NewAllocator() 12 | 13 | // Allocator for incoming frames, optimized to prevent overwriting after zeroing 14 | type Allocator struct { 15 | buffers []sync.Pool 16 | } 17 | 18 | // NewAllocator initiates a []byte allocator for frames less than 65536 bytes, 19 | // the waste(memory fragmentation) of space allocation is guaranteed to be 20 | // no more than 50%. 21 | func NewAllocator() *Allocator { 22 | alloc := new(Allocator) 23 | alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K 24 | for k := range alloc.buffers { 25 | i := k 26 | alloc.buffers[k].New = func() any { 27 | return make([]byte, 1< 65536 { 36 | return nil 37 | } 38 | 39 | bits := msb(size) 40 | if size == 1< 65536 || cap(buf) != 1< host on head of linked list 19 | m.cache.Get(ipToUint(ip.To4())) 20 | return ip, true 21 | } 22 | 23 | return nil, false 24 | } 25 | 26 | // PutByHost implements store.PutByHost 27 | func (m *memoryStore) PutByHost(host string, ip net.IP) { 28 | m.cache.Set(host, ip) 29 | } 30 | 31 | // GetByIP implements store.GetByIP 32 | func (m *memoryStore) GetByIP(ip net.IP) (string, bool) { 33 | if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist { 34 | host := elm.(string) 35 | 36 | // ensure host --> ip on head of linked list 37 | m.cache.Get(host) 38 | return host, true 39 | } 40 | 41 | return "", false 42 | } 43 | 44 | // PutByIP implements store.PutByIP 45 | func (m *memoryStore) PutByIP(ip net.IP, host string) { 46 | m.cache.Set(ipToUint(ip.To4()), host) 47 | } 48 | 49 | // DelByIP implements store.DelByIP 50 | func (m *memoryStore) DelByIP(ip net.IP) { 51 | ipNum := ipToUint(ip.To4()) 52 | if elm, exist := m.cache.Get(ipNum); exist { 53 | m.cache.Delete(elm.(string)) 54 | } 55 | m.cache.Delete(ipNum) 56 | } 57 | 58 | // Exist implements store.Exist 59 | func (m *memoryStore) Exist(ip net.IP) bool { 60 | return m.cache.Exist(ipToUint(ip.To4())) 61 | } 62 | 63 | // CloneTo implements store.CloneTo 64 | // only for memoryStore to memoryStore 65 | func (m *memoryStore) CloneTo(store store) { 66 | if ms, ok := store.(*memoryStore); ok { 67 | m.cache.CloneTo(ms.cache) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /component/ipset/ipset_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package ipset 4 | 5 | import ( 6 | "net" 7 | 8 | "github.com/vishvananda/netlink" 9 | ) 10 | 11 | // Test whether the ip is in the set or not 12 | func Test(setName string, ip net.IP) (bool, error) { 13 | return netlink.IpsetTest(setName, &netlink.IPSetEntry{ 14 | IP: ip, 15 | }) 16 | } 17 | 18 | // Verify dumps a specific ipset to check if we can use the set normally 19 | func Verify(setName string) error { 20 | _, err := netlink.IpsetList(setName) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /component/ipset/ipset_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package ipset 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | // Always return false in non-linux 10 | func Test(setName string, ip net.IP) (bool, error) { 11 | return false, nil 12 | } 13 | 14 | // Always pass in non-linux 15 | func Verify(setName string) error { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /component/mmdb/mmdb.go: -------------------------------------------------------------------------------- 1 | package mmdb 2 | 3 | import ( 4 | "sync" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/log" 8 | 9 | "github.com/oschwald/geoip2-golang" 10 | ) 11 | 12 | var ( 13 | mmdb *geoip2.Reader 14 | once sync.Once 15 | ) 16 | 17 | func LoadFromBytes(buffer []byte) { 18 | once.Do(func() { 19 | var err error 20 | mmdb, err = geoip2.FromBytes(buffer) 21 | if err != nil { 22 | log.Fatalln("Can't load mmdb: %s", err.Error()) 23 | } 24 | }) 25 | } 26 | 27 | func Verify() bool { 28 | instance, err := geoip2.Open(C.Path.MMDB()) 29 | if err == nil { 30 | instance.Close() 31 | } 32 | return err == nil 33 | } 34 | 35 | func Instance() *geoip2.Reader { 36 | once.Do(func() { 37 | var err error 38 | mmdb, err = geoip2.Open(C.Path.MMDB()) 39 | if err != nil { 40 | log.Fatalln("Can't load mmdb: %s", err.Error()) 41 | } 42 | }) 43 | 44 | return mmdb 45 | } 46 | -------------------------------------------------------------------------------- /component/nat/table.go: -------------------------------------------------------------------------------- 1 | package nat 2 | 3 | import ( 4 | "sync" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Table struct { 10 | mapping sync.Map 11 | } 12 | 13 | func (t *Table) Set(key string, pc C.PacketConn) { 14 | t.mapping.Store(key, pc) 15 | } 16 | 17 | func (t *Table) Get(key string) C.PacketConn { 18 | item, exist := t.mapping.Load(key) 19 | if !exist { 20 | return nil 21 | } 22 | return item.(C.PacketConn) 23 | } 24 | 25 | func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { 26 | item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{})) 27 | return item.(*sync.Cond), loaded 28 | } 29 | 30 | func (t *Table) Delete(key string) { 31 | t.mapping.Delete(key) 32 | } 33 | 34 | // New return *Cache 35 | func New() *Table { 36 | return &Table{} 37 | } 38 | -------------------------------------------------------------------------------- /component/pool/pool_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func lg() Factory { 12 | initial := -1 13 | return func(context.Context) (any, error) { 14 | initial++ 15 | return initial, nil 16 | } 17 | } 18 | 19 | func TestPool_Basic(t *testing.T) { 20 | g := lg() 21 | pool := New(g) 22 | 23 | elm, _ := pool.Get() 24 | assert.Equal(t, 0, elm.(int)) 25 | pool.Put(elm) 26 | elm, _ = pool.Get() 27 | assert.Equal(t, 0, elm.(int)) 28 | elm, _ = pool.Get() 29 | assert.Equal(t, 1, elm.(int)) 30 | } 31 | 32 | func TestPool_MaxSize(t *testing.T) { 33 | g := lg() 34 | size := 5 35 | pool := New(g, WithSize(size)) 36 | 37 | items := []any{} 38 | 39 | for i := 0; i < size; i++ { 40 | item, _ := pool.Get() 41 | items = append(items, item) 42 | } 43 | 44 | extra, _ := pool.Get() 45 | assert.Equal(t, size, extra.(int)) 46 | 47 | for _, item := range items { 48 | pool.Put(item) 49 | } 50 | 51 | pool.Put(extra) 52 | 53 | for _, item := range items { 54 | elm, _ := pool.Get() 55 | assert.Equal(t, item.(int), elm.(int)) 56 | } 57 | } 58 | 59 | func TestPool_MaxAge(t *testing.T) { 60 | g := lg() 61 | pool := New(g, WithAge(20)) 62 | 63 | elm, _ := pool.Get() 64 | pool.Put(elm) 65 | 66 | elm, _ = pool.Get() 67 | assert.Equal(t, 0, elm.(int)) 68 | pool.Put(elm) 69 | 70 | time.Sleep(time.Millisecond * 22) 71 | elm, _ = pool.Get() 72 | assert.Equal(t, 1, elm.(int)) 73 | } 74 | -------------------------------------------------------------------------------- /component/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "errors" 5 | "net/netip" 6 | ) 7 | 8 | var ( 9 | ErrInvalidNetwork = errors.New("invalid network") 10 | ErrPlatformNotSupport = errors.New("not support on this platform") 11 | ErrNotFound = errors.New("process not found") 12 | ) 13 | 14 | const ( 15 | TCP = "tcp" 16 | UDP = "udp" 17 | ) 18 | 19 | func FindProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) { 20 | return findProcessPath(network, from, to) 21 | } 22 | -------------------------------------------------------------------------------- /component/process/process_freebsd_test.go: -------------------------------------------------------------------------------- 1 | //go:build freebsd 2 | 3 | package process 4 | 5 | import ( 6 | "testing" 7 | "unsafe" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestEnforceStructValid12(t *testing.T) { 13 | if majorVersion != 12 && majorVersion != 13 { 14 | t.Skipf("Unsupported freebsd version: %d", majorVersion) 15 | 16 | return 17 | } 18 | 19 | assert.Equal(t, 0, int(unsafe.Offsetof(XTcpcb12{}.Len))) 20 | assert.Equal(t, 24, int(unsafe.Offsetof(XTcpcb12{}.SocketAddr))) 21 | assert.Equal(t, 116, int(unsafe.Offsetof(XTcpcb12{}.Family))) 22 | assert.Equal(t, 260, int(unsafe.Offsetof(XTcpcb12{}.InEndpoints))) 23 | assert.Equal(t, 0, int(unsafe.Offsetof(XInpcb12{}.Len))) 24 | assert.Equal(t, 16, int(unsafe.Offsetof(XInpcb12{}.SocketAddr))) 25 | assert.Equal(t, 108, int(unsafe.Offsetof(XInpcb12{}.Family))) 26 | assert.Equal(t, 252, int(unsafe.Offsetof(XInpcb12{}.InEndpoints))) 27 | assert.Equal(t, 0, int(unsafe.Offsetof(XFile12{}.Size))) 28 | assert.Equal(t, 8, int(unsafe.Offsetof(XFile12{}.Pid))) 29 | assert.Equal(t, 56, int(unsafe.Offsetof(XFile12{}.DataAddr))) 30 | assert.Equal(t, 64, int(unsafe.Sizeof(Xinpgen12{}))) 31 | assert.Equal(t, 744, int(unsafe.Sizeof(XTcpcb12{}))) 32 | assert.Equal(t, 400, int(unsafe.Sizeof(XInpcb12{}))) 33 | assert.Equal(t, 40, int(unsafe.Sizeof(InEndpoints12{}))) 34 | assert.Equal(t, 128, int(unsafe.Sizeof(XFile12{}))) 35 | } 36 | -------------------------------------------------------------------------------- /component/process/process_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux && !windows && !freebsd 2 | 3 | package process 4 | 5 | import ( 6 | "net/netip" 7 | ) 8 | 9 | func findProcessPath(_ string, _, _ netip.AddrPort) (string, error) { 10 | return "", ErrPlatformNotSupport 11 | } 12 | -------------------------------------------------------------------------------- /component/profile/profile.go: -------------------------------------------------------------------------------- 1 | package profile 2 | 3 | import ( 4 | "go.uber.org/atomic" 5 | ) 6 | 7 | // StoreSelected is a global switch for storing selected proxy to cache 8 | var StoreSelected = atomic.NewBool(true) 9 | -------------------------------------------------------------------------------- /component/resolver/defaults.go: -------------------------------------------------------------------------------- 1 | //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 2 | 3 | package resolver 4 | 5 | import _ "unsafe" 6 | 7 | //go:linkname defaultNS net.defaultNS 8 | var defaultNS []string 9 | 10 | func init() { 11 | defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"} 12 | } 13 | -------------------------------------------------------------------------------- /component/resolver/enhancer.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | var DefaultHostMapper Enhancer 8 | 9 | type Enhancer interface { 10 | FakeIPEnabled() bool 11 | MappingEnabled() bool 12 | IsFakeIP(net.IP) bool 13 | IsExistFakeIP(net.IP) bool 14 | FindHostByIP(net.IP) (string, bool) 15 | } 16 | 17 | func FakeIPEnabled() bool { 18 | if mapper := DefaultHostMapper; mapper != nil { 19 | return mapper.FakeIPEnabled() 20 | } 21 | 22 | return false 23 | } 24 | 25 | func MappingEnabled() bool { 26 | if mapper := DefaultHostMapper; mapper != nil { 27 | return mapper.MappingEnabled() 28 | } 29 | 30 | return false 31 | } 32 | 33 | func IsFakeIP(ip net.IP) bool { 34 | if mapper := DefaultHostMapper; mapper != nil { 35 | return mapper.IsFakeIP(ip) 36 | } 37 | 38 | return false 39 | } 40 | 41 | func IsExistFakeIP(ip net.IP) bool { 42 | if mapper := DefaultHostMapper; mapper != nil { 43 | return mapper.IsExistFakeIP(ip) 44 | } 45 | 46 | return false 47 | } 48 | 49 | func FindHostByIP(ip net.IP) (string, bool) { 50 | if mapper := DefaultHostMapper; mapper != nil { 51 | return mapper.FindHostByIP(ip) 52 | } 53 | 54 | return "", false 55 | } 56 | -------------------------------------------------------------------------------- /component/trie/node.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | // Node is the trie's node 4 | type Node struct { 5 | children map[string]*Node 6 | Data any 7 | } 8 | 9 | func (n *Node) getChild(s string) *Node { 10 | return n.children[s] 11 | } 12 | 13 | func (n *Node) hasChild(s string) bool { 14 | return n.getChild(s) != nil 15 | } 16 | 17 | func (n *Node) addChild(s string, child *Node) { 18 | n.children[s] = child 19 | } 20 | 21 | func newNode(data any) *Node { 22 | return &Node{ 23 | Data: data, 24 | children: map[string]*Node{}, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/initial.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/Dreamacro/clash/component/mmdb" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | ) 13 | 14 | func downloadMMDB(path string) (err error) { 15 | resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb") 16 | if err != nil { 17 | return 18 | } 19 | defer resp.Body.Close() 20 | 21 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) 22 | if err != nil { 23 | return err 24 | } 25 | defer f.Close() 26 | _, err = io.Copy(f, resp.Body) 27 | 28 | return err 29 | } 30 | 31 | func initMMDB() error { 32 | if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { 33 | log.Infoln("Can't find MMDB, start download") 34 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 35 | return fmt.Errorf("can't download MMDB: %s", err.Error()) 36 | } 37 | } 38 | 39 | if !mmdb.Verify() { 40 | log.Warnln("MMDB invalid, remove and download") 41 | if err := os.Remove(C.Path.MMDB()); err != nil { 42 | return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) 43 | } 44 | 45 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 46 | return fmt.Errorf("can't download MMDB: %s", err.Error()) 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // Init prepare necessary files 54 | func Init(dir string) error { 55 | // initial homedir 56 | if _, err := os.Stat(dir); os.IsNotExist(err) { 57 | if err := os.MkdirAll(dir, 0o777); err != nil { 58 | return fmt.Errorf("can't create config directory %s: %s", dir, err.Error()) 59 | } 60 | } 61 | 62 | // initial config.yaml 63 | if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { 64 | log.Infoln("Can't find config, create a initial config file") 65 | f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644) 66 | if err != nil { 67 | return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) 68 | } 69 | f.Write([]byte(`mixed-port: 7890`)) 70 | f.Close() 71 | } 72 | 73 | // initial mmdb 74 | if err := initMMDB(); err != nil { 75 | return fmt.Errorf("can't initial MMDB: %w", err) 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /constant/context.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/gofrs/uuid/v5" 7 | ) 8 | 9 | type PlainContext interface { 10 | ID() uuid.UUID 11 | } 12 | 13 | type ConnContext interface { 14 | PlainContext 15 | Metadata() *Metadata 16 | Conn() net.Conn 17 | } 18 | 19 | type PacketConnContext interface { 20 | PlainContext 21 | Metadata() *Metadata 22 | PacketConn() net.PacketConn 23 | } 24 | -------------------------------------------------------------------------------- /constant/dns.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | // DNSModeMapping is a mapping for EnhancedMode enum 10 | var DNSModeMapping = map[string]DNSMode{ 11 | DNSNormal.String(): DNSNormal, 12 | DNSFakeIP.String(): DNSFakeIP, 13 | } 14 | 15 | const ( 16 | DNSNormal DNSMode = iota 17 | DNSFakeIP 18 | DNSMapping 19 | ) 20 | 21 | type DNSMode int 22 | 23 | // UnmarshalYAML unserialize EnhancedMode with yaml 24 | func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error { 25 | var tp string 26 | if err := unmarshal(&tp); err != nil { 27 | return err 28 | } 29 | mode, exist := DNSModeMapping[tp] 30 | if !exist { 31 | return fmt.Errorf("invalid mode: %s", tp) 32 | } 33 | *e = mode 34 | return nil 35 | } 36 | 37 | // MarshalYAML serialize EnhancedMode with yaml 38 | func (e DNSMode) MarshalYAML() (any, error) { 39 | return e.String(), nil 40 | } 41 | 42 | // UnmarshalJSON unserialize EnhancedMode with json 43 | func (e *DNSMode) UnmarshalJSON(data []byte) error { 44 | var tp string 45 | json.Unmarshal(data, &tp) 46 | mode, exist := DNSModeMapping[tp] 47 | if !exist { 48 | return errors.New("invalid mode") 49 | } 50 | *e = mode 51 | return nil 52 | } 53 | 54 | // MarshalJSON serialize EnhancedMode with json 55 | func (e DNSMode) MarshalJSON() ([]byte, error) { 56 | return json.Marshal(e.String()) 57 | } 58 | 59 | func (e DNSMode) String() string { 60 | switch e { 61 | case DNSNormal: 62 | return "normal" 63 | case DNSFakeIP: 64 | return "fake-ip" 65 | case DNSMapping: 66 | return "redir-host" 67 | default: 68 | return "unknown" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /constant/listener.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | type Listener interface { 4 | RawAddress() string 5 | Address() string 6 | Close() error 7 | } 8 | -------------------------------------------------------------------------------- /constant/path.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "os" 5 | P "path" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | const Name = "clash" 11 | 12 | // Path is used to get the configuration path 13 | var Path = func() *path { 14 | homeDir, err := os.UserHomeDir() 15 | if err != nil { 16 | homeDir, _ = os.Getwd() 17 | } 18 | 19 | homeDir = P.Join(homeDir, ".config", Name) 20 | return &path{homeDir: homeDir, configFile: "config.yaml"} 21 | }() 22 | 23 | type path struct { 24 | homeDir string 25 | configFile string 26 | } 27 | 28 | // SetHomeDir is used to set the configuration path 29 | func SetHomeDir(root string) { 30 | Path.homeDir = root 31 | } 32 | 33 | // SetConfig is used to set the configuration file 34 | func SetConfig(file string) { 35 | Path.configFile = file 36 | } 37 | 38 | func (p *path) HomeDir() string { 39 | return p.homeDir 40 | } 41 | 42 | func (p *path) Config() string { 43 | return p.configFile 44 | } 45 | 46 | // Resolve return a absolute path or a relative path with homedir 47 | func (p *path) Resolve(path string) string { 48 | if !filepath.IsAbs(path) { 49 | return filepath.Join(p.HomeDir(), path) 50 | } 51 | 52 | return path 53 | } 54 | 55 | // IsSubPath return true if path is a subpath of homedir 56 | func (p *path) IsSubPath(path string) bool { 57 | homedir := p.HomeDir() 58 | path = p.Resolve(path) 59 | rel, err := filepath.Rel(homedir, path) 60 | if err != nil { 61 | return false 62 | } 63 | 64 | return !strings.Contains(rel, "..") 65 | } 66 | 67 | func (p *path) MMDB() string { 68 | return P.Join(p.homeDir, "Country.mmdb") 69 | } 70 | 71 | func (p *path) OldCache() string { 72 | return P.Join(p.homeDir, ".cache") 73 | } 74 | 75 | func (p *path) Cache() string { 76 | return P.Join(p.homeDir, "cache.db") 77 | } 78 | -------------------------------------------------------------------------------- /constant/provider/interface.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | // Vehicle Type 8 | const ( 9 | File VehicleType = iota 10 | HTTP 11 | Compatible 12 | ) 13 | 14 | // VehicleType defined 15 | type VehicleType int 16 | 17 | func (v VehicleType) String() string { 18 | switch v { 19 | case File: 20 | return "File" 21 | case HTTP: 22 | return "HTTP" 23 | case Compatible: 24 | return "Compatible" 25 | default: 26 | return "Unknown" 27 | } 28 | } 29 | 30 | type Vehicle interface { 31 | Read() ([]byte, error) 32 | Path() string 33 | Type() VehicleType 34 | } 35 | 36 | // Provider Type 37 | const ( 38 | Proxy ProviderType = iota 39 | Rule 40 | ) 41 | 42 | // ProviderType defined 43 | type ProviderType int 44 | 45 | func (pt ProviderType) String() string { 46 | switch pt { 47 | case Proxy: 48 | return "Proxy" 49 | case Rule: 50 | return "Rule" 51 | default: 52 | return "Unknown" 53 | } 54 | } 55 | 56 | // Provider interface 57 | type Provider interface { 58 | Name() string 59 | VehicleType() VehicleType 60 | Type() ProviderType 61 | Initial() error 62 | Update() error 63 | } 64 | 65 | // ProxyProvider interface 66 | type ProxyProvider interface { 67 | Provider 68 | Proxies() []constant.Proxy 69 | // Touch is used to inform the provider that the proxy is actually being used while getting the list of proxies. 70 | // Commonly used in DialContext and DialPacketConn 71 | Touch() 72 | HealthCheck() 73 | } 74 | 75 | // Rule Type 76 | const ( 77 | Domain RuleType = iota 78 | IPCIDR 79 | Classical 80 | ) 81 | 82 | // RuleType defined 83 | type RuleType int 84 | 85 | func (rt RuleType) String() string { 86 | switch rt { 87 | case Domain: 88 | return "Domain" 89 | case IPCIDR: 90 | return "IPCIDR" 91 | case Classical: 92 | return "Classical" 93 | default: 94 | return "Unknown" 95 | } 96 | } 97 | 98 | // RuleProvider interface 99 | type RuleProvider interface { 100 | Provider 101 | Behavior() RuleType 102 | Match(*constant.Metadata) bool 103 | ShouldResolveIP() bool 104 | AsRule(adaptor string) constant.Rule 105 | } 106 | -------------------------------------------------------------------------------- /constant/rule.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // Rule Type 4 | const ( 5 | Domain RuleType = iota 6 | DomainSuffix 7 | DomainKeyword 8 | GEOIP 9 | IPCIDR 10 | SrcIPCIDR 11 | SrcPort 12 | DstPort 13 | Process 14 | ProcessPath 15 | IPSet 16 | MATCH 17 | ) 18 | 19 | type RuleType int 20 | 21 | func (rt RuleType) String() string { 22 | switch rt { 23 | case Domain: 24 | return "Domain" 25 | case DomainSuffix: 26 | return "DomainSuffix" 27 | case DomainKeyword: 28 | return "DomainKeyword" 29 | case GEOIP: 30 | return "GeoIP" 31 | case IPCIDR: 32 | return "IPCIDR" 33 | case SrcIPCIDR: 34 | return "SrcIPCIDR" 35 | case SrcPort: 36 | return "SrcPort" 37 | case DstPort: 38 | return "DstPort" 39 | case Process: 40 | return "Process" 41 | case ProcessPath: 42 | return "ProcessPath" 43 | case IPSet: 44 | return "IPSet" 45 | case MATCH: 46 | return "Match" 47 | default: 48 | return "Unknown" 49 | } 50 | } 51 | 52 | type Rule interface { 53 | RuleType() RuleType 54 | Match(metadata *Metadata) bool 55 | Adapter() string 56 | Payload() string 57 | ShouldResolveIP() bool 58 | ShouldFindProcess() bool 59 | } 60 | -------------------------------------------------------------------------------- /constant/version.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var ( 4 | Version = "unknown version" 5 | BuildTime = "unknown time" 6 | ) 7 | -------------------------------------------------------------------------------- /context/conn.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | 8 | "github.com/gofrs/uuid/v5" 9 | ) 10 | 11 | type ConnContext struct { 12 | id uuid.UUID 13 | metadata *C.Metadata 14 | conn net.Conn 15 | } 16 | 17 | func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { 18 | id, _ := uuid.NewV4() 19 | return &ConnContext{ 20 | id: id, 21 | metadata: metadata, 22 | conn: conn, 23 | } 24 | } 25 | 26 | // ID implement C.ConnContext ID 27 | func (c *ConnContext) ID() uuid.UUID { 28 | return c.id 29 | } 30 | 31 | // Metadata implement C.ConnContext Metadata 32 | func (c *ConnContext) Metadata() *C.Metadata { 33 | return c.metadata 34 | } 35 | 36 | // Conn implement C.ConnContext Conn 37 | func (c *ConnContext) Conn() net.Conn { 38 | return c.conn 39 | } 40 | -------------------------------------------------------------------------------- /context/dns.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "github.com/gofrs/uuid/v5" 5 | "github.com/miekg/dns" 6 | ) 7 | 8 | const ( 9 | DNSTypeHost = "host" 10 | DNSTypeFakeIP = "fakeip" 11 | DNSTypeRaw = "raw" 12 | ) 13 | 14 | type DNSContext struct { 15 | id uuid.UUID 16 | msg *dns.Msg 17 | tp string 18 | } 19 | 20 | func NewDNSContext(msg *dns.Msg) *DNSContext { 21 | id, _ := uuid.NewV4() 22 | return &DNSContext{ 23 | id: id, 24 | msg: msg, 25 | } 26 | } 27 | 28 | // ID implement C.PlainContext ID 29 | func (c *DNSContext) ID() uuid.UUID { 30 | return c.id 31 | } 32 | 33 | // SetType set type of response 34 | func (c *DNSContext) SetType(tp string) { 35 | c.tp = tp 36 | } 37 | 38 | // Type return type of response 39 | func (c *DNSContext) Type() string { 40 | return c.tp 41 | } 42 | -------------------------------------------------------------------------------- /context/packetconn.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | 8 | "github.com/gofrs/uuid/v5" 9 | ) 10 | 11 | type PacketConnContext struct { 12 | id uuid.UUID 13 | metadata *C.Metadata 14 | packetConn net.PacketConn 15 | } 16 | 17 | func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext { 18 | id, _ := uuid.NewV4() 19 | return &PacketConnContext{ 20 | id: id, 21 | metadata: metadata, 22 | } 23 | } 24 | 25 | // ID implement C.PacketConnContext ID 26 | func (pc *PacketConnContext) ID() uuid.UUID { 27 | return pc.id 28 | } 29 | 30 | // Metadata implement C.PacketConnContext Metadata 31 | func (pc *PacketConnContext) Metadata() *C.Metadata { 32 | return pc.metadata 33 | } 34 | 35 | // PacketConn implement C.PacketConnContext PacketConn 36 | func (pc *PacketConnContext) PacketConn() net.PacketConn { 37 | return pc.packetConn 38 | } 39 | 40 | // InjectPacketConn injectPacketConn manually 41 | func (pc *PacketConnContext) InjectPacketConn(pconn C.PacketConn) { 42 | pc.packetConn = pconn 43 | } 44 | -------------------------------------------------------------------------------- /dns/enhancer.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/cache" 7 | "github.com/Dreamacro/clash/component/fakeip" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type ResolverEnhancer struct { 12 | mode C.DNSMode 13 | fakePool *fakeip.Pool 14 | mapping *cache.LruCache 15 | } 16 | 17 | func (h *ResolverEnhancer) FakeIPEnabled() bool { 18 | return h.mode == C.DNSFakeIP 19 | } 20 | 21 | func (h *ResolverEnhancer) MappingEnabled() bool { 22 | return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping 23 | } 24 | 25 | func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool { 26 | if !h.FakeIPEnabled() { 27 | return false 28 | } 29 | 30 | if pool := h.fakePool; pool != nil { 31 | return pool.Exist(ip) 32 | } 33 | 34 | return false 35 | } 36 | 37 | func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { 38 | if !h.FakeIPEnabled() { 39 | return false 40 | } 41 | 42 | if pool := h.fakePool; pool != nil { 43 | return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(ip) 44 | } 45 | 46 | return false 47 | } 48 | 49 | func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { 50 | if pool := h.fakePool; pool != nil { 51 | if host, existed := pool.LookBack(ip); existed { 52 | return host, true 53 | } 54 | } 55 | 56 | if mapping := h.mapping; mapping != nil { 57 | if host, existed := h.mapping.Get(ip.String()); existed { 58 | return host.(string), true 59 | } 60 | } 61 | 62 | return "", false 63 | } 64 | 65 | func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { 66 | if h.mapping != nil && o.mapping != nil { 67 | o.mapping.CloneTo(h.mapping) 68 | } 69 | 70 | if h.fakePool != nil && o.fakePool != nil { 71 | h.fakePool.CloneFrom(o.fakePool) 72 | } 73 | } 74 | 75 | func NewEnhancer(cfg Config) *ResolverEnhancer { 76 | var fakePool *fakeip.Pool 77 | var mapping *cache.LruCache 78 | 79 | if cfg.EnhancedMode != C.DNSNormal { 80 | fakePool = cfg.Pool 81 | mapping = cache.New(cache.WithSize(4096), cache.WithStale(true)) 82 | } 83 | 84 | return &ResolverEnhancer{ 85 | mode: cfg.EnhancedMode, 86 | fakePool: fakePool, 87 | mapping: mapping, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dns/filters.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/Dreamacro/clash/component/mmdb" 8 | "github.com/Dreamacro/clash/component/trie" 9 | ) 10 | 11 | type fallbackIPFilter interface { 12 | Match(net.IP) bool 13 | } 14 | 15 | type geoipFilter struct { 16 | code string 17 | } 18 | 19 | func (gf *geoipFilter) Match(ip net.IP) bool { 20 | record, _ := mmdb.Instance().Country(ip) 21 | return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate() 22 | } 23 | 24 | type ipnetFilter struct { 25 | ipnet *net.IPNet 26 | } 27 | 28 | func (inf *ipnetFilter) Match(ip net.IP) bool { 29 | return inf.ipnet.Contains(ip) 30 | } 31 | 32 | type fallbackDomainFilter interface { 33 | Match(domain string) bool 34 | } 35 | 36 | type domainFilter struct { 37 | tree *trie.DomainTrie 38 | } 39 | 40 | func NewDomainFilter(domains []string) *domainFilter { 41 | df := domainFilter{tree: trie.New()} 42 | for _, domain := range domains { 43 | df.tree.Insert(domain, "") 44 | } 45 | return &df 46 | } 47 | 48 | func (df *domainFilter) Match(domain string) bool { 49 | return df.tree.Search(domain) != nil 50 | } 51 | -------------------------------------------------------------------------------- /docs/advanced-usages/golang-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Integrating Clash in Golang Programs 3 | sidebarOrder: 3 4 | --- 5 | 6 | # Integrating Clash in Golang Programs 7 | 8 | If clash does not fit your own usage, you can use Clash in your own Golang code. 9 | 10 | There is already basic support: 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "context" 17 | "fmt" 18 | "io" 19 | "net" 20 | 21 | "github.com/Dreamacro/clash/adapter/outbound" 22 | "github.com/Dreamacro/clash/constant" 23 | "github.com/Dreamacro/clash/listener/socks" 24 | ) 25 | 26 | func main() { 27 | in := make(chan constant.ConnContext, 100) 28 | defer close(in) 29 | 30 | l, err := socks.New("127.0.0.1:10000", in) 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer l.Close() 35 | 36 | println("listen at:", l.Address()) 37 | 38 | direct := outbound.NewDirect() 39 | 40 | for c := range in { 41 | conn := c 42 | metadata := conn.Metadata() 43 | fmt.Printf("request incoming from %s to %s\n", metadata.SourceAddress(), metadata.RemoteAddress()) 44 | go func () { 45 | remote, err := direct.DialContext(context.Background(), metadata) 46 | if err != nil { 47 | fmt.Printf("dial error: %s\n", err.Error()) 48 | return 49 | } 50 | relay(remote, conn.Conn()) 51 | }() 52 | } 53 | } 54 | 55 | func relay(l, r net.Conn) { 56 | go io.Copy(l, r) 57 | io.Copy(r, l) 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/advanced-usages/wireguard.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Rule-based Wireguard 3 | sidebarOrder: 1 4 | --- 5 | 6 | # Rule-based Wireguard 7 | 8 | Suppose your kernel supports Wireguard and you have it enabled. The `Table` option stops _wg-quick_ from overriding default routes. 9 | 10 | Example `wg0.conf`: 11 | 12 | ```ini 13 | [Interface] 14 | PrivateKey = ... 15 | Address = 172.16.0.1/32 16 | MTU = ... 17 | Table = off 18 | PostUp = ip rule add from 172.16.0.1/32 table 6666 19 | 20 | [Peer] 21 | AllowedIPs = 0.0.0.0/0 22 | AllowedIPs = ::/0 23 | PublicKey = ... 24 | Endpoint = ... 25 | ``` 26 | 27 | Then in Clash you would only need to have a DIRECT proxy group that has a specific outbound interface: 28 | 29 | ```yaml 30 | proxy-groups: 31 | - name: Wireguard 32 | type: select 33 | interface-name: wg0 34 | proxies: 35 | - DIRECT 36 | rules: 37 | - DOMAIN,google.com,Wireguard 38 | ``` 39 | 40 | This should perform better than whereas if Clash implemented its own userspace Wireguard client. Wireguard is supported in the kernel. 41 | -------------------------------------------------------------------------------- /docs/assets/connection-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comzyh/clash/2e2f6f0d2b6ed5b4f83bf5bd1f08cb5e896405f3/docs/assets/connection-flow.png -------------------------------------------------------------------------------- /docs/configuration/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Introduction 3 | sidebarOrder: 1 4 | --- 5 | 6 | # Introduction 7 | 8 | In this chapter, we'll cover the common features of Clash and how they should be used and configured. 9 | 10 | Clash uses [YAML](https://yaml.org), _YAML Ain't Markup Language_, for configuration files. YAML is designed to be easy to be read, be written, and be interpreted by computers, and is commonly used for exact configuration files. 11 | 12 | ## Understanding how Clash works 13 | 14 | Before proceeding, it's important to understand how Clash works, in which there are two critical components: 15 | 16 | ![](/assets/connection-flow.png) 17 | 18 | 19 | 20 | ### Inbound 21 | 22 | Inbound is the component that listens on the local end. It works by opening a local port and listening for incoming connections. When a connection comes in, Clash looks up the rules that are configured in the configuration file, and decides which outbound that the connection should go next. 23 | 24 | ### Outbound 25 | 26 | Outbound is the component that connects to the remote end. Depending on the configuration, it can be a specific network interface, a proxy server, or a [proxy group](#proxy-groups). 27 | 28 | ## Rule-based Routing 29 | 30 | Clash supports rule-based routing, which means you can route packets to different outbounds based on the a variety of contraints. The rules can be defined in the `rules` section of the configuration file. 31 | 32 | There's a number of available rule types, and each rule type has its own syntax. The general syntax of a rule is: 33 | 34 | ```txt 35 | TYPE,ARGUMENT,POLICY(,no-resolve) 36 | ``` 37 | 38 | In the upcoming guides, you will learn more about how rules can be configured. 39 | -------------------------------------------------------------------------------- /docs/introduction/_dummy-index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: What is Clash? 3 | sidebarOrder: 1 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/introduction/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Getting Started 3 | sidebarOrder: 2 4 | --- 5 | 6 | # Getting Started 7 | 8 | To get started with Clash, you can either build it from source or download pre-built binaries. 9 | 10 | ## Using pre-built binaries 11 | 12 | You can download Clash core binaries here: [https://github.com/Dreamacro/clash/releases](https://github.com/Dreamacro/clash/releases) 13 | 14 | ## Install from source 15 | 16 | You can build Clash on your own device with Golang 1.19+: 17 | 18 | ```shell 19 | $ go install github.com/Dreamacro/clash@latest 20 | go: downloading github.com/Dreamacro/clash v1.15.1 21 | ``` 22 | 23 | The binary is built under `$GOPATH/bin`: 24 | 25 | ```shell 26 | $ $GOPATH/bin/clash -v 27 | Clash unknown version darwin arm64 with go1.20.3 unknown time 28 | ``` 29 | 30 | ## Build for a different arch/os 31 | 32 | Golang supports cross-compilation, so you can build for a device on a different architecture or operating system. You can use _make_ to build them easily - for example: 33 | 34 | ```shell 35 | $ git clone --depth 1 https://github.com/Dreamacro/clash 36 | Cloning into 'clash'... 37 | remote: Enumerating objects: 359, done. 38 | remote: Counting objects: 100% (359/359), done. 39 | remote: Compressing objects: 100% (325/325), done. 40 | remote: Total 359 (delta 25), reused 232 (delta 17), pack-reused 0 41 | Receiving objects: 100% (359/359), 248.99 KiB | 1.63 MiB/s, done. 42 | Resolving deltas: 100% (25/25), done. 43 | $ cd clash && make darwin-arm64 44 | fatal: No names found, cannot describe anything. 45 | GOARCH=arm64 GOOS=darwin CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=unknown version" -X "github.com/Dreamacro/clash/constant.BuildTime=Mon May 8 16:47:10 UTC 2023" -w -s -buildid=' -o bin/clash-darwin-arm64 46 | $ file bin/clash-darwin-arm64 47 | bin/clash-darwin-arm64: Mach-O 64-bit executable arm64 48 | ``` 49 | 50 | For other build targets, check out the [Makefile](https://github.com/Dreamacro/clash/blob/master/Makefile). 51 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comzyh/clash/2e2f6f0d2b6ed5b4f83bf5bd1f08cb5e896405f3/docs/logo.png -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vitepress dev", 4 | "docs:build": "vitepress build", 5 | "docs:preview": "vitepress preview" 6 | }, 7 | "devDependencies": { 8 | "@types/node": "^20.2.1", 9 | "directory-tree": "^3.5.1", 10 | "markdown-yaml-metadata-parser": "^3.0.0", 11 | "vitepress": "^1.0.0-alpha.76" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/premium/ebpf.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "Feature: eBPF Redirect to TUN" 3 | sidebarOrder: 3 4 | --- 5 | 6 | # eBPF Redirect to TUN 7 | 8 | eBPF redirect to TUN is a feature that intercepts all network traffic on a specific network interface and redirects it to the TUN interface. [Support from your kernel](https://github.com/iovisor/bcc/blob/master/INSTALL.md#kernel-configuration) is required. 9 | 10 | ::: warning 11 | This feature conflicts with `tun.auto-route`. 12 | ::: 13 | 14 | While it usually brings better performance compared to `tun.auto-redir` and `tun.auto-route`, it's less tested compared to `auto-route`. Therefore, you should proceed with caution. 15 | 16 | ## Configuration 17 | 18 | ```yaml 19 | ebpf: 20 | redirect-to-tun: 21 | - eth0 22 | ``` 23 | 24 | ## Known Issues 25 | 26 | - This feature breaks Tailscaled, so you should use `tun.auto-route` instead. 27 | -------------------------------------------------------------------------------- /docs/premium/experimental-features.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Experimental Features 3 | sidebarOrder: 9 4 | --- 5 | 6 | # Experimental Features 7 | 8 | Occasionally we make new features that would require a fair amount of testing before having it in the main release. These features are marked as experimental and are disabled by default. 9 | 10 | ::: warning 11 | Some features listed here can be unstable, and might get removed in any future version - we do not recommend using them unless you have a specific reason to do so. 12 | ::: 13 | 14 | ## Sniff TLS SNI 15 | 16 | ```yaml 17 | experimental: 18 | sniff-tls-sni: true 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/premium/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Introduction 3 | sidebarOrder: 1 4 | --- 5 | 6 | # Introduction 7 | 8 | In the past, there was only one open-source version of Clash, until some [improper uses and redistributions](https://github.com/Dreamacro/clash/issues/541#issuecomment-672029110) of Clash arose. From that, we decided to fork Clash and develop the more advanced features in a private GitHub repository. 9 | 10 | Don't worry just yet - the Premium core will stay free of charge, and the security is enforced with peer reviews from multiple credible developers. 11 | 12 | ## What's the difference? 13 | 14 | The Premium core is a fork of the open-source Clash core with the addition of the following features: 15 | 16 | - [TUN Device](/premium/tun-device) with the support of `auto-redir` and `auto-route` 17 | - [eBPF Redirect to TUN](/premium/ebpf) 18 | - [Rule Providers](/premium/rule-providers) 19 | - [Script](/premium/script) 20 | - [Script Shortcuts](/premium/script-shortcuts) 21 | - [Userspace Wireguard](/premium/userspace-wireguard) 22 | - [The Profiling Engine](/premium/the-profiling-engine) 23 | 24 | ## Obtaining a Copy 25 | 26 | You can download the latest Clash Premium binaries from [GitHub Releases](https://github.com/Dreamacro/clash/releases/tag/premium). 27 | -------------------------------------------------------------------------------- /docs/premium/rule-providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "Feature: Rule Providers" 3 | sidebarOrder: 4 4 | --- 5 | 6 | # Rule Providers 7 | 8 | Rule Providers are pretty much the same compared to Proxy Providers. It enables users to load rules from external sources and overall cleaner configuration. This feature is currently Premium core only. 9 | 10 | To define a Rule Provider, add the `rule-providers` field to the main configuration: 11 | 12 | ```yaml 13 | rule-providers: 14 | apple: 15 | behavior: "domain" # domain, ipcidr or classical (premium core only) 16 | type: http 17 | url: "url" 18 | # format: 'yaml' # or 'text' 19 | interval: 3600 20 | path: ./apple.yaml 21 | microsoft: 22 | behavior: "domain" 23 | type: file 24 | path: /microsoft.yaml 25 | 26 | rules: 27 | - RULE-SET,apple,REJECT 28 | - RULE-SET,microsoft,policy 29 | ``` 30 | 31 | There are three behavior types available: 32 | 33 | ## `domain` 34 | 35 | yaml: 36 | 37 | ```yaml 38 | payload: 39 | - '.blogger.com' 40 | - '*.*.microsoft.com' 41 | - 'books.itunes.apple.com' 42 | ``` 43 | 44 | text: 45 | 46 | ```txt 47 | # comment 48 | .blogger.com 49 | *.*.microsoft.com 50 | books.itunes.apple.com 51 | ``` 52 | 53 | ## `ipcidr` 54 | 55 | yaml 56 | 57 | ```yaml 58 | payload: 59 | - '192.168.1.0/24' 60 | - '10.0.0.0.1/32' 61 | ``` 62 | 63 | text: 64 | 65 | ```txt 66 | # comment 67 | 192.168.1.0/24 68 | 10.0.0.0.1/32 69 | ``` 70 | 71 | ## `classical` 72 | 73 | yaml: 74 | 75 | ```yaml 76 | payload: 77 | - DOMAIN-SUFFIX,google.com 78 | - DOMAIN-KEYWORD,google 79 | - DOMAIN,ad.com 80 | - SRC-IP-CIDR,192.168.1.201/32 81 | - IP-CIDR,127.0.0.0/8 82 | - GEOIP,CN 83 | - DST-PORT,80 84 | - SRC-PORT,7777 85 | # MATCH is not necessary here 86 | ``` 87 | 88 | text: 89 | 90 | ```txt 91 | # comment 92 | DOMAIN-SUFFIX,google.com 93 | DOMAIN-KEYWORD,google 94 | DOMAIN,ad.com 95 | SRC-IP-CIDR,192.168.1.201/32 96 | IP-CIDR,127.0.0.0/8 97 | GEOIP,CN 98 | DST-PORT,80 99 | SRC-PORT,7777 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/premium/script-shortcuts.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "Feature: Script Shortcuts" 3 | sidebarOrder: 6 4 | --- 5 | 6 | # Script Shortcuts 7 | 8 | Clash Premium implements the Scripting feature powered by Python3, enableing users to programmatically select policies for the packets with dynamic flexibility. 9 | 10 | You can either controll the entire rule-matching engine with a single Python script, or define a number of shortcuts and use them in companion with the regular rules. This page refers to the latter feature. For the former, see [Script](./script.md). 11 | 12 | This feature enables the use of script in `rules` mode. By default, DNS resolution takes place for SCRIPT rules. `no-resolve` can be appended to the rule to prevent the resolution. (i.e.: `SCRIPT,quic,DIRECT,no-resolve`) 13 | 14 | ```yaml 15 | mode: Rule 16 | 17 | script: 18 | engine: expr # or starlark (10x to 20x slower) 19 | shortcuts: 20 | quic: network == 'udp' and dst_port == 443 21 | curl: resolve_process_name() == 'curl' 22 | # curl: resolve_process_path() == '/usr/bin/curl' 23 | 24 | rules: 25 | - SCRIPT,quic,REJECT 26 | ``` 27 | 28 | ## Evaluation Engines 29 | 30 | [Expr](https://expr.medv.io/) is used as the default engine for Script Shortcuts, offering 10x to 20x performance boost compared to Starlark. 31 | 32 | [Starlark](https://github.com/google/starlark-go) is a Python-like langauge for configuration purposes, you can also use it for Script Shortcuts. 33 | 34 | ## Variables 35 | 36 | - network: string 37 | - type: string 38 | - src_ip: string 39 | - dst_ip: string 40 | - src_port: uint16 41 | - dst_port: uint16 42 | - host: string 43 | - process_path: string 44 | 45 | ::: warning 46 | Starlark is missing `process_path` variable for now. 47 | ::: 48 | 49 | ## Functions 50 | 51 | ```ts 52 | type resolve_ip = (host: string) => string // ip string 53 | type in_cidr = (ip: string, cidr: string) => boolean // ip in cidr 54 | type in_ipset = (name: string, ip: string) => boolean // ip in ipset 55 | type geoip = (ip: string) => string // country code 56 | type match_provider = (name: string) => boolean // in rule provider 57 | type resolve_process_name = () => string // find process name (curl .e.g) 58 | type resolve_process_path = () => string // find process path (/usr/bin/curl .e.g) 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/premium/the-profiling-engine.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "Feature: The Profiling Engine" 3 | sidebarOrder: 8 4 | --- 5 | 6 | # The Profiling Engine 7 | 8 | https://github.com/Dreamacro/clash-tracing 9 | 10 | ```yaml 11 | profile: 12 | tracing: true 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/premium/userspace-wireguard.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "Feature: Userspace Wireguard" 3 | sidebarOrder: 7 4 | --- 5 | 6 | # Userspace Wireguard 7 | 8 | Due to the dependency on gvisor TCP/IP stack, Wireguard outbound is currently only available in the Premium core. 9 | 10 | ```yaml 11 | proxies: 12 | - name: "wg" 13 | type: wireguard 14 | server: 127.0.0.1 15 | port: 443 16 | ip: 172.16.0.2 17 | # ipv6: your_ipv6 18 | private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU= 19 | public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= 20 | # preshared-key: base64 21 | # remote-dns-resolve: true # remote resolve DNS with `dns` field, default is true 22 | # dns: [1.1.1.1, 8.8.8.8] 23 | # mtu: 1420 24 | udp: true 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- 1 | ../logo.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Dreamacro/clash 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd 7 | github.com/go-chi/chi/v5 v5.0.8 8 | github.com/go-chi/cors v1.2.1 9 | github.com/go-chi/render v1.0.2 10 | github.com/gofrs/uuid/v5 v5.0.0 11 | github.com/gorilla/websocket v1.5.0 12 | github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb 13 | github.com/mdlayher/netlink v1.7.2 14 | github.com/miekg/dns v1.1.54 15 | github.com/oschwald/geoip2-golang v1.8.0 16 | github.com/samber/lo v1.38.1 17 | github.com/sirupsen/logrus v1.9.2 18 | github.com/stretchr/testify v1.8.3 19 | github.com/vishvananda/netlink v1.2.1-beta.2.0.20230420174744-55c8b9515a01 20 | go.etcd.io/bbolt v1.3.7 21 | go.uber.org/atomic v1.11.0 22 | go.uber.org/automaxprocs v1.5.2 23 | golang.org/x/crypto v0.9.0 24 | golang.org/x/net v0.10.0 25 | golang.org/x/sync v0.2.0 26 | golang.org/x/sys v0.8.0 27 | gopkg.in/yaml.v3 v3.0.1 28 | gvisor.dev/gvisor v0.0.0-20230630184836-7b5c9449aa20 29 | ) 30 | 31 | require ( 32 | github.com/ajg/form v1.5.1 // indirect 33 | github.com/davecgh/go-spew v1.1.1 // indirect 34 | github.com/google/btree v1.0.1 // indirect 35 | github.com/google/go-cmp v0.5.9 // indirect 36 | github.com/josharian/native v1.1.0 // indirect 37 | github.com/kr/text v0.2.0 // indirect 38 | github.com/mdlayher/socket v0.4.1 // indirect 39 | github.com/oschwald/maxminddb-golang v1.10.0 // indirect 40 | github.com/pierrec/lz4/v4 v4.1.14 // indirect 41 | github.com/pmezard/go-difflib v1.0.0 // indirect 42 | github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect 43 | github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect 44 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect 45 | golang.org/x/mod v0.8.0 // indirect 46 | golang.org/x/text v0.9.0 // indirect 47 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 48 | golang.org/x/tools v0.6.0 // indirect 49 | ) 50 | -------------------------------------------------------------------------------- /hub/hub.go: -------------------------------------------------------------------------------- 1 | package hub 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/config" 5 | "github.com/Dreamacro/clash/hub/executor" 6 | "github.com/Dreamacro/clash/hub/route" 7 | ) 8 | 9 | type Option func(*config.Config) 10 | 11 | func WithExternalUI(externalUI string) Option { 12 | return func(cfg *config.Config) { 13 | cfg.General.ExternalUI = externalUI 14 | } 15 | } 16 | 17 | func WithExternalController(externalController string) Option { 18 | return func(cfg *config.Config) { 19 | cfg.General.ExternalController = externalController 20 | } 21 | } 22 | 23 | func WithSecret(secret string) Option { 24 | return func(cfg *config.Config) { 25 | cfg.General.Secret = secret 26 | } 27 | } 28 | 29 | // Parse call at the beginning of clash 30 | func Parse(options ...Option) error { 31 | cfg, err := executor.Parse() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | for _, option := range options { 37 | option(cfg) 38 | } 39 | 40 | if cfg.General.ExternalUI != "" { 41 | route.SetUIPath(cfg.General.ExternalUI) 42 | } 43 | 44 | if cfg.General.ExternalController != "" { 45 | go route.Start(cfg.General.ExternalController, cfg.General.Secret) 46 | } 47 | 48 | executor.ApplyConfig(cfg, true) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /hub/route/common.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | // When name is composed of a partial escape string, Golang does not unescape it 11 | func getEscapeParam(r *http.Request, paramName string) string { 12 | param := chi.URLParam(r, paramName) 13 | if newParam, err := url.PathUnescape(param); err == nil { 14 | param = newParam 15 | } 16 | return param 17 | } 18 | -------------------------------------------------------------------------------- /hub/route/ctxkeys.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | var ( 4 | CtxKeyProxyName = contextKey("proxy name") 5 | CtxKeyProviderName = contextKey("provider name") 6 | CtxKeyProxy = contextKey("proxy") 7 | CtxKeyProvider = contextKey("provider") 8 | ) 9 | 10 | type contextKey string 11 | 12 | func (c contextKey) String() string { 13 | return "clash context key " + string(c) 14 | } 15 | -------------------------------------------------------------------------------- /hub/route/dns.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "net/http" 7 | 8 | "github.com/Dreamacro/clash/component/resolver" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/go-chi/render" 12 | "github.com/miekg/dns" 13 | "github.com/samber/lo" 14 | ) 15 | 16 | func dnsRouter() http.Handler { 17 | r := chi.NewRouter() 18 | r.Get("/query", queryDNS) 19 | return r 20 | } 21 | 22 | func queryDNS(w http.ResponseWriter, r *http.Request) { 23 | if resolver.DefaultResolver == nil { 24 | render.Status(r, http.StatusInternalServerError) 25 | render.JSON(w, r, newError("DNS section is disabled")) 26 | return 27 | } 28 | 29 | name := r.URL.Query().Get("name") 30 | qTypeStr, _ := lo.Coalesce(r.URL.Query().Get("type"), "A") 31 | 32 | qType, exist := dns.StringToType[qTypeStr] 33 | if !exist { 34 | render.Status(r, http.StatusBadRequest) 35 | render.JSON(w, r, newError("invalid query type")) 36 | return 37 | } 38 | 39 | ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) 40 | defer cancel() 41 | 42 | msg := dns.Msg{} 43 | msg.SetQuestion(dns.Fqdn(name), qType) 44 | resp, err := resolver.DefaultResolver.ExchangeContext(ctx, &msg) 45 | if err != nil { 46 | render.Status(r, http.StatusInternalServerError) 47 | render.JSON(w, r, newError(err.Error())) 48 | return 49 | } 50 | 51 | responseData := render.M{ 52 | "Status": resp.Rcode, 53 | "Question": resp.Question, 54 | "TC": resp.Truncated, 55 | "RD": resp.RecursionDesired, 56 | "RA": resp.RecursionAvailable, 57 | "AD": resp.AuthenticatedData, 58 | "CD": resp.CheckingDisabled, 59 | } 60 | 61 | rr2Json := func(rr dns.RR, _ int) render.M { 62 | header := rr.Header() 63 | return render.M{ 64 | "name": header.Name, 65 | "type": header.Rrtype, 66 | "TTL": header.Ttl, 67 | "data": lo.Substring(rr.String(), len(header.String()), math.MaxUint), 68 | } 69 | } 70 | 71 | if len(resp.Answer) > 0 { 72 | responseData["Answer"] = lo.Map(resp.Answer, rr2Json) 73 | } 74 | if len(resp.Ns) > 0 { 75 | responseData["Authority"] = lo.Map(resp.Ns, rr2Json) 76 | } 77 | if len(resp.Extra) > 0 { 78 | responseData["Additional"] = lo.Map(resp.Extra, rr2Json) 79 | } 80 | 81 | render.JSON(w, r, responseData) 82 | } 83 | -------------------------------------------------------------------------------- /hub/route/errors.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | var ( 4 | ErrUnauthorized = newError("Unauthorized") 5 | ErrBadRequest = newError("Body invalid") 6 | ErrForbidden = newError("Forbidden") 7 | ErrNotFound = newError("Resource not found") 8 | ErrRequestTimeout = newError("Timeout") 9 | ) 10 | 11 | // HTTPError is custom HTTP error for API 12 | type HTTPError struct { 13 | Message string `json:"message"` 14 | } 15 | 16 | func (e *HTTPError) Error() string { 17 | return e.Message 18 | } 19 | 20 | func newError(msg string) *HTTPError { 21 | return &HTTPError{Message: msg} 22 | } 23 | -------------------------------------------------------------------------------- /hub/route/rules.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Dreamacro/clash/tunnel" 7 | 8 | "github.com/go-chi/chi/v5" 9 | "github.com/go-chi/render" 10 | ) 11 | 12 | func ruleRouter() http.Handler { 13 | r := chi.NewRouter() 14 | r.Get("/", getRules) 15 | return r 16 | } 17 | 18 | type Rule struct { 19 | Type string `json:"type"` 20 | Payload string `json:"payload"` 21 | Proxy string `json:"proxy"` 22 | } 23 | 24 | func getRules(w http.ResponseWriter, r *http.Request) { 25 | rawRules := tunnel.Rules() 26 | 27 | rules := []Rule{} 28 | for _, rule := range rawRules { 29 | rules = append(rules, Rule{ 30 | Type: rule.RuleType().String(), 31 | Payload: rule.Payload(), 32 | Proxy: rule.Adapter(), 33 | }) 34 | } 35 | 36 | render.JSON(w, r, render.M{ 37 | "rules": rules, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /listener/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/auth" 5 | ) 6 | 7 | var authenticator auth.Authenticator 8 | 9 | func Authenticator() auth.Authenticator { 10 | return authenticator 11 | } 12 | 13 | func SetAuthenticator(au auth.Authenticator) { 14 | authenticator = au 15 | } 16 | -------------------------------------------------------------------------------- /listener/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/Dreamacro/clash/adapter/inbound" 11 | C "github.com/Dreamacro/clash/constant" 12 | "github.com/Dreamacro/clash/transport/socks5" 13 | ) 14 | 15 | func newClient(source net.Addr, originTarget net.Addr, in chan<- C.ConnContext) *http.Client { 16 | return &http.Client{ 17 | Transport: &http.Transport{ 18 | // from http.DefaultTransport 19 | MaxIdleConns: 100, 20 | IdleConnTimeout: 90 * time.Second, 21 | TLSHandshakeTimeout: 10 * time.Second, 22 | ExpectContinueTimeout: 1 * time.Second, 23 | DialContext: func(context context.Context, network, address string) (net.Conn, error) { 24 | if network != "tcp" && network != "tcp4" && network != "tcp6" { 25 | return nil, errors.New("unsupported network " + network) 26 | } 27 | 28 | dstAddr := socks5.ParseAddr(address) 29 | if dstAddr == nil { 30 | return nil, socks5.ErrAddressNotSupported 31 | } 32 | 33 | left, right := net.Pipe() 34 | 35 | in <- inbound.NewHTTP(dstAddr, source, originTarget, right) 36 | 37 | return left, nil 38 | }, 39 | }, 40 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 41 | return http.ErrUseLastResponse 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /listener/http/hack.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "net/http" 6 | _ "unsafe" 7 | ) 8 | 9 | //go:linkname ReadRequest net/http.readRequest 10 | func ReadRequest(b *bufio.Reader) (req *http.Request, err error) 11 | -------------------------------------------------------------------------------- /listener/http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/cache" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type Listener struct { 11 | listener net.Listener 12 | addr string 13 | closed bool 14 | } 15 | 16 | // RawAddress implements C.Listener 17 | func (l *Listener) RawAddress() string { 18 | return l.addr 19 | } 20 | 21 | // Address implements C.Listener 22 | func (l *Listener) Address() string { 23 | return l.listener.Addr().String() 24 | } 25 | 26 | // Close implements C.Listener 27 | func (l *Listener) Close() error { 28 | l.closed = true 29 | return l.listener.Close() 30 | } 31 | 32 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 33 | return NewWithAuthenticate(addr, in, true) 34 | } 35 | 36 | func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) { 37 | l, err := net.Listen("tcp", addr) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | var c *cache.LruCache 43 | if authenticate { 44 | c = cache.New(cache.WithAge(30)) 45 | } 46 | 47 | hl := &Listener{ 48 | listener: l, 49 | addr: addr, 50 | } 51 | go func() { 52 | for { 53 | conn, err := hl.listener.Accept() 54 | if err != nil { 55 | if hl.closed { 56 | break 57 | } 58 | continue 59 | } 60 | go HandleConn(conn, in, c) 61 | } 62 | }() 63 | 64 | return hl, nil 65 | } 66 | -------------------------------------------------------------------------------- /listener/http/upgrade.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/adapter/inbound" 9 | N "github.com/Dreamacro/clash/common/net" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | ) 13 | 14 | func isUpgradeRequest(req *http.Request) bool { 15 | for _, header := range req.Header["Connection"] { 16 | for _, elm := range strings.Split(header, ",") { 17 | if strings.EqualFold(strings.TrimSpace(elm), "Upgrade") { 18 | return true 19 | } 20 | } 21 | } 22 | 23 | return false 24 | } 25 | 26 | func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext) { 27 | defer conn.Close() 28 | 29 | removeProxyHeaders(request.Header) 30 | removeExtraHTTPHostPort(request) 31 | 32 | address := request.Host 33 | if _, _, err := net.SplitHostPort(address); err != nil { 34 | address = net.JoinHostPort(address, "80") 35 | } 36 | 37 | dstAddr := socks5.ParseAddr(address) 38 | if dstAddr == nil { 39 | return 40 | } 41 | 42 | left, right := net.Pipe() 43 | 44 | in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), conn.LocalAddr(), right) 45 | 46 | bufferedLeft := N.NewBufferedConn(left) 47 | defer bufferedLeft.Close() 48 | 49 | err := request.Write(bufferedLeft) 50 | if err != nil { 51 | return 52 | } 53 | 54 | resp, err := http.ReadResponse(bufferedLeft.Reader(), request) 55 | if err != nil { 56 | return 57 | } 58 | 59 | removeProxyHeaders(resp.Header) 60 | 61 | err = resp.Write(conn) 62 | if err != nil { 63 | return 64 | } 65 | 66 | if resp.StatusCode == http.StatusSwitchingProtocols { 67 | N.Relay(bufferedLeft, conn) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /listener/mixed/mixed.go: -------------------------------------------------------------------------------- 1 | package mixed 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/cache" 7 | N "github.com/Dreamacro/clash/common/net" 8 | C "github.com/Dreamacro/clash/constant" 9 | "github.com/Dreamacro/clash/listener/http" 10 | "github.com/Dreamacro/clash/listener/socks" 11 | "github.com/Dreamacro/clash/transport/socks4" 12 | "github.com/Dreamacro/clash/transport/socks5" 13 | ) 14 | 15 | type Listener struct { 16 | listener net.Listener 17 | addr string 18 | cache *cache.LruCache 19 | closed bool 20 | } 21 | 22 | // RawAddress implements C.Listener 23 | func (l *Listener) RawAddress() string { 24 | return l.addr 25 | } 26 | 27 | // Address implements C.Listener 28 | func (l *Listener) Address() string { 29 | return l.listener.Addr().String() 30 | } 31 | 32 | // Close implements C.Listener 33 | func (l *Listener) Close() error { 34 | l.closed = true 35 | return l.listener.Close() 36 | } 37 | 38 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 39 | l, err := net.Listen("tcp", addr) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | ml := &Listener{ 45 | listener: l, 46 | addr: addr, 47 | cache: cache.New(cache.WithAge(30)), 48 | } 49 | go func() { 50 | for { 51 | c, err := ml.listener.Accept() 52 | if err != nil { 53 | if ml.closed { 54 | break 55 | } 56 | continue 57 | } 58 | go handleConn(c, in, ml.cache) 59 | } 60 | }() 61 | 62 | return ml, nil 63 | } 64 | 65 | func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache) { 66 | conn.(*net.TCPConn).SetKeepAlive(true) 67 | 68 | bufConn := N.NewBufferedConn(conn) 69 | head, err := bufConn.Peek(1) 70 | if err != nil { 71 | return 72 | } 73 | 74 | switch head[0] { 75 | case socks4.Version: 76 | socks.HandleSocks4(bufConn, in) 77 | case socks5.Version: 78 | socks.HandleSocks5(bufConn, in) 79 | default: 80 | http.HandleConn(bufConn, in, cache) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /listener/redir/tcp.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type Listener struct { 11 | listener net.Listener 12 | addr string 13 | closed bool 14 | } 15 | 16 | // RawAddress implements C.Listener 17 | func (l *Listener) RawAddress() string { 18 | return l.addr 19 | } 20 | 21 | // Address implements C.Listener 22 | func (l *Listener) Address() string { 23 | return l.listener.Addr().String() 24 | } 25 | 26 | // Close implements C.Listener 27 | func (l *Listener) Close() error { 28 | l.closed = true 29 | return l.listener.Close() 30 | } 31 | 32 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 33 | l, err := net.Listen("tcp", addr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | rl := &Listener{ 38 | listener: l, 39 | addr: addr, 40 | } 41 | 42 | go func() { 43 | for { 44 | c, err := l.Accept() 45 | if err != nil { 46 | if rl.closed { 47 | break 48 | } 49 | continue 50 | } 51 | go handleRedir(c, in) 52 | } 53 | }() 54 | 55 | return rl, nil 56 | } 57 | 58 | func handleRedir(conn net.Conn, in chan<- C.ConnContext) { 59 | target, err := parserPacket(conn) 60 | if err != nil { 61 | conn.Close() 62 | return 63 | } 64 | conn.(*net.TCPConn).SetKeepAlive(true) 65 | in <- inbound.NewSocket(target, conn, C.REDIR) 66 | } 67 | -------------------------------------------------------------------------------- /listener/redir/tcp_darwin.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | "unsafe" 7 | 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | func parserPacket(c net.Conn) (socks5.Addr, error) { 12 | const ( 13 | PfInout = 0 14 | PfIn = 1 15 | PfOut = 2 16 | IOCOut = 0x40000000 17 | IOCIn = 0x80000000 18 | IOCInOut = IOCIn | IOCOut 19 | IOCPARMMask = 0x1FFF 20 | LEN = 4*16 + 4*4 + 4*1 21 | // #define _IOC(inout,group,num,len) (inout | ((len & IOCPARMMask) << 16) | ((group) << 8) | (num)) 22 | // #define _IOWR(g,n,t) _IOC(IOCInOut, (g), (n), sizeof(t)) 23 | // #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) 24 | DIOCNATLOOK = IOCInOut | ((LEN & IOCPARMMask) << 16) | ('D' << 8) | 23 25 | ) 26 | 27 | fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) 28 | if err != nil { 29 | return nil, err 30 | } 31 | defer syscall.Close(fd) 32 | 33 | nl := struct { // struct pfioc_natlook 34 | saddr, daddr, rsaddr, rdaddr [16]byte 35 | sxport, dxport, rsxport, rdxport [4]byte 36 | af, proto, protoVariant, direction uint8 37 | }{ 38 | af: syscall.AF_INET, 39 | proto: syscall.IPPROTO_TCP, 40 | direction: PfOut, 41 | } 42 | saddr := c.RemoteAddr().(*net.TCPAddr) 43 | daddr := c.LocalAddr().(*net.TCPAddr) 44 | copy(nl.saddr[:], saddr.IP) 45 | copy(nl.daddr[:], daddr.IP) 46 | nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port) 47 | nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) 48 | 49 | if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { 50 | return nil, errno 51 | } 52 | 53 | addr := make([]byte, 1+net.IPv4len+2) 54 | addr[0] = socks5.AtypIPv4 55 | copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) 56 | copy(addr[1+net.IPv4len:], nl.rdxport[:2]) 57 | return addr, nil 58 | } 59 | -------------------------------------------------------------------------------- /listener/redir/tcp_freebsd.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "net" 7 | "net/netip" 8 | "syscall" 9 | "unsafe" 10 | 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | const ( 17 | SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h 18 | IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h 19 | ) 20 | 21 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 22 | c, ok := conn.(*net.TCPConn) 23 | if !ok { 24 | return nil, errors.New("only work with TCP connection") 25 | } 26 | 27 | rc, err := c.SyscallConn() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | var addr netip.AddrPort 33 | 34 | rc.Control(func(fd uintptr) { 35 | if ip4 := c.LocalAddr().(*net.TCPAddr).IP.To4(); ip4 != nil { 36 | addr, err = getorigdst(fd) 37 | } else { 38 | addr, err = getorigdst6(fd) 39 | } 40 | }) 41 | 42 | return socks5.AddrFromStdAddrPort(addr), err 43 | } 44 | 45 | // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c 46 | func getorigdst(fd uintptr) (netip.AddrPort, error) { 47 | addr := unix.RawSockaddrInet4{} 48 | size := uint32(unsafe.Sizeof(addr)) 49 | _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0) 50 | if err != 0 { 51 | return netip.AddrPort{}, err 52 | } 53 | port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:]) 54 | return netip.AddrPortFrom(netip.AddrFrom4(addr.Addr), port), nil 55 | } 56 | 57 | func getorigdst6(fd uintptr) (netip.AddrPort, error) { 58 | addr := unix.RawSockaddrInet6{} 59 | size := uint32(unsafe.Sizeof(addr)) 60 | _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0) 61 | if err != 0 { 62 | return netip.AddrPort{}, err 63 | } 64 | port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:]) 65 | return netip.AddrPortFrom(netip.AddrFrom16(addr.Addr), port), nil 66 | } 67 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "net" 7 | "net/netip" 8 | "syscall" 9 | "unsafe" 10 | 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | const ( 17 | SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h 18 | IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h 19 | ) 20 | 21 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 22 | c, ok := conn.(*net.TCPConn) 23 | if !ok { 24 | return nil, errors.New("only work with TCP connection") 25 | } 26 | 27 | rc, err := c.SyscallConn() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | var addr netip.AddrPort 33 | 34 | rc.Control(func(fd uintptr) { 35 | if ip4 := c.LocalAddr().(*net.TCPAddr).IP.To4(); ip4 != nil { 36 | addr, err = getorigdst(fd) 37 | } else { 38 | addr, err = getorigdst6(fd) 39 | } 40 | }) 41 | 42 | return socks5.AddrFromStdAddrPort(addr), err 43 | } 44 | 45 | // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c 46 | func getorigdst(fd uintptr) (netip.AddrPort, error) { 47 | addr := unix.RawSockaddrInet4{} 48 | size := uint32(unsafe.Sizeof(addr)) 49 | if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0); err != nil { 50 | return netip.AddrPort{}, err 51 | } 52 | port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:]) 53 | return netip.AddrPortFrom(netip.AddrFrom4(addr.Addr), port), nil 54 | } 55 | 56 | func getorigdst6(fd uintptr) (netip.AddrPort, error) { 57 | addr := unix.RawSockaddrInet6{} 58 | size := uint32(unsafe.Sizeof(addr)) 59 | if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0); err != nil { 60 | return netip.AddrPort{}, err 61 | } 62 | port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:]) 63 | return netip.AddrPortFrom(netip.AddrFrom16(addr.Addr), port), nil 64 | } 65 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux_386.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 9 | 10 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 11 | var a [6]uintptr 12 | a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 13 | if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { 14 | return errno 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux_other.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !386 2 | 3 | package redir 4 | 5 | import "syscall" 6 | 7 | const GETSOCKOPT = syscall.SYS_GETSOCKOPT 8 | 9 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 10 | if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { 11 | return errno 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /listener/redir/tcp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux && !freebsd 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | ) 11 | 12 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 13 | return nil, errors.New("system not support yet") 14 | } 15 | -------------------------------------------------------------------------------- /listener/socks/udp.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | "github.com/Dreamacro/clash/common/pool" 8 | "github.com/Dreamacro/clash/common/sockopt" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/log" 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | ) 13 | 14 | type UDPListener struct { 15 | packetConn net.PacketConn 16 | addr string 17 | closed bool 18 | } 19 | 20 | // RawAddress implements C.Listener 21 | func (l *UDPListener) RawAddress() string { 22 | return l.addr 23 | } 24 | 25 | // Address implements C.Listener 26 | func (l *UDPListener) Address() string { 27 | return l.packetConn.LocalAddr().String() 28 | } 29 | 30 | // Close implements C.Listener 31 | func (l *UDPListener) Close() error { 32 | l.closed = true 33 | return l.packetConn.Close() 34 | } 35 | 36 | func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { 37 | l, err := net.ListenPacket("udp", addr) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil { 43 | log.Warnln("Failed to Reuse UDP Address: %s", err) 44 | } 45 | 46 | sl := &UDPListener{ 47 | packetConn: l, 48 | addr: addr, 49 | } 50 | go func() { 51 | for { 52 | buf := pool.Get(pool.UDPBufferSize) 53 | n, remoteAddr, err := l.ReadFrom(buf) 54 | if err != nil { 55 | pool.Put(buf) 56 | if sl.closed { 57 | break 58 | } 59 | continue 60 | } 61 | handleSocksUDP(l, in, buf[:n], remoteAddr) 62 | } 63 | }() 64 | 65 | return sl, nil 66 | } 67 | 68 | func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { 69 | target, payload, err := socks5.DecodeUDPPacket(buf) 70 | if err != nil { 71 | // Unresolved UDP packet, return buffer to the pool 72 | pool.Put(buf) 73 | return 74 | } 75 | packet := &packet{ 76 | pc: pc, 77 | rAddr: addr, 78 | payload: payload, 79 | bufRef: buf, 80 | } 81 | select { 82 | case in <- inbound.NewPacket(target, pc.LocalAddr(), packet, C.SOCKS5): 83 | default: 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /listener/socks/utils.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | "github.com/Dreamacro/clash/transport/socks5" 8 | ) 9 | 10 | type packet struct { 11 | pc net.PacketConn 12 | rAddr net.Addr 13 | payload []byte 14 | bufRef []byte 15 | } 16 | 17 | func (c *packet) Data() []byte { 18 | return c.payload 19 | } 20 | 21 | // WriteBack write UDP packet with source(ip, port) = `addr` 22 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 23 | packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) 24 | if err != nil { 25 | return 26 | } 27 | return c.pc.WriteTo(packet, c.rAddr) 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *packet) LocalAddr() net.Addr { 32 | return c.rAddr 33 | } 34 | 35 | func (c *packet) Drop() { 36 | pool.Put(c.bufRef) 37 | } 38 | -------------------------------------------------------------------------------- /listener/tproxy/packet.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | "github.com/Dreamacro/clash/common/pool" 8 | ) 9 | 10 | type packet struct { 11 | lAddr netip.AddrPort 12 | buf []byte 13 | } 14 | 15 | func (c *packet) Data() []byte { 16 | return c.buf 17 | } 18 | 19 | // WriteBack opens a new socket binding `addr` to write UDP packet back 20 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 21 | tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr) 22 | if err != nil { 23 | n = 0 24 | return 25 | } 26 | n, err = tc.Write(b) 27 | tc.Close() 28 | return 29 | } 30 | 31 | // LocalAddr returns the source IP/Port of UDP Packet 32 | func (c *packet) LocalAddr() net.Addr { 33 | return &net.UDPAddr{IP: c.lAddr.Addr().AsSlice(), Port: int(c.lAddr.Port()), Zone: c.lAddr.Addr().Zone()} 34 | } 35 | 36 | func (c *packet) Drop() { 37 | pool.Put(c.buf) 38 | } 39 | -------------------------------------------------------------------------------- /listener/tproxy/setsockopt_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | func setsockopt(rc syscall.RawConn, addr string) error { 11 | isIPv6 := true 12 | host, _, err := net.SplitHostPort(addr) 13 | if err != nil { 14 | return err 15 | } 16 | ip := net.ParseIP(host) 17 | if ip != nil && ip.To4() != nil { 18 | isIPv6 = false 19 | } 20 | 21 | rc.Control(func(fd uintptr) { 22 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 23 | 24 | if err == nil { 25 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) 26 | } 27 | if err == nil && isIPv6 { 28 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) 29 | } 30 | 31 | if err == nil { 32 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) 33 | } 34 | if err == nil && isIPv6 { 35 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) 36 | } 37 | }) 38 | 39 | return err 40 | } 41 | -------------------------------------------------------------------------------- /listener/tproxy/setsockopt_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "errors" 7 | "syscall" 8 | ) 9 | 10 | func setsockopt(rc syscall.RawConn, addr string) error { 11 | return errors.New("not supported on current platform") 12 | } 13 | -------------------------------------------------------------------------------- /listener/tproxy/tcp.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | type Listener struct { 12 | listener net.Listener 13 | addr string 14 | closed bool 15 | } 16 | 17 | // RawAddress implements C.Listener 18 | func (l *Listener) RawAddress() string { 19 | return l.addr 20 | } 21 | 22 | // Address implements C.Listener 23 | func (l *Listener) Address() string { 24 | return l.listener.Addr().String() 25 | } 26 | 27 | // Close implements C.Listener 28 | func (l *Listener) Close() error { 29 | l.closed = true 30 | return l.listener.Close() 31 | } 32 | 33 | func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) { 34 | target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) 35 | conn.(*net.TCPConn).SetKeepAlive(true) 36 | in <- inbound.NewSocket(target, conn, C.TPROXY) 37 | } 38 | 39 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 40 | l, err := net.Listen("tcp", addr) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | tl := l.(*net.TCPListener) 46 | rc, err := tl.SyscallConn() 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | err = setsockopt(rc, addr) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | rl := &Listener{ 57 | listener: l, 58 | addr: addr, 59 | } 60 | 61 | go func() { 62 | for { 63 | c, err := l.Accept() 64 | if err != nil { 65 | if rl.closed { 66 | break 67 | } 68 | continue 69 | } 70 | go rl.handleTProxy(c, in) 71 | } 72 | }() 73 | 74 | return rl, nil 75 | } 76 | -------------------------------------------------------------------------------- /listener/tproxy/udp.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | "github.com/Dreamacro/clash/adapter/inbound" 8 | "github.com/Dreamacro/clash/common/pool" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/transport/socks5" 11 | ) 12 | 13 | type UDPListener struct { 14 | packetConn net.PacketConn 15 | addr string 16 | closed bool 17 | } 18 | 19 | // RawAddress implements C.Listener 20 | func (l *UDPListener) RawAddress() string { 21 | return l.addr 22 | } 23 | 24 | // Address implements C.Listener 25 | func (l *UDPListener) Address() string { 26 | return l.packetConn.LocalAddr().String() 27 | } 28 | 29 | // Close implements C.Listener 30 | func (l *UDPListener) Close() error { 31 | l.closed = true 32 | return l.packetConn.Close() 33 | } 34 | 35 | func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { 36 | l, err := net.ListenPacket("udp", addr) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | rl := &UDPListener{ 42 | packetConn: l, 43 | addr: addr, 44 | } 45 | 46 | c := l.(*net.UDPConn) 47 | 48 | rc, err := c.SyscallConn() 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | err = setsockopt(rc, addr) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | go func() { 59 | oob := make([]byte, 1024) 60 | for { 61 | buf := pool.Get(pool.UDPBufferSize) 62 | n, oobn, _, lAddr, err := c.ReadMsgUDPAddrPort(buf, oob) 63 | if err != nil { 64 | pool.Put(buf) 65 | if rl.closed { 66 | break 67 | } 68 | continue 69 | } 70 | 71 | rAddr, err := getOrigDst(oob[:oobn]) 72 | if err != nil { 73 | continue 74 | } 75 | 76 | if rAddr.Addr().Is4() { 77 | // try to unmap 4in6 address 78 | lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port()) 79 | } 80 | handlePacketConn(in, buf[:n], lAddr, rAddr) 81 | } 82 | }() 83 | 84 | return rl, nil 85 | } 86 | 87 | func handlePacketConn(in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) { 88 | target := socks5.AddrFromStdAddrPort(rAddr) 89 | pkt := &packet{ 90 | lAddr: lAddr, 91 | buf: buf, 92 | } 93 | select { 94 | case in <- inbound.NewPacket(target, target.UDPAddr(), pkt, C.TPROXY): 95 | default: 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /listener/tproxy/udp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | "net/netip" 9 | ) 10 | 11 | func getOrigDst(oob []byte) (netip.AddrPort, error) { 12 | return netip.AddrPort{}, errors.New("UDP redir not supported on current platform") 13 | } 14 | 15 | func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) { 16 | return nil, errors.New("UDP redir not supported on current platform") 17 | } 18 | -------------------------------------------------------------------------------- /listener/tun/dev/dev.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import "gvisor.dev/gvisor/pkg/tcpip/stack" 4 | 5 | // TunDevice is cross-platform tun interface 6 | type TunDevice interface { 7 | Name() string 8 | URL() string 9 | AsLinkEndpoint() (stack.LinkEndpoint, error) 10 | Close() 11 | } 12 | -------------------------------------------------------------------------------- /listener/tun/dev/dev_unsupport.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!android,!darwin 2 | 3 | package dev 4 | 5 | import ( 6 | "errors" 7 | "runtime" 8 | 9 | "net/url" 10 | ) 11 | 12 | func OpenTunDevice(_ url.URL) (TunDevice, error) { 13 | return nil, errors.New("Unsupported platform " + runtime.GOOS + "/" + runtime.GOARCH) 14 | } 15 | -------------------------------------------------------------------------------- /listener/tun/tun.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import "github.com/Dreamacro/clash/dns" 4 | 5 | // TunAdapter hold the state of tun/tap interface 6 | type TunAdapter interface { 7 | Close() 8 | DeviceURL() string 9 | // Creates dns server on tun device 10 | ReCreateDNSServer(addr string) error 11 | // Set the resolver to serve DNS request 12 | ResetDNSResolver(resolver *dns.Resolver, mapper *dns.ResolverEnhancer) error 13 | // Get the current listening address of DNS Server 14 | DNSListen() string 15 | } 16 | -------------------------------------------------------------------------------- /listener/tunnel/packet.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | type packet struct { 10 | pc net.PacketConn 11 | rAddr net.Addr 12 | payload []byte 13 | } 14 | 15 | func (c *packet) Data() []byte { 16 | return c.payload 17 | } 18 | 19 | // WriteBack write UDP packet with source(ip, port) = `addr` 20 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 21 | return c.pc.WriteTo(b, c.rAddr) 22 | } 23 | 24 | // LocalAddr returns the source IP/Port of UDP Packet 25 | func (c *packet) LocalAddr() net.Addr { 26 | return c.rAddr 27 | } 28 | 29 | func (c *packet) Drop() { 30 | pool.Put(c.payload) 31 | } 32 | -------------------------------------------------------------------------------- /listener/tunnel/tcp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/adapter/inbound" 8 | C "github.com/Dreamacro/clash/constant" 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | ) 11 | 12 | type Listener struct { 13 | listener net.Listener 14 | addr string 15 | target socks5.Addr 16 | proxy string 17 | closed bool 18 | } 19 | 20 | // RawAddress implements C.Listener 21 | func (l *Listener) RawAddress() string { 22 | return l.addr 23 | } 24 | 25 | // Address implements C.Listener 26 | func (l *Listener) Address() string { 27 | return l.listener.Addr().String() 28 | } 29 | 30 | // Close implements C.Listener 31 | func (l *Listener) Close() error { 32 | l.closed = true 33 | return l.listener.Close() 34 | } 35 | 36 | func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext) { 37 | conn.(*net.TCPConn).SetKeepAlive(true) 38 | ctx := inbound.NewSocket(l.target, conn, C.TUNNEL) 39 | ctx.Metadata().SpecialProxy = l.proxy 40 | in <- ctx 41 | } 42 | 43 | func New(addr, target, proxy string, in chan<- C.ConnContext) (*Listener, error) { 44 | l, err := net.Listen("tcp", addr) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | targetAddr := socks5.ParseAddr(target) 50 | if targetAddr == nil { 51 | return nil, fmt.Errorf("invalid target address %s", target) 52 | } 53 | 54 | rl := &Listener{ 55 | listener: l, 56 | target: targetAddr, 57 | proxy: proxy, 58 | addr: addr, 59 | } 60 | 61 | go func() { 62 | for { 63 | c, err := l.Accept() 64 | if err != nil { 65 | if rl.closed { 66 | break 67 | } 68 | continue 69 | } 70 | go rl.handleTCP(c, in) 71 | } 72 | }() 73 | 74 | return rl, nil 75 | } 76 | -------------------------------------------------------------------------------- /listener/tunnel/udp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/adapter/inbound" 8 | "github.com/Dreamacro/clash/common/pool" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/transport/socks5" 11 | ) 12 | 13 | type PacketConn struct { 14 | conn net.PacketConn 15 | addr string 16 | target socks5.Addr 17 | proxy string 18 | closed bool 19 | } 20 | 21 | // RawAddress implements C.Listener 22 | func (l *PacketConn) RawAddress() string { 23 | return l.addr 24 | } 25 | 26 | // Address implements C.Listener 27 | func (l *PacketConn) Address() string { 28 | return l.conn.LocalAddr().String() 29 | } 30 | 31 | // Close implements C.Listener 32 | func (l *PacketConn) Close() error { 33 | l.closed = true 34 | return l.conn.Close() 35 | } 36 | 37 | func NewUDP(addr, target, proxy string, in chan<- *inbound.PacketAdapter) (*PacketConn, error) { 38 | l, err := net.ListenPacket("udp", addr) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | targetAddr := socks5.ParseAddr(target) 44 | if targetAddr == nil { 45 | return nil, fmt.Errorf("invalid target address %s", target) 46 | } 47 | 48 | sl := &PacketConn{ 49 | conn: l, 50 | target: targetAddr, 51 | proxy: proxy, 52 | addr: addr, 53 | } 54 | go func() { 55 | for { 56 | buf := pool.Get(pool.UDPBufferSize) 57 | n, remoteAddr, err := l.ReadFrom(buf) 58 | if err != nil { 59 | pool.Put(buf) 60 | if sl.closed { 61 | break 62 | } 63 | continue 64 | } 65 | sl.handleUDP(l, in, buf[:n], remoteAddr) 66 | } 67 | }() 68 | 69 | return sl, nil 70 | } 71 | 72 | func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { 73 | packet := &packet{ 74 | pc: pc, 75 | rAddr: addr, 76 | payload: buf, 77 | } 78 | 79 | ctx := inbound.NewPacket(l.target, pc.LocalAddr(), packet, C.TUNNEL) 80 | ctx.Metadata().SpecialProxy = l.proxy 81 | select { 82 | case in <- ctx: 83 | default: 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /log/level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // LogLevelMapping is a mapping for LogLevel enum 9 | var LogLevelMapping = map[string]LogLevel{ 10 | ERROR.String(): ERROR, 11 | WARNING.String(): WARNING, 12 | INFO.String(): INFO, 13 | DEBUG.String(): DEBUG, 14 | SILENT.String(): SILENT, 15 | } 16 | 17 | const ( 18 | DEBUG LogLevel = iota 19 | INFO 20 | WARNING 21 | ERROR 22 | SILENT 23 | ) 24 | 25 | type LogLevel int 26 | 27 | // UnmarshalYAML unserialize LogLevel with yaml 28 | func (l *LogLevel) UnmarshalYAML(unmarshal func(any) error) error { 29 | var tp string 30 | unmarshal(&tp) 31 | level, exist := LogLevelMapping[tp] 32 | if !exist { 33 | return errors.New("invalid mode") 34 | } 35 | *l = level 36 | return nil 37 | } 38 | 39 | // UnmarshalJSON unserialize LogLevel with json 40 | func (l *LogLevel) UnmarshalJSON(data []byte) error { 41 | var tp string 42 | json.Unmarshal(data, &tp) 43 | level, exist := LogLevelMapping[tp] 44 | if !exist { 45 | return errors.New("invalid mode") 46 | } 47 | *l = level 48 | return nil 49 | } 50 | 51 | // MarshalJSON serialize LogLevel with json 52 | func (l LogLevel) MarshalJSON() ([]byte, error) { 53 | return json.Marshal(l.String()) 54 | } 55 | 56 | // MarshalYAML serialize LogLevel with yaml 57 | func (l LogLevel) MarshalYAML() (any, error) { 58 | return l.String(), nil 59 | } 60 | 61 | func (l LogLevel) String() string { 62 | switch l { 63 | case INFO: 64 | return "info" 65 | case WARNING: 66 | return "warning" 67 | case ERROR: 68 | return "error" 69 | case DEBUG: 70 | return "debug" 71 | case SILENT: 72 | return "silent" 73 | default: 74 | return "unknown" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Dreamacro/clash/common/observable" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var ( 13 | logCh = make(chan any) 14 | source = observable.NewObservable(logCh) 15 | level = INFO 16 | ) 17 | 18 | func init() { 19 | log.SetOutput(os.Stdout) 20 | log.SetLevel(log.DebugLevel) 21 | } 22 | 23 | type Event struct { 24 | LogLevel LogLevel 25 | Payload string 26 | } 27 | 28 | func (e *Event) Type() string { 29 | return e.LogLevel.String() 30 | } 31 | 32 | func Infoln(format string, v ...any) { 33 | event := newLog(INFO, format, v...) 34 | logCh <- event 35 | print(event) 36 | } 37 | 38 | func Warnln(format string, v ...any) { 39 | event := newLog(WARNING, format, v...) 40 | logCh <- event 41 | print(event) 42 | } 43 | 44 | func Errorln(format string, v ...any) { 45 | event := newLog(ERROR, format, v...) 46 | logCh <- event 47 | print(event) 48 | } 49 | 50 | func Debugln(format string, v ...any) { 51 | event := newLog(DEBUG, format, v...) 52 | logCh <- event 53 | print(event) 54 | } 55 | 56 | func Fatalln(format string, v ...any) { 57 | log.Fatalf(format, v...) 58 | } 59 | 60 | func Subscribe() observable.Subscription { 61 | sub, _ := source.Subscribe() 62 | return sub 63 | } 64 | 65 | func UnSubscribe(sub observable.Subscription) { 66 | source.UnSubscribe(sub) 67 | } 68 | 69 | func Level() LogLevel { 70 | return level 71 | } 72 | 73 | func SetLevel(newLevel LogLevel) { 74 | level = newLevel 75 | } 76 | 77 | func print(data Event) { 78 | if data.LogLevel < level { 79 | return 80 | } 81 | 82 | switch data.LogLevel { 83 | case INFO: 84 | log.Infoln(data.Payload) 85 | case WARNING: 86 | log.Warnln(data.Payload) 87 | case ERROR: 88 | log.Errorln(data.Payload) 89 | case DEBUG: 90 | log.Debugln(data.Payload) 91 | } 92 | } 93 | 94 | func newLog(logLevel LogLevel, format string, v ...any) Event { 95 | return Event{ 96 | LogLevel: logLevel, 97 | Payload: fmt.Sprintf(format, v...), 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /rule/base.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | errPayload = errors.New("payload error") 9 | 10 | noResolve = "no-resolve" 11 | ) 12 | 13 | func HasNoResolve(params []string) bool { 14 | for _, p := range params { 15 | if p == noResolve { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | -------------------------------------------------------------------------------- /rule/domain.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Domain struct { 10 | domain string 11 | adapter string 12 | } 13 | 14 | func (d *Domain) RuleType() C.RuleType { 15 | return C.Domain 16 | } 17 | 18 | func (d *Domain) Match(metadata *C.Metadata) bool { 19 | return metadata.Host == d.domain 20 | } 21 | 22 | func (d *Domain) Adapter() string { 23 | return d.adapter 24 | } 25 | 26 | func (d *Domain) Payload() string { 27 | return d.domain 28 | } 29 | 30 | func (d *Domain) ShouldResolveIP() bool { 31 | return false 32 | } 33 | 34 | func (d *Domain) ShouldFindProcess() bool { 35 | return false 36 | } 37 | 38 | func NewDomain(domain string, adapter string) *Domain { 39 | return &Domain{ 40 | domain: strings.ToLower(domain), 41 | adapter: adapter, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rule/domain_keyword.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainKeyword struct { 10 | keyword string 11 | adapter string 12 | } 13 | 14 | func (dk *DomainKeyword) RuleType() C.RuleType { 15 | return C.DomainKeyword 16 | } 17 | 18 | func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { 19 | return strings.Contains(metadata.Host, dk.keyword) 20 | } 21 | 22 | func (dk *DomainKeyword) Adapter() string { 23 | return dk.adapter 24 | } 25 | 26 | func (dk *DomainKeyword) Payload() string { 27 | return dk.keyword 28 | } 29 | 30 | func (dk *DomainKeyword) ShouldResolveIP() bool { 31 | return false 32 | } 33 | 34 | func (dk *DomainKeyword) ShouldFindProcess() bool { 35 | return false 36 | } 37 | 38 | func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { 39 | return &DomainKeyword{ 40 | keyword: strings.ToLower(keyword), 41 | adapter: adapter, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rule/domain_suffix.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainSuffix struct { 10 | suffix string 11 | adapter string 12 | } 13 | 14 | func (ds *DomainSuffix) RuleType() C.RuleType { 15 | return C.DomainSuffix 16 | } 17 | 18 | func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { 19 | domain := metadata.Host 20 | return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix 21 | } 22 | 23 | func (ds *DomainSuffix) Adapter() string { 24 | return ds.adapter 25 | } 26 | 27 | func (ds *DomainSuffix) Payload() string { 28 | return ds.suffix 29 | } 30 | 31 | func (ds *DomainSuffix) ShouldResolveIP() bool { 32 | return false 33 | } 34 | 35 | func (ds *DomainSuffix) ShouldFindProcess() bool { 36 | return false 37 | } 38 | 39 | func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { 40 | return &DomainSuffix{ 41 | suffix: strings.ToLower(suffix), 42 | adapter: adapter, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rule/final.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | type Match struct { 8 | adapter string 9 | } 10 | 11 | func (f *Match) RuleType() C.RuleType { 12 | return C.MATCH 13 | } 14 | 15 | func (f *Match) Match(metadata *C.Metadata) bool { 16 | return true 17 | } 18 | 19 | func (f *Match) Adapter() string { 20 | return f.adapter 21 | } 22 | 23 | func (f *Match) Payload() string { 24 | return "" 25 | } 26 | 27 | func (f *Match) ShouldResolveIP() bool { 28 | return false 29 | } 30 | 31 | func (f *Match) ShouldFindProcess() bool { 32 | return false 33 | } 34 | 35 | func NewMatch(adapter string) *Match { 36 | return &Match{ 37 | adapter: adapter, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rule/geoip.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Dreamacro/clash/component/mmdb" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type GEOIP struct { 11 | country string 12 | adapter string 13 | noResolveIP bool 14 | } 15 | 16 | func (g *GEOIP) RuleType() C.RuleType { 17 | return C.GEOIP 18 | } 19 | 20 | func (g *GEOIP) Match(metadata *C.Metadata) bool { 21 | ip := metadata.DstIP 22 | if ip == nil { 23 | return false 24 | } 25 | 26 | if strings.EqualFold(g.country, "LAN") { 27 | return ip.IsPrivate() 28 | } 29 | record, _ := mmdb.Instance().Country(ip) 30 | return strings.EqualFold(record.Country.IsoCode, g.country) 31 | } 32 | 33 | func (g *GEOIP) Adapter() string { 34 | return g.adapter 35 | } 36 | 37 | func (g *GEOIP) Payload() string { 38 | return g.country 39 | } 40 | 41 | func (g *GEOIP) ShouldResolveIP() bool { 42 | return !g.noResolveIP 43 | } 44 | 45 | func (g *GEOIP) ShouldFindProcess() bool { 46 | return false 47 | } 48 | 49 | func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { 50 | geoip := &GEOIP{ 51 | country: country, 52 | adapter: adapter, 53 | noResolveIP: noResolveIP, 54 | } 55 | 56 | return geoip 57 | } 58 | -------------------------------------------------------------------------------- /rule/ipcidr.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type IPCIDROption func(*IPCIDR) 10 | 11 | func WithIPCIDRSourceIP(b bool) IPCIDROption { 12 | return func(i *IPCIDR) { 13 | i.isSourceIP = b 14 | } 15 | } 16 | 17 | func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { 18 | return func(i *IPCIDR) { 19 | i.noResolveIP = noResolve 20 | } 21 | } 22 | 23 | type IPCIDR struct { 24 | ipnet *net.IPNet 25 | adapter string 26 | isSourceIP bool 27 | noResolveIP bool 28 | } 29 | 30 | func (i *IPCIDR) RuleType() C.RuleType { 31 | if i.isSourceIP { 32 | return C.SrcIPCIDR 33 | } 34 | return C.IPCIDR 35 | } 36 | 37 | func (i *IPCIDR) Match(metadata *C.Metadata) bool { 38 | ip := metadata.DstIP 39 | if i.isSourceIP { 40 | ip = metadata.SrcIP 41 | } 42 | return ip != nil && i.ipnet.Contains(ip) 43 | } 44 | 45 | func (i *IPCIDR) Adapter() string { 46 | return i.adapter 47 | } 48 | 49 | func (i *IPCIDR) Payload() string { 50 | return i.ipnet.String() 51 | } 52 | 53 | func (i *IPCIDR) ShouldResolveIP() bool { 54 | return !i.noResolveIP 55 | } 56 | 57 | func (i *IPCIDR) ShouldFindProcess() bool { 58 | return false 59 | } 60 | 61 | func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { 62 | _, ipnet, err := net.ParseCIDR(s) 63 | if err != nil { 64 | return nil, errPayload 65 | } 66 | 67 | ipcidr := &IPCIDR{ 68 | ipnet: ipnet, 69 | adapter: adapter, 70 | } 71 | 72 | for _, o := range opts { 73 | o(ipcidr) 74 | } 75 | 76 | return ipcidr, nil 77 | } 78 | -------------------------------------------------------------------------------- /rule/ipset.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/ipset" 5 | C "github.com/Dreamacro/clash/constant" 6 | "github.com/Dreamacro/clash/log" 7 | ) 8 | 9 | type IPSet struct { 10 | name string 11 | adapter string 12 | noResolveIP bool 13 | } 14 | 15 | func (f *IPSet) RuleType() C.RuleType { 16 | return C.IPSet 17 | } 18 | 19 | func (f *IPSet) Match(metadata *C.Metadata) bool { 20 | exist, err := ipset.Test(f.name, metadata.DstIP) 21 | if err != nil { 22 | log.Warnln("check ipset '%s' failed: %s", f.name, err.Error()) 23 | return false 24 | } 25 | return exist 26 | } 27 | 28 | func (f *IPSet) Adapter() string { 29 | return f.adapter 30 | } 31 | 32 | func (f *IPSet) Payload() string { 33 | return f.name 34 | } 35 | 36 | func (f *IPSet) ShouldResolveIP() bool { 37 | return !f.noResolveIP 38 | } 39 | 40 | func (f *IPSet) ShouldFindProcess() bool { 41 | return false 42 | } 43 | 44 | func NewIPSet(name string, adapter string, noResolveIP bool) (*IPSet, error) { 45 | if err := ipset.Verify(name); err != nil { 46 | return nil, err 47 | } 48 | 49 | return &IPSet{ 50 | name: name, 51 | adapter: adapter, 52 | noResolveIP: noResolveIP, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /rule/parser.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { 10 | var ( 11 | parseErr error 12 | parsed C.Rule 13 | ) 14 | 15 | switch tp { 16 | case "DOMAIN": 17 | parsed = NewDomain(payload, target) 18 | case "DOMAIN-SUFFIX": 19 | parsed = NewDomainSuffix(payload, target) 20 | case "DOMAIN-KEYWORD": 21 | parsed = NewDomainKeyword(payload, target) 22 | case "GEOIP": 23 | noResolve := HasNoResolve(params) 24 | parsed = NewGEOIP(payload, target, noResolve) 25 | case "IP-CIDR", "IP-CIDR6": 26 | noResolve := HasNoResolve(params) 27 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) 28 | case "SRC-IP-CIDR": 29 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) 30 | case "SRC-PORT": 31 | parsed, parseErr = NewPort(payload, target, true) 32 | case "DST-PORT": 33 | parsed, parseErr = NewPort(payload, target, false) 34 | case "PROCESS-NAME": 35 | parsed, parseErr = NewProcess(payload, target, true) 36 | case "PROCESS-PATH": 37 | parsed, parseErr = NewProcess(payload, target, false) 38 | case "IPSET": 39 | noResolve := HasNoResolve(params) 40 | parsed, parseErr = NewIPSet(payload, target, noResolve) 41 | case "MATCH": 42 | parsed = NewMatch(target) 43 | default: 44 | parseErr = fmt.Errorf("unsupported rule type %s", tp) 45 | } 46 | 47 | return parsed, parseErr 48 | } 49 | -------------------------------------------------------------------------------- /rule/port.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strconv" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Port struct { 10 | adapter string 11 | port string 12 | isSource bool 13 | } 14 | 15 | func (p *Port) RuleType() C.RuleType { 16 | if p.isSource { 17 | return C.SrcPort 18 | } 19 | return C.DstPort 20 | } 21 | 22 | func (p *Port) Match(metadata *C.Metadata) bool { 23 | if p.isSource { 24 | return metadata.SrcPort == p.port 25 | } 26 | return metadata.DstPort == p.port 27 | } 28 | 29 | func (p *Port) Adapter() string { 30 | return p.adapter 31 | } 32 | 33 | func (p *Port) Payload() string { 34 | return p.port 35 | } 36 | 37 | func (p *Port) ShouldResolveIP() bool { 38 | return false 39 | } 40 | 41 | func (p *Port) ShouldFindProcess() bool { 42 | return false 43 | } 44 | 45 | func NewPort(port string, adapter string, isSource bool) (*Port, error) { 46 | _, err := strconv.ParseUint(port, 10, 16) 47 | if err != nil { 48 | return nil, errPayload 49 | } 50 | return &Port{ 51 | adapter: adapter, 52 | port: port, 53 | isSource: isSource, 54 | }, nil 55 | } 56 | -------------------------------------------------------------------------------- /rule/process.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type Process struct { 11 | adapter string 12 | process string 13 | nameOnly bool 14 | } 15 | 16 | func (ps *Process) RuleType() C.RuleType { 17 | if ps.nameOnly { 18 | return C.Process 19 | } 20 | 21 | return C.ProcessPath 22 | } 23 | 24 | func (ps *Process) Match(metadata *C.Metadata) bool { 25 | if ps.nameOnly { 26 | return strings.EqualFold(filepath.Base(metadata.ProcessPath), ps.process) 27 | } 28 | 29 | return strings.EqualFold(metadata.ProcessPath, ps.process) 30 | } 31 | 32 | func (ps *Process) Adapter() string { 33 | return ps.adapter 34 | } 35 | 36 | func (ps *Process) Payload() string { 37 | return ps.process 38 | } 39 | 40 | func (ps *Process) ShouldResolveIP() bool { 41 | return false 42 | } 43 | 44 | func (ps *Process) ShouldFindProcess() bool { 45 | return true 46 | } 47 | 48 | func NewProcess(process string, adapter string, nameOnly bool) (*Process, error) { 49 | return &Process{ 50 | adapter: adapter, 51 | process: process, 52 | nameOnly: nameOnly, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /test/.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - govet 6 | - gci 7 | - staticcheck 8 | 9 | linters-settings: 10 | gci: 11 | sections: 12 | - standard 13 | - default 14 | - prefix(github.com/Dreamacro/clash) 15 | staticcheck: 16 | go: '1.20' 17 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | GOOS=darwin golangci-lint run --fix ./... 3 | GOOS=linux golangci-lint run --fix ./... 4 | 5 | test: 6 | go test -p 1 -v ./... 7 | 8 | benchmark: 9 | go test -benchmem -run=^$$ -bench . 10 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## Clash testing suit 2 | 3 | ### Protocol testing suit 4 | 5 | * TCP pingpong test 6 | * UDP pingpong test 7 | * TCP large data test 8 | * UDP large data test 9 | 10 | ### Protocols 11 | 12 | - [x] Shadowsocks 13 | - [x] Normal 14 | - [x] ObfsHTTP 15 | - [x] ObfsTLS 16 | - [x] ObfsV2rayPlugin 17 | - [x] Vmess 18 | - [x] Normal 19 | - [x] AEAD 20 | - [x] HTTP 21 | - [x] HTTP2 22 | - [x] TLS 23 | - [x] Websocket 24 | - [x] Websocket TLS 25 | - [x] gRPC 26 | - [x] Trojan 27 | - [x] Normal 28 | - [x] gRPC 29 | - [x] Snell 30 | - [x] Normal 31 | - [x] ObfsHTTP 32 | - [x] ObfsTLS 33 | 34 | ### Features 35 | 36 | - [ ] DNS 37 | - [x] DNS Server 38 | - [x] FakeIP 39 | - [x] Host 40 | 41 | ### Command 42 | 43 | Prerequisite 44 | 45 | * docker (support Linux and macOS) 46 | 47 | ``` 48 | $ make test 49 | ``` 50 | 51 | benchmark (Linux) 52 | 53 | > Cannot represent the throughput of the protocol on your machine 54 | > but you can compare the corresponding throughput of the protocol on clash 55 | > (change chunkSize to measure the maximum throughput of clash on your machine) 56 | 57 | ``` 58 | $ make benchmark 59 | ``` 60 | -------------------------------------------------------------------------------- /test/config/example.org-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5 3 | 5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo 4 | PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE 5 | sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R 6 | i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5 7 | LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge 8 | gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+ 9 | y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO 10 | jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z 11 | ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv 12 | H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG 13 | o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ 14 | CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49 15 | aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33 16 | bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7 17 | Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh 18 | ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO 19 | pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT 20 | 4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi 21 | GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA 22 | vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB 23 | fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z 24 | zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X 25 | DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28 26 | 9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP 27 | XCar+uxMBXI1zbXqd9QdEwy4Ig== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/config/example.org.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB 3 | hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh 4 | bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl 5 | cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz 6 | MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl 7 | bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy 8 | by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 9 | ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8 10 | 3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI 11 | YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b 12 | IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo 13 | UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2 14 | I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG 15 | A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln 16 | F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP 17 | TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa 18 | Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1 19 | Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz 20 | Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG 21 | WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6 22 | AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6 23 | gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS 24 | tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /test/config/snell-http.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | obfs = http 5 | -------------------------------------------------------------------------------- /test/config/snell-tls.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | obfs = tls 5 | -------------------------------------------------------------------------------- /test/config/snell.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | -------------------------------------------------------------------------------- /test/config/trojan-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "trojan", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "password": "example", 11 | "email": "grpc@example.com" 12 | } 13 | ] 14 | }, 15 | "streamSettings": { 16 | "network": "grpc", 17 | "security": "tls", 18 | "tlsSettings": { 19 | "certificates": [ 20 | { 21 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 22 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 23 | } 24 | ] 25 | }, 26 | "grpcSettings": { 27 | "serviceName": "example" 28 | } 29 | } 30 | } 31 | ], 32 | "outbounds": [ 33 | { 34 | "protocol": "freedom" 35 | } 36 | ], 37 | "log": { 38 | "loglevel": "debug" 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/trojan-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "disable_http_check": true, 6 | "password": [ 7 | "example" 8 | ], 9 | "websocket": { 10 | "enabled": true, 11 | "path": "/", 12 | "host": "example.org" 13 | }, 14 | "ssl": { 15 | "verify": true, 16 | "cert": "/fullchain.pem", 17 | "key": "/privkey.pem", 18 | "sni": "example.org" 19 | } 20 | } -------------------------------------------------------------------------------- /test/config/trojan.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "password": [ 6 | "password" 7 | ], 8 | "log_level": 1, 9 | "ssl": { 10 | "cert": "/path/to/certificate.crt", 11 | "key": "/path/to/private.key", 12 | "key_password": "", 13 | "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384", 14 | "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", 15 | "prefer_server_cipher": true, 16 | "alpn": [ 17 | "http/1.1" 18 | ], 19 | "alpn_port_override": { 20 | "h2": 81 21 | }, 22 | "reuse_session": true, 23 | "session_ticket": false, 24 | "session_timeout": 600, 25 | "plain_http_response": "", 26 | "curves": "", 27 | "dhparam": "" 28 | }, 29 | "tcp": { 30 | "prefer_ipv4": false, 31 | "no_delay": true, 32 | "keep_alive": true, 33 | "reuse_port": false, 34 | "fast_open": false, 35 | "fast_open_qlen": 20 36 | }, 37 | "mysql": { 38 | "enabled": false 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/vmess-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "grpc", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "grpcSettings": { 26 | "serviceName": "example!" 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ], 36 | "log": { 37 | "loglevel": "debug" 38 | } 39 | } -------------------------------------------------------------------------------- /test/config/vmess-http.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp", 16 | "tcpSettings": { 17 | "header": { 18 | "type": "http", 19 | "response": { 20 | "version": "1.1", 21 | "status": "200", 22 | "reason": "OK", 23 | "headers": { 24 | "Content-Type": [ 25 | "application/octet-stream", 26 | "video/mpeg", 27 | "application/x-msdownload", 28 | "text/html", 29 | "application/x-shockwave-flash" 30 | ], 31 | "Transfer-Encoding": [ 32 | "chunked" 33 | ], 34 | "Connection": [ 35 | "keep-alive" 36 | ], 37 | "Pragma": "no-cache" 38 | } 39 | } 40 | } 41 | }, 42 | "security": "none" 43 | } 44 | } 45 | ], 46 | "outbounds": [ 47 | { 48 | "protocol": "freedom" 49 | } 50 | ], 51 | "log": { 52 | "loglevel": "debug" 53 | } 54 | } -------------------------------------------------------------------------------- /test/config/vmess-http2.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "http", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "httpSettings": { 26 | "host": [ 27 | "example.org" 28 | ], 29 | "path": "/test" 30 | } 31 | } 32 | } 33 | ], 34 | "outbounds": [ 35 | { 36 | "protocol": "freedom" 37 | } 38 | ], 39 | "log": { 40 | "loglevel": "debug" 41 | } 42 | } -------------------------------------------------------------------------------- /test/config/vmess-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ], 33 | "log": { 34 | "loglevel": "debug" 35 | } 36 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-0rtt.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "none", 17 | "wsSettings": { 18 | "maxEarlyData": 128, 19 | "earlyDataHeaderName": "Sec-WebSocket-Protocol" 20 | } 21 | } 22 | } 23 | ], 24 | "outbounds": [ 25 | { 26 | "protocol": "freedom" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-tls-zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811", 11 | "alterId": 0, 12 | "security": "zero" 13 | } 14 | ] 15 | }, 16 | "streamSettings": { 17 | "network": "ws", 18 | "security": "tls", 19 | "tlsSettings": { 20 | "certificates": [ 21 | { 22 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 23 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 24 | } 25 | ] 26 | } 27 | } 28 | } 29 | ], 30 | "outbounds": [ 31 | { 32 | "protocol": "freedom" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /test/config/vmess-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "none" 17 | } 18 | } 19 | ], 20 | "outbounds": [ 21 | { 22 | "protocol": "freedom" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /test/config/vmess.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp" 16 | } 17 | } 18 | ], 19 | "outbounds": [ 20 | { 21 | "protocol": "freedom" 22 | } 23 | ], 24 | "log": { 25 | "loglevel": "debug" 26 | } 27 | } -------------------------------------------------------------------------------- /test/docker_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/docker/docker/api/types" 7 | "github.com/docker/docker/api/types/container" 8 | "github.com/docker/docker/client" 9 | ) 10 | 11 | func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) { 12 | c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 13 | if err != nil { 14 | return "", err 15 | } 16 | defer c.Close() 17 | 18 | if !isDarwin { 19 | hostCfg.NetworkMode = "host" 20 | } 21 | 22 | container, err := c.ContainerCreate(context.Background(), cfg, hostCfg, nil, nil, name) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | if err = c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil { 28 | return "", err 29 | } 30 | 31 | return container.ID, nil 32 | } 33 | 34 | func cleanContainer(id string) error { 35 | c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 36 | if err != nil { 37 | return err 38 | } 39 | defer c.Close() 40 | 41 | removeOpts := types.ContainerRemoveOptions{Force: true} 42 | return c.ContainerRemove(context.Background(), id, removeOpts) 43 | } 44 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "strconv" 7 | 8 | "go.uber.org/automaxprocs/maxprocs" 9 | ) 10 | 11 | func main() { 12 | maxprocs.Set(maxprocs.Logger(func(string, ...any) {})) 13 | os.Stdout.Write([]byte(strconv.FormatInt(int64(runtime.GOMAXPROCS(0)), 10))) 14 | } 15 | -------------------------------------------------------------------------------- /test/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | ) 8 | 9 | func Listen(network, address string) (net.Listener, error) { 10 | lc := net.ListenConfig{} 11 | 12 | var lastErr error 13 | for i := 0; i < 5; i++ { 14 | l, err := lc.Listen(context.Background(), network, address) 15 | if err == nil { 16 | return l, nil 17 | } 18 | 19 | lastErr = err 20 | time.Sleep(time.Millisecond * 200) 21 | } 22 | return nil, lastErr 23 | } 24 | 25 | func ListenPacket(network, address string) (net.PacketConn, error) { 26 | var lastErr error 27 | for i := 0; i < 5; i++ { 28 | l, err := net.ListenPacket(network, address) 29 | if err == nil { 30 | return l, nil 31 | } 32 | 33 | lastErr = err 34 | time.Sleep(time.Millisecond * 200) 35 | } 36 | return nil, lastErr 37 | } 38 | 39 | func TCPing(addr string) bool { 40 | for i := 0; i < 10; i++ { 41 | conn, err := net.Dial("tcp", addr) 42 | if err == nil { 43 | conn.Close() 44 | return true 45 | } 46 | time.Sleep(time.Millisecond * 500) 47 | } 48 | 49 | return false 50 | } 51 | -------------------------------------------------------------------------------- /test/util_darwin_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "syscall" 8 | 9 | "golang.org/x/net/route" 10 | ) 11 | 12 | func defaultRouteIP() (net.IP, error) { 13 | idx, err := defaultRouteInterfaceIndex() 14 | if err != nil { 15 | return nil, err 16 | } 17 | iface, err := net.InterfaceByIndex(idx) 18 | if err != nil { 19 | return nil, err 20 | } 21 | addrs, err := iface.Addrs() 22 | if err != nil { 23 | return nil, err 24 | } 25 | for _, addr := range addrs { 26 | ip := addr.(*net.IPNet).IP 27 | if ip.To4() != nil { 28 | return ip, nil 29 | } 30 | } 31 | 32 | return nil, errors.New("no ipv4 addr") 33 | } 34 | 35 | func defaultRouteInterfaceIndex() (int, error) { 36 | rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) 37 | if err != nil { 38 | return 0, fmt.Errorf("route.FetchRIB: %w", err) 39 | } 40 | msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) 41 | if err != nil { 42 | return 0, fmt.Errorf("route.ParseRIB: %w", err) 43 | } 44 | for _, message := range msgs { 45 | routeMessage := message.(*route.RouteMessage) 46 | if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 { 47 | continue 48 | } 49 | 50 | addresses := routeMessage.Addrs 51 | 52 | destination, ok := addresses[0].(*route.Inet4Addr) 53 | if !ok { 54 | continue 55 | } 56 | 57 | if destination.IP != [4]byte{0, 0, 0, 0} { 58 | continue 59 | } 60 | 61 | switch addresses[1].(type) { 62 | case *route.Inet4Addr: 63 | return routeMessage.Index, nil 64 | default: 65 | continue 66 | } 67 | } 68 | 69 | return 0, fmt.Errorf("ambiguous gateway interfaces found") 70 | } 71 | -------------------------------------------------------------------------------- /test/util_other_test.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func defaultRouteIP() (net.IP, error) { 11 | return nil, errors.New("not supported") 12 | } 13 | -------------------------------------------------------------------------------- /transport/shadowsocks/README.md: -------------------------------------------------------------------------------- 1 | ## Embedded go-shadowsocks2 2 | 3 | from https://github.com/Dreamacro/go-shadowsocks2 4 | 5 | origin https://github.com/riobard/go-shadowsocks2 6 | -------------------------------------------------------------------------------- /transport/snell/cipher.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | 7 | "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" 8 | 9 | "golang.org/x/crypto/argon2" 10 | "golang.org/x/crypto/chacha20poly1305" 11 | ) 12 | 13 | type snellCipher struct { 14 | psk []byte 15 | keySize int 16 | makeAEAD func(key []byte) (cipher.AEAD, error) 17 | } 18 | 19 | func (sc *snellCipher) KeySize() int { return sc.keySize } 20 | func (sc *snellCipher) SaltSize() int { return 16 } 21 | func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { 22 | return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) 23 | } 24 | 25 | func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { 26 | return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) 27 | } 28 | 29 | func snellKDF(psk, salt []byte, keySize int) []byte { 30 | // snell use a special kdf function 31 | return argon2.IDKey(psk, salt, 3, 8, 1, 32)[:keySize] 32 | } 33 | 34 | func aesGCM(key []byte) (cipher.AEAD, error) { 35 | blk, err := aes.NewCipher(key) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return cipher.NewGCM(blk) 40 | } 41 | 42 | func NewAES128GCM(psk []byte) shadowaead.Cipher { 43 | return &snellCipher{ 44 | psk: psk, 45 | keySize: 16, 46 | makeAEAD: aesGCM, 47 | } 48 | } 49 | 50 | func NewChacha20Poly1305(psk []byte) shadowaead.Cipher { 51 | return &snellCipher{ 52 | psk: psk, 53 | keySize: 32, 54 | makeAEAD: chacha20poly1305.New, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /transport/snell/pool.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/component/pool" 9 | "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" 10 | ) 11 | 12 | type Pool struct { 13 | pool *pool.Pool 14 | } 15 | 16 | func (p *Pool) Get() (net.Conn, error) { 17 | return p.GetContext(context.Background()) 18 | } 19 | 20 | func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) { 21 | elm, err := p.pool.GetContext(ctx) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &PoolConn{elm.(*Snell), p}, nil 27 | } 28 | 29 | func (p *Pool) Put(conn net.Conn) { 30 | if err := HalfClose(conn); err != nil { 31 | conn.Close() 32 | return 33 | } 34 | 35 | p.pool.Put(conn) 36 | } 37 | 38 | type PoolConn struct { 39 | *Snell 40 | pool *Pool 41 | } 42 | 43 | func (pc *PoolConn) Read(b []byte) (int, error) { 44 | // save old status of reply (it mutable by Read) 45 | reply := pc.Snell.reply 46 | 47 | n, err := pc.Snell.Read(b) 48 | if err == shadowaead.ErrZeroChunk { 49 | // if reply is false, it should be client halfclose. 50 | // ignore error and read data again. 51 | if !reply { 52 | pc.Snell.reply = false 53 | return pc.Snell.Read(b) 54 | } 55 | } 56 | return n, err 57 | } 58 | 59 | func (pc *PoolConn) Write(b []byte) (int, error) { 60 | return pc.Snell.Write(b) 61 | } 62 | 63 | func (pc *PoolConn) Close() error { 64 | // clash use SetReadDeadline to break bidirectional copy between client and server. 65 | // reset it before reuse connection to avoid io timeout error. 66 | pc.Snell.Conn.SetReadDeadline(time.Time{}) 67 | pc.pool.Put(pc.Snell) 68 | return nil 69 | } 70 | 71 | func NewPool(factory func(context.Context) (*Snell, error)) *Pool { 72 | p := pool.New( 73 | func(ctx context.Context) (any, error) { 74 | return factory(ctx) 75 | }, 76 | pool.WithAge(15000), 77 | pool.WithSize(10), 78 | pool.WithEvict(func(item any) { 79 | item.(*Snell).Close() 80 | }), 81 | ) 82 | 83 | return &Pool{p} 84 | } 85 | -------------------------------------------------------------------------------- /transport/ssr/obfs/base.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | type Base struct { 4 | Host string 5 | Port int 6 | Key []byte 7 | IVSize int 8 | Param string 9 | } 10 | -------------------------------------------------------------------------------- /transport/ssr/obfs/http_post.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | func init() { 4 | register("http_post", newHTTPPost, 0) 5 | } 6 | 7 | func newHTTPPost(b *Base) Obfs { 8 | return &httpObfs{Base: b, post: true} 9 | } 10 | -------------------------------------------------------------------------------- /transport/ssr/obfs/obfs.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | var ( 10 | errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") 11 | errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") 12 | errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") 13 | ) 14 | 15 | type authData struct { 16 | clientID [32]byte 17 | } 18 | 19 | type Obfs interface { 20 | StreamConn(net.Conn) net.Conn 21 | } 22 | 23 | type obfsCreator func(b *Base) Obfs 24 | 25 | var obfsList = make(map[string]struct { 26 | overhead int 27 | new obfsCreator 28 | }) 29 | 30 | func register(name string, c obfsCreator, o int) { 31 | obfsList[name] = struct { 32 | overhead int 33 | new obfsCreator 34 | }{overhead: o, new: c} 35 | } 36 | 37 | func PickObfs(name string, b *Base) (Obfs, int, error) { 38 | if choice, ok := obfsList[name]; ok { 39 | return choice.new(b), choice.overhead, nil 40 | } 41 | return nil, 0, fmt.Errorf("Obfs %s not supported", name) 42 | } 43 | -------------------------------------------------------------------------------- /transport/ssr/obfs/plain.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import "net" 4 | 5 | type plain struct{} 6 | 7 | func init() { 8 | register("plain", newPlain, 0) 9 | } 10 | 11 | func newPlain(b *Base) Obfs { 12 | return &plain{} 13 | } 14 | 15 | func (p *plain) StreamConn(c net.Conn) net.Conn { return c } 16 | -------------------------------------------------------------------------------- /transport/ssr/obfs/random_head.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "hash/crc32" 7 | mathRand "math/rand" 8 | "net" 9 | 10 | "github.com/Dreamacro/clash/common/pool" 11 | ) 12 | 13 | func init() { 14 | register("random_head", newRandomHead, 0) 15 | } 16 | 17 | type randomHead struct { 18 | *Base 19 | } 20 | 21 | func newRandomHead(b *Base) Obfs { 22 | return &randomHead{Base: b} 23 | } 24 | 25 | type randomHeadConn struct { 26 | net.Conn 27 | *randomHead 28 | hasSentHeader bool 29 | rawTransSent bool 30 | rawTransRecv bool 31 | buf []byte 32 | } 33 | 34 | func (r *randomHead) StreamConn(c net.Conn) net.Conn { 35 | return &randomHeadConn{Conn: c, randomHead: r} 36 | } 37 | 38 | func (c *randomHeadConn) Read(b []byte) (int, error) { 39 | if c.rawTransRecv { 40 | return c.Conn.Read(b) 41 | } 42 | buf := pool.Get(pool.RelayBufferSize) 43 | defer pool.Put(buf) 44 | c.Conn.Read(buf) 45 | c.rawTransRecv = true 46 | c.Write(nil) 47 | return 0, nil 48 | } 49 | 50 | func (c *randomHeadConn) Write(b []byte) (int, error) { 51 | if c.rawTransSent { 52 | return c.Conn.Write(b) 53 | } 54 | c.buf = append(c.buf, b...) 55 | if !c.hasSentHeader { 56 | c.hasSentHeader = true 57 | dataLength := mathRand.Intn(96) + 4 58 | buf := pool.Get(dataLength + 4) 59 | defer pool.Put(buf) 60 | rand.Read(buf[:dataLength]) 61 | binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength])) 62 | _, err := c.Conn.Write(buf) 63 | return len(b), err 64 | } 65 | if c.rawTransRecv { 66 | _, err := c.Conn.Write(c.buf) 67 | c.buf = nil 68 | c.rawTransSent = true 69 | return len(b), err 70 | } 71 | return len(b), nil 72 | } 73 | -------------------------------------------------------------------------------- /transport/ssr/protocol/auth_aes128_md5.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "github.com/Dreamacro/clash/transport/ssr/tools" 4 | 5 | func init() { 6 | register("auth_aes128_md5", newAuthAES128MD5, 9) 7 | } 8 | 9 | func newAuthAES128MD5(b *Base) Protocol { 10 | a := &authAES128{ 11 | Base: b, 12 | authData: &authData{}, 13 | authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, 14 | userData: &userData{}, 15 | } 16 | a.initUserData() 17 | return a 18 | } 19 | -------------------------------------------------------------------------------- /transport/ssr/protocol/base.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "encoding/binary" 10 | mathRand "math/rand" 11 | "sync" 12 | "time" 13 | 14 | "github.com/Dreamacro/clash/common/pool" 15 | "github.com/Dreamacro/clash/log" 16 | "github.com/Dreamacro/clash/transport/shadowsocks/core" 17 | ) 18 | 19 | type Base struct { 20 | Key []byte 21 | Overhead int 22 | Param string 23 | } 24 | 25 | type userData struct { 26 | userKey []byte 27 | userID [4]byte 28 | } 29 | 30 | type authData struct { 31 | clientID [4]byte 32 | connectionID uint32 33 | mutex sync.Mutex 34 | } 35 | 36 | func (a *authData) next() *authData { 37 | r := &authData{} 38 | a.mutex.Lock() 39 | defer a.mutex.Unlock() 40 | if a.connectionID > 0xff000000 || a.connectionID == 0 { 41 | rand.Read(a.clientID[:]) 42 | a.connectionID = mathRand.Uint32() & 0xffffff 43 | } 44 | a.connectionID++ 45 | copy(r.clientID[:], a.clientID[:]) 46 | r.connectionID = a.connectionID 47 | return r 48 | } 49 | 50 | func (a *authData) putAuthData(buf *bytes.Buffer) { 51 | binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix())) 52 | buf.Write(a.clientID[:]) 53 | binary.Write(buf, binary.LittleEndian, a.connectionID) 54 | } 55 | 56 | func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error { 57 | encrypt := pool.Get(16) 58 | defer pool.Put(encrypt) 59 | binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix())) 60 | copy(encrypt[4:], a.clientID[:]) 61 | binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) 62 | binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0])) 63 | binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1])) 64 | 65 | cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16) 66 | block, err := aes.NewCipher(cipherKey) 67 | if err != nil { 68 | log.Warnln("New cipher error: %s", err.Error()) 69 | return err 70 | } 71 | iv := bytes.Repeat([]byte{0}, 16) 72 | cbcCipher := cipher.NewCBCEncrypter(block, iv) 73 | 74 | cbcCipher.CryptBlocks(encrypt, encrypt) 75 | 76 | b.Write(encrypt) 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /transport/ssr/protocol/origin.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | ) 7 | 8 | type origin struct{} 9 | 10 | func init() { register("origin", newOrigin, 0) } 11 | 12 | func newOrigin(b *Base) Protocol { return &origin{} } 13 | 14 | func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } 15 | 16 | func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c } 17 | 18 | func (o *origin) Decode(dst, src *bytes.Buffer) error { 19 | dst.ReadFrom(src) 20 | return nil 21 | } 22 | 23 | func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { 24 | buf.Write(b) 25 | return nil 26 | } 27 | 28 | func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } 29 | 30 | func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { 31 | buf.Write(b) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /transport/ssr/protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | type PacketConn struct { 10 | net.PacketConn 11 | Protocol 12 | } 13 | 14 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 15 | buf := pool.GetBuffer() 16 | defer pool.PutBuffer(buf) 17 | err := c.EncodePacket(buf, b) 18 | if err != nil { 19 | return 0, err 20 | } 21 | _, err = c.PacketConn.WriteTo(buf.Bytes(), addr) 22 | return len(b), err 23 | } 24 | 25 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 26 | n, addr, err := c.PacketConn.ReadFrom(b) 27 | if err != nil { 28 | return n, addr, err 29 | } 30 | decoded, err := c.DecodePacket(b[:n]) 31 | if err != nil { 32 | return n, addr, err 33 | } 34 | copy(b, decoded) 35 | return len(decoded), addr, nil 36 | } 37 | -------------------------------------------------------------------------------- /transport/ssr/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | ) 10 | 11 | var ( 12 | errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32") 13 | errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length") 14 | errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32") 15 | errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac") 16 | errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length") 17 | errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum") 18 | errAuthChainLengthError = errors.New("auth_chain decode data wrong length") 19 | errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum") 20 | ) 21 | 22 | type Protocol interface { 23 | StreamConn(net.Conn, []byte) net.Conn 24 | PacketConn(net.PacketConn) net.PacketConn 25 | Decode(dst, src *bytes.Buffer) error 26 | Encode(buf *bytes.Buffer, b []byte) error 27 | DecodePacket([]byte) ([]byte, error) 28 | EncodePacket(buf *bytes.Buffer, b []byte) error 29 | } 30 | 31 | type protocolCreator func(b *Base) Protocol 32 | 33 | var protocolList = make(map[string]struct { 34 | overhead int 35 | new protocolCreator 36 | }) 37 | 38 | func register(name string, c protocolCreator, o int) { 39 | protocolList[name] = struct { 40 | overhead int 41 | new protocolCreator 42 | }{overhead: o, new: c} 43 | } 44 | 45 | func PickProtocol(name string, b *Base) (Protocol, error) { 46 | if choice, ok := protocolList[name]; ok { 47 | b.Overhead += choice.overhead 48 | return choice.new(b), nil 49 | } 50 | return nil, fmt.Errorf("protocol %s not supported", name) 51 | } 52 | 53 | func getHeadSize(b []byte, defaultValue int) int { 54 | if len(b) < 2 { 55 | return defaultValue 56 | } 57 | headType := b[0] & 7 58 | switch headType { 59 | case 1: 60 | return 7 61 | case 4: 62 | return 19 63 | case 3: 64 | return 4 + int(b[1]) 65 | } 66 | return defaultValue 67 | } 68 | 69 | func getDataLength(b []byte) int { 70 | bLength := len(b) 71 | dataLength := getHeadSize(b, 30) + rand.Intn(32) 72 | if bLength < dataLength { 73 | return bLength 74 | } 75 | return dataLength 76 | } 77 | -------------------------------------------------------------------------------- /transport/ssr/protocol/stream.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/common/pool" 8 | ) 9 | 10 | type Conn struct { 11 | net.Conn 12 | Protocol 13 | decoded bytes.Buffer 14 | underDecoded bytes.Buffer 15 | } 16 | 17 | func (c *Conn) Read(b []byte) (int, error) { 18 | if c.decoded.Len() > 0 { 19 | return c.decoded.Read(b) 20 | } 21 | 22 | buf := pool.Get(pool.RelayBufferSize) 23 | defer pool.Put(buf) 24 | n, err := c.Conn.Read(buf) 25 | if err != nil { 26 | return 0, err 27 | } 28 | c.underDecoded.Write(buf[:n]) 29 | err = c.Decode(&c.decoded, &c.underDecoded) 30 | if err != nil { 31 | return 0, err 32 | } 33 | n, _ = c.decoded.Read(b) 34 | return n, nil 35 | } 36 | 37 | func (c *Conn) Write(b []byte) (int, error) { 38 | bLength := len(b) 39 | buf := pool.GetBuffer() 40 | defer pool.PutBuffer(buf) 41 | err := c.Encode(buf, b) 42 | if err != nil { 43 | return 0, err 44 | } 45 | _, err = c.Conn.Write(buf.Bytes()) 46 | if err != nil { 47 | return 0, err 48 | } 49 | return bLength, nil 50 | } 51 | -------------------------------------------------------------------------------- /transport/ssr/tools/bufPool.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io" 7 | ) 8 | 9 | func AppendRandBytes(b *bytes.Buffer, length int) { 10 | b.ReadFrom(io.LimitReader(rand.Reader, int64(length))) 11 | } 12 | -------------------------------------------------------------------------------- /transport/ssr/tools/crypto.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | ) 8 | 9 | const HmacSHA1Len = 10 10 | 11 | func HmacMD5(key, data []byte) []byte { 12 | hmacMD5 := hmac.New(md5.New, key) 13 | hmacMD5.Write(data) 14 | return hmacMD5.Sum(nil) 15 | } 16 | 17 | func HmacSHA1(key, data []byte) []byte { 18 | hmacSHA1 := hmac.New(sha1.New, key) 19 | hmacSHA1.Write(data) 20 | return hmacSHA1.Sum(nil) 21 | } 22 | 23 | func MD5Sum(b []byte) []byte { 24 | h := md5.New() 25 | h.Write(b) 26 | return h.Sum(nil) 27 | } 28 | 29 | func SHA1Sum(b []byte) []byte { 30 | h := sha1.New() 31 | h.Write(b) 32 | return h.Sum(nil) 33 | } 34 | -------------------------------------------------------------------------------- /transport/ssr/tools/random.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | // XorShift128Plus - a pseudorandom number generator 10 | type XorShift128Plus struct { 11 | s [2]uint64 12 | } 13 | 14 | func (r *XorShift128Plus) Next() uint64 { 15 | x := r.s[0] 16 | y := r.s[1] 17 | r.s[0] = y 18 | x ^= x << 23 19 | x ^= y ^ (x >> 17) ^ (y >> 26) 20 | r.s[1] = x 21 | return x + y 22 | } 23 | 24 | func (r *XorShift128Plus) InitFromBin(bin []byte) { 25 | var full []byte 26 | if len(bin) < 16 { 27 | full := pool.Get(16)[:0] 28 | defer pool.Put(full) 29 | full = append(full, bin...) 30 | for len(full) < 16 { 31 | full = append(full, 0) 32 | } 33 | } else { 34 | full = bin 35 | } 36 | r.s[0] = binary.LittleEndian.Uint64(full[:8]) 37 | r.s[1] = binary.LittleEndian.Uint64(full[8:16]) 38 | } 39 | 40 | func (r *XorShift128Plus) InitFromBinAndLength(bin []byte, length int) { 41 | var full []byte 42 | if len(bin) < 16 { 43 | full := pool.Get(16)[:0] 44 | defer pool.Put(full) 45 | full = append(full, bin...) 46 | for len(full) < 16 { 47 | full = append(full, 0) 48 | } 49 | } 50 | full = bin 51 | binary.LittleEndian.PutUint16(full, uint16(length)) 52 | r.s[0] = binary.LittleEndian.Uint64(full[:8]) 53 | r.s[1] = binary.LittleEndian.Uint64(full[8:16]) 54 | for i := 0; i < 4; i++ { 55 | r.Next() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /transport/v2ray-plugin/websocket.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/Dreamacro/clash/transport/vmess" 9 | ) 10 | 11 | // Option is options of websocket obfs 12 | type Option struct { 13 | Host string 14 | Port string 15 | Path string 16 | Headers map[string]string 17 | TLS bool 18 | SkipCertVerify bool 19 | Mux bool 20 | } 21 | 22 | // NewV2rayObfs return a HTTPObfs 23 | func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { 24 | header := http.Header{} 25 | for k, v := range option.Headers { 26 | header.Add(k, v) 27 | } 28 | 29 | config := &vmess.WebsocketConfig{ 30 | Host: option.Host, 31 | Port: option.Port, 32 | Path: option.Path, 33 | Headers: header, 34 | } 35 | 36 | if option.TLS { 37 | config.TLS = true 38 | config.TLSConfig = &tls.Config{ 39 | ServerName: option.Host, 40 | InsecureSkipVerify: option.SkipCertVerify, 41 | NextProtos: []string{"http/1.1"}, 42 | } 43 | if host := config.Headers.Get("Host"); host != "" { 44 | config.TLSConfig.ServerName = host 45 | } 46 | } 47 | 48 | var err error 49 | conn, err = vmess.StreamWebsocketConn(conn, config) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | if option.Mux { 55 | conn = NewMux(conn, MuxOption{ 56 | ID: [2]byte{0, 0}, 57 | Host: "127.0.0.1", 58 | Port: 0, 59 | }) 60 | } 61 | return conn, nil 62 | } 63 | -------------------------------------------------------------------------------- /transport/vmess/http.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | "net/http" 10 | "net/textproto" 11 | ) 12 | 13 | type httpConn struct { 14 | net.Conn 15 | cfg *HTTPConfig 16 | reader *bufio.Reader 17 | whandshake bool 18 | } 19 | 20 | type HTTPConfig struct { 21 | Method string 22 | Host string 23 | Path []string 24 | Headers map[string][]string 25 | } 26 | 27 | // Read implements net.Conn.Read() 28 | func (hc *httpConn) Read(b []byte) (int, error) { 29 | if hc.reader != nil { 30 | n, err := hc.reader.Read(b) 31 | return n, err 32 | } 33 | 34 | reader := textproto.NewConn(hc.Conn) 35 | // First line: GET /index.html HTTP/1.0 36 | if _, err := reader.ReadLine(); err != nil { 37 | return 0, err 38 | } 39 | 40 | if _, err := reader.ReadMIMEHeader(); err != nil { 41 | return 0, err 42 | } 43 | 44 | hc.reader = reader.R 45 | return reader.R.Read(b) 46 | } 47 | 48 | // Write implements io.Writer. 49 | func (hc *httpConn) Write(b []byte) (int, error) { 50 | if hc.whandshake { 51 | return hc.Conn.Write(b) 52 | } 53 | 54 | path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] 55 | host := hc.cfg.Host 56 | if header := hc.cfg.Headers["Host"]; len(header) != 0 { 57 | host = header[rand.Intn(len(header))] 58 | } 59 | 60 | u := fmt.Sprintf("http://%s%s", host, path) 61 | req, _ := http.NewRequest(http.MethodGet, u, bytes.NewBuffer(b)) 62 | for key, list := range hc.cfg.Headers { 63 | req.Header.Set(key, list[rand.Intn(len(list))]) 64 | } 65 | req.ContentLength = int64(len(b)) 66 | if err := req.Write(hc.Conn); err != nil { 67 | return 0, err 68 | } 69 | hc.whandshake = true 70 | return len(b), nil 71 | } 72 | 73 | func (hc *httpConn) Close() error { 74 | return hc.Conn.Close() 75 | } 76 | 77 | func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn { 78 | return &httpConn{ 79 | Conn: conn, 80 | cfg: cfg, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /transport/vmess/tls.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "net" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type TLSConfig struct { 12 | Host string 13 | SkipCertVerify bool 14 | NextProtos []string 15 | } 16 | 17 | func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { 18 | tlsConfig := &tls.Config{ 19 | ServerName: cfg.Host, 20 | InsecureSkipVerify: cfg.SkipCertVerify, 21 | NextProtos: cfg.NextProtos, 22 | } 23 | 24 | tlsConn := tls.Client(conn, tlsConfig) 25 | 26 | // fix tls handshake not timeout 27 | ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) 28 | defer cancel() 29 | err := tlsConn.HandshakeContext(ctx) 30 | return tlsConn, err 31 | } 32 | -------------------------------------------------------------------------------- /transport/vmess/user.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | 7 | "github.com/gofrs/uuid/v5" 8 | ) 9 | 10 | // ID cmdKey length 11 | const ( 12 | IDBytesLen = 16 13 | ) 14 | 15 | // The ID of en entity, in the form of a UUID. 16 | type ID struct { 17 | UUID *uuid.UUID 18 | CmdKey []byte 19 | } 20 | 21 | // newID returns an ID with given UUID. 22 | func newID(uuid *uuid.UUID) *ID { 23 | id := &ID{UUID: uuid, CmdKey: make([]byte, IDBytesLen)} 24 | md5hash := md5.New() 25 | md5hash.Write(uuid.Bytes()) 26 | md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) 27 | md5hash.Sum(id.CmdKey[:0]) 28 | return id 29 | } 30 | 31 | func nextID(u *uuid.UUID) *uuid.UUID { 32 | md5hash := md5.New() 33 | md5hash.Write(u.Bytes()) 34 | md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) 35 | var newid uuid.UUID 36 | for { 37 | md5hash.Sum(newid[:0]) 38 | if !bytes.Equal(newid.Bytes(), u.Bytes()) { 39 | return &newid 40 | } 41 | md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) 42 | } 43 | } 44 | 45 | func newAlterIDs(primary *ID, alterIDCount uint16) []*ID { 46 | alterIDs := make([]*ID, alterIDCount) 47 | prevID := primary.UUID 48 | for idx := range alterIDs { 49 | newid := nextID(prevID) 50 | alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]} 51 | prevID = newid 52 | } 53 | alterIDs = append(alterIDs, primary) 54 | return alterIDs 55 | } 56 | -------------------------------------------------------------------------------- /tunnel/connection.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/netip" 7 | "time" 8 | 9 | N "github.com/Dreamacro/clash/common/net" 10 | "github.com/Dreamacro/clash/common/pool" 11 | C "github.com/Dreamacro/clash/constant" 12 | ) 13 | 14 | func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { 15 | addr := metadata.UDPAddr() 16 | if addr == nil { 17 | return errors.New("udp addr invalid") 18 | } 19 | 20 | if _, err := pc.WriteTo(packet.Data(), addr); err != nil { 21 | return err 22 | } 23 | // reset timeout 24 | pc.SetReadDeadline(time.Now().Add(udpTimeout)) 25 | 26 | return nil 27 | } 28 | 29 | func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, oAddr, fAddr netip.Addr) { 30 | buf := pool.Get(pool.UDPBufferSize) 31 | defer pool.Put(buf) 32 | defer natTable.Delete(key) 33 | defer pc.Close() 34 | 35 | for { 36 | pc.SetReadDeadline(time.Now().Add(udpTimeout)) 37 | n, from, err := pc.ReadFrom(buf) 38 | if err != nil { 39 | return 40 | } 41 | 42 | fromUDPAddr := *from.(*net.UDPAddr) 43 | if fAddr.IsValid() { 44 | fromAddr, _ := netip.AddrFromSlice(fromUDPAddr.IP) 45 | fromAddr = fromAddr.Unmap() 46 | if oAddr == fromAddr { 47 | fromUDPAddr.IP = fAddr.AsSlice() 48 | } 49 | } 50 | 51 | _, err = packet.WriteBack(buf[:n], &fromUDPAddr) 52 | if err != nil { 53 | return 54 | } 55 | } 56 | } 57 | 58 | func handleSocket(ctx C.ConnContext, outbound net.Conn) { 59 | N.Relay(ctx.Conn(), outbound) 60 | } 61 | -------------------------------------------------------------------------------- /tunnel/mode.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | type TunnelMode int 10 | 11 | // ModeMapping is a mapping for Mode enum 12 | var ModeMapping = map[string]TunnelMode{ 13 | Global.String(): Global, 14 | Rule.String(): Rule, 15 | Direct.String(): Direct, 16 | } 17 | 18 | const ( 19 | Global TunnelMode = iota 20 | Rule 21 | Direct 22 | ) 23 | 24 | // UnmarshalJSON unserialize Mode 25 | func (m *TunnelMode) UnmarshalJSON(data []byte) error { 26 | var tp string 27 | json.Unmarshal(data, &tp) 28 | mode, exist := ModeMapping[strings.ToLower(tp)] 29 | if !exist { 30 | return errors.New("invalid mode") 31 | } 32 | *m = mode 33 | return nil 34 | } 35 | 36 | // UnmarshalYAML unserialize Mode with yaml 37 | func (m *TunnelMode) UnmarshalYAML(unmarshal func(any) error) error { 38 | var tp string 39 | unmarshal(&tp) 40 | mode, exist := ModeMapping[strings.ToLower(tp)] 41 | if !exist { 42 | return errors.New("invalid mode") 43 | } 44 | *m = mode 45 | return nil 46 | } 47 | 48 | // MarshalJSON serialize Mode 49 | func (m TunnelMode) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(m.String()) 51 | } 52 | 53 | // MarshalYAML serialize TunnelMode with yaml 54 | func (m TunnelMode) MarshalYAML() (any, error) { 55 | return m.String(), nil 56 | } 57 | 58 | func (m TunnelMode) String() string { 59 | switch m { 60 | case Global: 61 | return "global" 62 | case Rule: 63 | return "rule" 64 | case Direct: 65 | return "direct" 66 | default: 67 | return "Unknown" 68 | } 69 | } 70 | --------------------------------------------------------------------------------