├── .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 │ └── test.yaml ├── .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 │ ├── vless.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 └── util │ └── manipulation.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 │ └── locales │ │ ├── en_US.ts │ │ ├── index.ts │ │ ├── side_bar.ts │ │ └── zh_CN.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 └── zh_CN │ ├── advanced-usages │ ├── golang-api.md │ ├── openconnect.md │ └── wireguard.md │ ├── 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 │ ├── 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 │ └── 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 │ ├── inbounds.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 └── 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 ├── parser_test.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 │ ├── vless-grpc.json │ ├── vless-http.json │ ├── vless-http2.json │ ├── vless-tls.json │ ├── vless-ws-0rtt.json │ ├── vless-ws-tls-zero.json │ ├── vless-ws-tls.json │ ├── vless-ws.json │ ├── vless.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 ├── listener_test.go ├── main.go ├── rule_test.go ├── snell_test.go ├── ss_test.go ├── trojan_test.go ├── util.go ├── vless_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/zh_CN/ 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.21' 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.21' 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: Build 27 | if: startsWith(github.ref, 'refs/tags/') 28 | env: 29 | NAME: clash 30 | BINDIR: bin 31 | run: make -j $(go run ./test/main.go) releases 32 | 33 | - name: Upload Release 34 | uses: softprops/action-gh-release@v1 35 | if: startsWith(github.ref, 'refs/tags/') 36 | with: 37 | files: bin/* 38 | draft: true 39 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Setup Go 11 | uses: actions/setup-go@v4 12 | with: 13 | check-latest: true 14 | go-version: '1.21' 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v3 18 | 19 | - name: Cache go module 20 | uses: actions/cache@v3 21 | with: 22 | path: | 23 | ~/go/pkg/mod 24 | ~/.cache/go-build 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | 29 | - name: Get dependencies, run test 30 | run: | 31 | go test ./... 32 | 33 | build-test: 34 | name: Build Test 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Setup Go 38 | uses: actions/setup-go@v4 39 | with: 40 | check-latest: true 41 | go-version: '1.21' 42 | 43 | - name: Check out code into the Go module directory 44 | uses: actions/checkout@v3 45 | 46 | - name: Cache go module 47 | uses: actions/cache@v3 48 | with: 49 | path: | 50 | ~/go/pkg/mod 51 | ~/.cache/go-build 52 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 53 | restore-keys: | 54 | ${{ runner.os }}-go- 55 | 56 | - name: Build 57 | env: 58 | NAME: clash 59 | BINDIR: bin 60 | run: make -j $(go run ./test/main.go) all 61 | -------------------------------------------------------------------------------- /.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 | - exhaustive 15 | 16 | linters-settings: 17 | gci: 18 | custom-order: true 19 | sections: 20 | - standard 21 | - prefix(github.com/Dreamacro/clash) 22 | - default 23 | staticcheck: 24 | go: '1.21' 25 | exhaustive: 26 | default-signifies-exhaustive: true 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM} golang:alpine as builder 2 | 3 | RUN apk add --no-cache make git ca-certificates && \ 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 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 19 | COPY --from=builder /Country.mmdb /root/.config/clash/ 20 | COPY --from=builder /clash / 21 | ENTRYPOINT ["/clash"] 22 | -------------------------------------------------------------------------------- /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); err == nil { 18 | metadata.SrcIP = ip 19 | metadata.SrcPort = C.Port(port) 20 | } 21 | if originTarget != nil { 22 | if addrPort, err := netip.ParseAddrPort(originTarget.String()); err == nil { 23 | metadata.OriginDst = addrPort 24 | } 25 | } 26 | return context.NewConnContext(conn, metadata) 27 | } 28 | -------------------------------------------------------------------------------- /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()); err == nil { 17 | metadata.SrcIP = ip 18 | metadata.SrcPort = C.Port(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()); err == nil { 28 | metadata.SrcIP = ip 29 | metadata.SrcPort = C.Port(port) 30 | } 31 | if originTarget != nil { 32 | if addrPort, err := netip.ParseAddrPort(originTarget.String()); err == nil { 33 | metadata.OriginDst = addrPort 34 | } 35 | } 36 | return &PacketAdapter{ 37 | UDPPacket: packet, 38 | metadata: metadata, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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()); err == nil { 18 | metadata.SrcIP = ip 19 | metadata.SrcPort = C.Port(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 | "fmt" 5 | "net" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/Dreamacro/clash/common/util" 11 | C "github.com/Dreamacro/clash/constant" 12 | "github.com/Dreamacro/clash/transport/socks5" 13 | ) 14 | 15 | func parseSocksAddr(target socks5.Addr) *C.Metadata { 16 | metadata := &C.Metadata{} 17 | 18 | switch target[0] { 19 | case socks5.AtypDomainName: 20 | // trim for FQDN 21 | metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") 22 | metadata.DstPort = C.Port((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) 23 | case socks5.AtypIPv4: 24 | ip := net.IP(target[1 : 1+net.IPv4len]) 25 | metadata.DstIP = ip 26 | metadata.DstPort = C.Port((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) 27 | case socks5.AtypIPv6: 28 | ip := net.IP(target[1 : 1+net.IPv6len]) 29 | metadata.DstIP = ip 30 | metadata.DstPort = C.Port((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) 31 | } 32 | 33 | return metadata 34 | } 35 | 36 | func parseHTTPAddr(request *http.Request) *C.Metadata { 37 | host := request.URL.Hostname() 38 | port, _ := strconv.ParseUint(util.EmptyOr(request.URL.Port(), "80"), 10, 16) 39 | 40 | // trim FQDN (#737) 41 | host = strings.TrimRight(host, ".") 42 | 43 | metadata := &C.Metadata{ 44 | NetWork: C.TCP, 45 | Host: host, 46 | DstIP: nil, 47 | DstPort: C.Port(port), 48 | } 49 | 50 | if ip := net.ParseIP(host); ip != nil { 51 | metadata.DstIP = ip 52 | } 53 | 54 | return metadata 55 | } 56 | 57 | func parseAddr(addr net.Addr) (net.IP, int, error) { 58 | switch a := addr.(type) { 59 | case *net.TCPAddr: 60 | return a.IP, a.Port, nil 61 | case *net.UDPAddr: 62 | return a.IP, a.Port, nil 63 | default: 64 | return nil, 0, fmt.Errorf("unknown address type %s", addr.String()) 65 | } 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/util.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/component/resolver" 8 | C "github.com/Dreamacro/clash/constant" 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | 11 | "github.com/Dreamacro/protobytes" 12 | ) 13 | 14 | func tcpKeepAlive(c net.Conn) { 15 | if tcp, ok := c.(*net.TCPConn); ok { 16 | tcp.SetKeepAlive(true) 17 | tcp.SetKeepAlivePeriod(30 * time.Second) 18 | } 19 | } 20 | 21 | func serializesSocksAddr(metadata *C.Metadata) []byte { 22 | buf := protobytes.BytesWriter{} 23 | 24 | addrType := metadata.AddrType() 25 | buf.PutUint8(uint8(addrType)) 26 | 27 | switch addrType { 28 | case socks5.AtypDomainName: 29 | buf.PutUint8(uint8(len(metadata.Host))) 30 | buf.PutString(metadata.Host) 31 | case socks5.AtypIPv4: 32 | buf.PutSlice(metadata.DstIP.To4()) 33 | case socks5.AtypIPv6: 34 | buf.PutSlice(metadata.DstIP.To16()) 35 | } 36 | 37 | buf.PutUint16be(uint16(metadata.DstPort)) 38 | return buf.Bytes() 39 | } 40 | 41 | func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { 42 | host, port, err := net.SplitHostPort(address) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | ip, err := resolver.ResolveIP(host) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 52 | } 53 | 54 | func safeConnClose(c net.Conn, err error) { 55 | if err != nil { 56 | c.Close() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /adapter/outbound/vless.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | type ( 4 | Vless = Vmess 5 | VlessOption = VmessOption 6 | ) 7 | 8 | func NewVless(option VlessOption) (*Vless, error) { 9 | return newVmess(option, true) 10 | } 11 | -------------------------------------------------------------------------------- /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 | "strconv" 7 | "time" 8 | 9 | C "github.com/Dreamacro/clash/constant" 10 | ) 11 | 12 | func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { 13 | host, port, err := net.SplitHostPort(rawAddress) 14 | if err != nil { 15 | err = fmt.Errorf("addrToMetadata failed: %w", err) 16 | return 17 | } 18 | 19 | ip := net.ParseIP(host) 20 | p, _ := strconv.ParseUint(port, 10, 16) 21 | if ip == nil { 22 | addr = &C.Metadata{ 23 | Host: host, 24 | DstIP: nil, 25 | DstPort: C.Port(p), 26 | } 27 | return 28 | } else if ip4 := ip.To4(); ip4 != nil { 29 | addr = &C.Metadata{ 30 | Host: "", 31 | DstIP: ip4, 32 | DstPort: C.Port(p), 33 | } 34 | return 35 | } 36 | 37 | addr = &C.Metadata{ 38 | Host: "", 39 | DstIP: ip, 40 | DstPort: C.Port(p), 41 | } 42 | return 43 | } 44 | 45 | func tcpKeepAlive(c net.Conn) { 46 | if tcp, ok := c.(*net.TCPConn); ok { 47 | tcp.SetKeepAlive(true) 48 | tcp.SetKeepAlivePeriod(30 * time.Second) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /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_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: 41 | return make([]byte, size) 42 | default: 43 | bits := msb(size) 44 | if size == 1< 65536 { 56 | return nil 57 | } 58 | 59 | bits := msb(cap(buf)) 60 | if 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 | -------------------------------------------------------------------------------- /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/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 | // 14 | // on Unix systems, `$HOME/.config/clash`. 15 | // on Windows, `%USERPROFILE%/.config/clash`. 16 | var Path = func() *path { 17 | homeDir, err := os.UserHomeDir() 18 | if err != nil { 19 | homeDir, _ = os.Getwd() 20 | } 21 | 22 | homeDir = P.Join(homeDir, ".config", Name) 23 | 24 | if _, err = os.Stat(homeDir); err != nil { 25 | if configHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok { 26 | homeDir = P.Join(configHome, Name) 27 | } 28 | } 29 | return &path{homeDir: homeDir, configFile: "config.yaml"} 30 | }() 31 | 32 | type path struct { 33 | homeDir string 34 | configFile string 35 | } 36 | 37 | // SetHomeDir is used to set the configuration path 38 | func SetHomeDir(root string) { 39 | Path.homeDir = root 40 | } 41 | 42 | // SetConfig is used to set the configuration file 43 | func SetConfig(file string) { 44 | Path.configFile = file 45 | } 46 | 47 | func (p *path) HomeDir() string { 48 | return p.homeDir 49 | } 50 | 51 | func (p *path) Config() string { 52 | return p.configFile 53 | } 54 | 55 | // Resolve return a absolute path or a relative path with homedir 56 | func (p *path) Resolve(path string) string { 57 | if !filepath.IsAbs(path) { 58 | return filepath.Join(p.HomeDir(), path) 59 | } 60 | 61 | return path 62 | } 63 | 64 | // IsSubPath return true if path is a subpath of homedir 65 | func (p *path) IsSubPath(path string) bool { 66 | homedir := p.HomeDir() 67 | path = p.Resolve(path) 68 | rel, err := filepath.Rel(homedir, path) 69 | if err != nil { 70 | return false 71 | } 72 | 73 | return !strings.Contains(rel, "..") 74 | } 75 | 76 | func (p *path) MMDB() string { 77 | return P.Join(p.homeDir, "Country.mmdb") 78 | } 79 | 80 | func (p *path) OldCache() string { 81 | return P.Join(p.homeDir, ".cache") 82 | } 83 | 84 | func (p *path) Cache() string { 85 | return P.Join(p.homeDir, "cache.db") 86 | } 87 | -------------------------------------------------------------------------------- /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/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/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | import locales from './locales' 3 | 4 | // https://vitepress.dev/reference/site-config 5 | export default defineConfig({ 6 | title: 'Clash', 7 | 8 | base: '/clash/', 9 | 10 | head: [ 11 | [ 12 | 'link', 13 | { rel: 'icon', type: "image/x-icon", href: '/clash/logo.png' } 14 | ], 15 | ], 16 | 17 | locales: locales.locales, 18 | 19 | lastUpdated: true, 20 | 21 | themeConfig: { 22 | search: { 23 | provider: 'local', 24 | options: { 25 | locales: { 26 | zh_CN: { 27 | translations: { 28 | button: { 29 | buttonText: '搜索文档', 30 | buttonAriaLabel: '搜索文档' 31 | }, 32 | modal: { 33 | noResultsText: '无法找到相关结果', 34 | resetButtonTitle: '清除查询条件', 35 | footer: { 36 | selectText: '选择', 37 | navigateText: '切换' 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | 44 | } 45 | } 46 | }, 47 | }) 48 | -------------------------------------------------------------------------------- /docs/.vitepress/locales/en_US.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | import { defineConfig } from 'vitepress' 3 | import { generateSidebarChapter } from './side_bar.js' 4 | 5 | const require = createRequire(import.meta.url) 6 | 7 | const chapters = generateSidebarChapter('en_US', new Map([ 8 | ['introduction', 'Introduction'], 9 | ['configuration', 'Configuration'], 10 | ['premium', 'Premium'], 11 | ['runtime', 'Runtime'], 12 | ['advanced-usages', 'Advanced Usages'], 13 | ])) 14 | 15 | export default defineConfig({ 16 | lang: 'en-US', 17 | 18 | description: 'A rule-based tunnel in Go.', 19 | 20 | themeConfig: { 21 | nav: nav(), 22 | 23 | logo: '/logo.png', 24 | 25 | lastUpdatedText: 'Last updated at', 26 | 27 | sidebar: chapters, 28 | 29 | socialLinks: [ 30 | { icon: 'github', link: 'https://github.com/Dreamacro/clash' }, 31 | ], 32 | 33 | editLink: { 34 | pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path', 35 | text: 'Edit this page on GitHub' 36 | }, 37 | 38 | outline: { 39 | level: 'deep', 40 | label: 'On this page', 41 | }, 42 | 43 | } 44 | }) 45 | 46 | function nav() { 47 | return [ 48 | { text: 'Home', link: '/' }, 49 | { text: 'Configuration', link: '/configuration/configuration-reference' }, 50 | { 51 | text: 'Download', 52 | items: [ 53 | { text: 'Open-source Edition', link: 'https://github.com/Dreamacro/clash/releases/' }, 54 | { text: 'Premium Edition', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' }, 55 | ] 56 | } 57 | ] 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /docs/.vitepress/locales/index.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | import en_US from './en_US' 3 | import zh_CN from './zh_CN' 4 | 5 | export default defineConfig({ 6 | locales: { 7 | root: { 8 | label: 'English', 9 | lang: en_US.lang, 10 | themeConfig: en_US.themeConfig, 11 | description: en_US.description 12 | }, 13 | zh_CN: { 14 | label: '简体中文', 15 | lang: zh_CN.lang, 16 | themeConfig: zh_CN.themeConfig, 17 | description: zh_CN.description 18 | } 19 | } 20 | }) -------------------------------------------------------------------------------- /docs/.vitepress/locales/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | import { defineConfig } from 'vitepress' 3 | import { generateSidebarChapter } from './side_bar.js' 4 | 5 | const require = createRequire(import.meta.url) 6 | 7 | const chapters = generateSidebarChapter('zh_CN', new Map([ 8 | ['introduction', '简介'], 9 | ['configuration', '配置'], 10 | ['premium', 'Premium 版本'], 11 | ['runtime', '运行时'], 12 | ['advanced-usages', '高级用法'], 13 | ])) 14 | 15 | export default defineConfig({ 16 | lang: 'zh-CN', 17 | 18 | description: '基于规则的 Go 网络隧道. ', 19 | 20 | themeConfig: { 21 | nav: nav(), 22 | 23 | logo: '/logo.png', 24 | 25 | lastUpdatedText: '最后更新于', 26 | 27 | sidebar: chapters, 28 | 29 | socialLinks: [ 30 | { icon: 'github', link: 'https://github.com/Dreamacro/clash' }, 31 | ], 32 | 33 | editLink: { 34 | pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path', 35 | text: '在 GitHub 中编辑此页面' 36 | }, 37 | 38 | docFooter: { prev: '上一篇', next: '下一篇' }, 39 | 40 | outline: { 41 | level: 'deep', 42 | label: '页面导航', 43 | }, 44 | 45 | } 46 | }) 47 | 48 | function nav() { 49 | return [ 50 | { text: '主页', link: '/zh_CN/' }, 51 | { text: '配置', link: '/zh_CN/configuration/configuration-reference' }, 52 | { 53 | text: '下载', 54 | items: [ 55 | { text: 'GitHub 开源版', link: 'https://github.com/Dreamacro/clash/releases/' }, 56 | { text: 'Premium 版本', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' }, 57 | ] 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /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/Ieooo/clash/d034a408be42815e98f3aea80be24949946aea83/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](./outbound#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/Ieooo/clash/d034a408be42815e98f3aea80be24949946aea83/docs/logo.png -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "scripts": { 4 | "docs:dev": "vitepress dev", 5 | "docs:build": "vitepress build", 6 | "docs:preview": "vitepress preview" 7 | }, 8 | "devDependencies": { 9 | "@types/node": "^20.5.0", 10 | "directory-tree": "^3.5.1", 11 | "markdown-yaml-metadata-parser": "^3.0.0", 12 | "vitepress": "1.0.0-rc.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /docs/zh_CN/advanced-usages/golang-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 在 Golang 程序中集成 Clash 3 | sidebarOrder: 3 4 | --- 5 | 6 | # 在 Golang 程序中集成 Clash 7 | 8 | 如果 Clash 不能满足您的需求, 您可以在自己的 Golang 代码中使用 Clash. 9 | 10 | 目前已经有基本的支持: 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("请求从 %s 传入到 %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 错误: %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/zh_CN/advanced-usages/wireguard.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 基于规则的 Wireguard 3 | sidebarOrder: 1 4 | --- 5 | 6 | # 基于规则的 Wireguard 7 | 8 | 假设您的内核支持 Wireguard 并且您已经启用了它. `Table` 选项可以阻止 _wg-quick_ 覆写默认路由. 9 | 10 | 例如 `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 | 然后在 Clash 中您只需要有一个 DIRECT 策略组, 它包含一个指定的出站接口: 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 | 这通常比 Clash 自己实现的用户空间 Wireguard 客户端性能更好. Wireguard 在内核中支持. 41 | -------------------------------------------------------------------------------- /docs/zh_CN/configuration/inbound.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: Inbound 入站 3 | sidebarOrder: 3 4 | --- 5 | 6 | # Inbound 入站 7 | 8 | Clash 支持多种入站协议, 包括: 9 | 10 | - SOCKS5 11 | - HTTP(S) 12 | - Redirect TCP 13 | - TProxy TCP 14 | - TProxy UDP 15 | - Linux TUN 设备 (仅 Premium 版本) 16 | 17 | 任何入站协议的连接都将由同一个内部规则匹配引擎处理. 也就是说, Clash **目前**不支持为不同的入站协议设置不同的规则集. 18 | 19 | ## 配置 20 | 21 | ```yaml 22 | # HTTP(S) 代理服务端口 23 | # port: 7890 24 | 25 | # SOCKS5 代理服务端口 26 | socks-port: 7891 27 | 28 | # HTTP(S) 和 SOCKS4(A)/SOCKS5 代理服务共用一个端口 29 | mixed-port: 7890 30 | 31 | # Linux 和 macOS 的透明代理服务端口 (TCP 和 TProxy UDP 重定向) 32 | # redir-port: 7892 33 | 34 | # Linux 的透明代理服务端口 (TProxy TCP 和 TProxy UDP) 35 | # tproxy-port: 7893 36 | 37 | # 设置为 true 以允许来自其他 LAN IP 地址的连接 38 | # allow-lan: false 39 | ``` 40 | 41 | ## Mixed 混合端口 42 | 43 | 混合端口是一个特殊的端口, 它同时支持 HTTP(S) 和 SOCKS5 协议. 您可以使用任何支持 HTTP 或 SOCKS 代理的程序连接到这个端口, 例如: 44 | 45 | ```shell 46 | $ curl -x socks5h://127.0.0.1:7890 -v http://connect.rom.miui.com/generate_204 47 | * Trying 127.0.0.1:7890... 48 | * SOCKS5 connect to connect.rom.miui.com:80 (remotely resolved) 49 | * SOCKS5 request granted. 50 | * Connected to (nil) (127.0.0.1) port 7890 (#0) 51 | > GET /generate_204 HTTP/1.1 52 | > Host: connect.rom.miui.com 53 | > User-Agent: curl/7.81.0 54 | > Accept: */* 55 | > 56 | * Mark bundle as not supporting multiuse 57 | < HTTP/1.1 204 No Content 58 | < Date: Thu, 11 May 2023 06:18:22 GMT 59 | < Connection: keep-alive 60 | < Content-Type: text/plain 61 | < 62 | * Connection #0 to host (nil) left intact 63 | ``` 64 | 65 | ## Redirect 和 TProxy 66 | 67 | Redirect 和 TProxy 是两种实现透明代理的不同方式, 均被 Clash 所支持. 68 | 69 | 然而, 您不一定需要手动设置这两个功能 - 我们建议您使用 [Clash Premium 版本](/zh_CN/premium/introduction) 来配置透明代理, 因为它内置了对操作系统路由表、规则和 nftables 的自动管理. 70 | -------------------------------------------------------------------------------- /docs/zh_CN/configuration/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 介绍 3 | sidebarOrder: 1 4 | --- 5 | 6 | # 介绍 7 | 8 | 在本章中, 我们将介绍 Clash 的常见功能以及如何使用和配置它们. 9 | 10 | Clash 使用 [YAML](https://yaml.org) (YAML Ain't Markup Language) 作为配置文件格式. YAML 旨在易于阅读、编写和解析, 通常用于配置文件. 11 | 12 | ## 了解 Clash 的工作原理 13 | 14 | 在继续之前, 有必要了解 Clash 的工作原理, 其中有两个关键部分: 15 | 16 | ![](/assets/connection-flow.png) 17 | 18 | 19 | 20 | ### Inbound 入站 21 | 22 | Inbound 入站是在本地端监听的部分, 它通过打开一个本地端口并监听传入的连接来工作. 当连接进来时, Clash 会查询配置文件中配置的规则, 并决定连接应该去哪个 Outbound 出站. 23 | 24 | ### Outbound 出站 25 | 26 | Outbound 出站是连接到远程端的部分. 根据配置的不同, 它可以是一个特定的网络接口、一个代理服务器或一个[策略组](/zh_CN/configuration/outbound#proxy-groups-策略组). 27 | 28 | ## 基于规则的路由 29 | 30 | Clash 支持基于规则的路由, 这意味着您可以根据各种规则将数据包路由到不同的出站. 规则可以在配置文件的 `rules` 部分中定义. 31 | 32 | 有许多可用的规则类型, 每种规则类型都有自己的语法. 规则的一般语法是: 33 | 34 | ```txt 35 | # 类型,参数,策略(,no-resolve) 36 | TYPE,ARGUMENT,POLICY(,no-resolve) 37 | ``` 38 | 39 | 在下一步指南中, 您将了解有关如何配置规则的更多信息. 40 | -------------------------------------------------------------------------------- /docs/zh_CN/index.md: -------------------------------------------------------------------------------- 1 | 2 | # 什么是 Clash? 3 | 4 | 欢迎访问 Clash 内核项目的官方说明文档. 5 | 6 | Clash是一个跨平台的基于规则的代理工具, 在网络和应用层运行, 支持各种代理和反审查协议的开箱即用. 7 | 8 | 在一些互联网受到严格审查或封锁的国家和地区, 它已被互联网用户广泛采用. 无论如何, 任何想要改善其 Internet 体验的人都可以使用 Clash. 9 | 10 | 目前, Clash 包含两个版本: 11 | 12 | - [Clash](https://github.com/Dreamacro/clash): 发布于[github.com/Dreamacro/clash](https://github.com/Dreamacro/clash)的开源版本 13 | - [Clash Premium 版本](https://github.com/Dreamacro/clash/releases/tag/premium): 具有[TUN 和更多支持](/zh_CN/premium/introduction) 的专有内核 (免费) 14 | 15 | 虽然这个 Wiki 涵盖了上述两个版本的内容, 然而对于普通用户来说, Clash 的使用可能仍是一种挑战. 而对于考虑使用 GUI 客户端的用户, 我们确实有一些建议: 16 | 17 | - [Clash for Windows](https://github.com/Fndroid/clash_for_windows_pkg/releases) (Windows 和 macOS) 18 | - [Clash for Android](https://github.com/Kr328/ClashForAndroid) 19 | - [ClashX](https://github.com/yichengchen/clashX) 或 [ClashX Pro](https://install.appcenter.ms/users/clashx/apps/clashx-pro/distribution_groups/public) (macOS) 20 | 21 | ## 特点概述 22 | 23 | - 入站连接支持: HTTP, HTTPS, SOCKS5 服务端, TUN 设备* 24 | - 出站连接支持: Shadowsocks(R), VMess, Trojan, Snell, SOCKS5, HTTP(S), Wireguard* 25 | - 基于规则的路由: 动态脚本、域名、IP地址、进程名称和更多* 26 | - Fake-IP DNS: 尽量减少 DNS 污染的影响, 提高网络性能 27 | - 透明代理: 使用自动路由表/规则管理 Redirect TCP 和 TProxy TCP/UDP* 28 | - Proxy Groups 策略组: 自动化的可用性测试 (fallback)、负载均衡 (load balance) 或 延迟测试 (url-test) 29 | - 远程 Providers: 动态加载远程代理列表 30 | - RESTful API: 通过一个全面的 API 就地更新配置 31 | 32 | 33 | \*: 只在免费的 Premium 版本中提供. 34 | 35 | 36 | ## License 37 | 38 | Clash 是根据 [GPL-3.0](https://github.com/Dreamacro/clash/blob/master/LICENSE) 开源许可证发布的. 在 [v0.16.0](https://github.com/Dreamacro/clash/releases/tag/v0.16.0) 或 [e5284c](https://github.com/Dreamacro/clash/commit/e5284cf647717a8087a185d88d15a01096274bc2) 提交之前, 其基于 MIT 许可证授权. 39 | -------------------------------------------------------------------------------- /docs/zh_CN/introduction/_dummy-index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 什么是 Clash? 3 | sidebarOrder: 1 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/zh_CN/introduction/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 快速开始 3 | sidebarOrder: 2 4 | --- 5 | 6 | # 快速开始 7 | 8 | 为了开始使用 Clash, 您可以从源码编译或者下载预编译的二进制文件. 9 | 10 | ## 使用预编译的二进制文件 11 | 12 | 您可以在这里下载 Clash 的内核二进制文件: [https://github.com/Dreamacro/clash/releases](https://github.com/Dreamacro/clash/releases) 13 | 14 | ## 从源码编译 15 | 16 | 您可以使用 Golang 1.19+ 在您的设备上编译 Clash: 17 | 18 | ```shell 19 | $ go install github.com/Dreamacro/clash@latest 20 | go: downloading github.com/Dreamacro/clash v1.15.1 21 | ``` 22 | 23 | 二进制文件将会被编译到 `$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 | ## 跨平台/操作系统编译 31 | 32 | Golang 支持交叉编译, 所以您可以为不同架构或操作系统的设备编译 Clash. 您可以使用 _make_ 来轻松地编译它们, 例如: 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 | 对于其他构建目标, 请查看 [Makefile](https://github.com/Dreamacro/clash/blob/master/Makefile). -------------------------------------------------------------------------------- /docs/zh_CN/premium/ebpf.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: eBPF 重定向到 TUN" 3 | sidebarOrder: 3 4 | --- 5 | 6 | # 功能: eBPF 重定向到 TUN 7 | 8 | eBPF 重定向到 TUN 是一项拦截特定网络接口上的所有网络流量, 并将其重定向到 TUN 接口的功能. 该功能需要[内核支持](https://github.com/iovisor/bcc/blob/master/INSTALL.md#kernel-configuration). 9 | 10 | ::: warning 11 | 此功能与 `tun.auto-route` 冲突. 12 | ::: 13 | 14 | 虽然它通常与 `tun.auto-redir` 和 `tun.auto-route` 相比具有更好的性能, 但与 `auto-route` 相比, 它并不够成熟. 因此, 您应该谨慎使用. 15 | 16 | ## 配置 17 | 18 | ```yaml 19 | ebpf: 20 | redirect-to-tun: 21 | - eth0 22 | ``` 23 | 24 | ## 已知问题 25 | 26 | - 此功能与 Tailscaled 冲突, 因此您应该使用 `tun.auto-route` 作为替代. 27 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/experimental-features.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 实验功能 3 | sidebarOrder: 9 4 | --- 5 | 6 | # 实验功能 7 | 8 | 偶尔我们会做一些新的功能, 这些功能需要大量的测试才能在主要版本中使用. 这些功能被标记为实验性的, 并且默认是禁用的. 9 | 10 | ::: warning 11 | 这里列出的一些功能可能不稳定, 并且可能在任何未来版本中被删除 - 我们不建议使用它们, 除非您有特定的原因. 12 | ::: 13 | 14 | ## 嗅探 TLS SNI 15 | 16 | ```yaml 17 | experimental: 18 | sniff-tls-sni: true 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: 简介 3 | sidebarOrder: 1 4 | --- 5 | 6 | # 简介 7 | 8 | 在过去, 只有一个开源版本的 Clash, 直到一些 [不当使用和再分发](https://github.com/Dreamacro/clash/issues/541#issuecomment-672029110) 的 Clash 出现. 从那时起, 我们决定分叉 Clash 并在私有 GitHub 存储库中开发更高级的功能. 9 | 10 | 不要担心 - Premium 内核将保持免费, 并且其源代码的安全性通过多个可信的开发人员相互审查以保证. 11 | 12 | ## 有什么区别? 13 | 14 | Premium 内核是开源 Clash 内核的 Fork 分支, 增加了以下功能: 15 | 16 | - [TUN 设备](/zh_CN/premium/tun-device) 支持 `auto-redir` 和 `auto-route` 17 | - [eBPF 重定向到 TUN](/zh_CN/premium/ebpf) 18 | - [Rule Providers 规则集](/zh_CN/premium/rule-providers) 19 | - [Script 脚本](/zh_CN/premium/script) 20 | - [Script Shotcuts 脚本捷径](/zh_CN/premium/script-shortcuts) 21 | - [用户空间 Wireguard](/zh_CN/premium/userspace-wireguard) 22 | - [性能分析引擎](/zh_CN/premium/the-profiling-engine) 23 | 24 | ## 获取副本 25 | 26 | 您可以从 [GitHub Releases](https://github.com/Dreamacro/clash/releases/tag/premium) 下载最新的 Clash Premium 二进制文件. 27 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/rule-providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: Rule Providers 规则集" 3 | sidebarOrder: 4 4 | --- 5 | 6 | # Rule Providers 规则集 7 | 8 | Rule Providers 规则集和 [Proxy Providers 代理集](/zh_CN/configuration/outbound#proxy-providers-代理集) 基本相同. 它允许用户从外部源加载规则, 从而使配置更加简洁. 该功能目前仅适用于 Clash Premium 内核. 9 | 10 | 要定义 Rule Providers 规则集, 请将 `rule-providers` 规则集字段添加到主配置中: 11 | 12 | ```yaml 13 | rule-providers: 14 | apple: 15 | behavior: "domain" # domain, ipcidr or classical (仅限 Clash Premium 内核) 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 | 有三种行为类型可用: 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 在这里并不是必须的 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/zh_CN/premium/script-shortcuts.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: Script Shortcuts 脚本捷径" 3 | sidebarOrder: 6 4 | --- 5 | 6 | # Script Shortcuts 脚本捷径 7 | 8 | Clash Premium 实现了基于 Python3 的脚本功能, 允许用户以动态灵活的方式为数据包选择策略. 9 | 10 | 您可以使用单个 Python 脚本控制整个规则匹配引擎, 也可以定义一些 Shortcuts 捷径并将它们与常规规则一起使用. 本页参考后者功能. 有关前者, 请参见 [脚本](./script.md). 11 | 12 | 此功能使得在 `rules` 模式下使用脚本成为可能. 默认情况下, DNS 解析将在 SCRIPT 规则中进行. 可以在规则后面添加 `no-resolve` 来阻止解析. (例如: `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 | ## 评估引擎 29 | 30 | [Expr](https://expr.medv.io/) 作为 Script Shortcuts 的默认引擎, 相比 Starlark 提供了 10 倍到 20 倍的性能提升. 31 | 32 | [Starlark](https://github.com/google/starlark-go) 是一种类似 Python 的配置语言, 您也可以将其用于 Script Shortcuts. 33 | 34 | ## 变量 35 | 36 | - network: string 37 | - type: string 38 | - src_ip: string 39 | - dst_ip: string 40 | - src_port: uint16 41 | - dst_port: uint16 42 | - inbound_port: uint16 43 | - host: string 44 | - process_path: string 45 | 46 | ::: warning 47 | Starlark 目前不包含 `process_path` 变量. 48 | ::: 49 | 50 | ## 函数 51 | 52 | ```ts 53 | type resolve_ip = (host: string) => string // ip string 54 | type in_cidr = (ip: string, cidr: string) => boolean // ip in cidr 55 | type in_ipset = (name: string, ip: string) => boolean // ip in ipset 56 | type geoip = (ip: string) => string // country code 57 | type match_provider = (name: string) => boolean // in rule provider 58 | type resolve_process_name = () => string // find process name (curl .e.g) 59 | type resolve_process_path = () => string // find process path (/usr/bin/curl .e.g) 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/script.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: Script 脚本" 3 | sidebarOrder: 5 4 | --- 5 | 6 | # Script 脚本 7 | 8 | Clash Premium 实现了基于 Python3 的脚本功能, 使用户能够以动态灵活的方式为数据包选择策略. 9 | 10 | 您可以使用单个 Python 脚本控制整个规则匹配引擎, 也可以定义一些快捷方式, 并与常规规则一起使用. 本页介绍了第一种功能, 有关后者, 请参见[Script Shortcuts 脚本捷径](./script-shortcuts.md). 11 | 12 | ## 控制整个规则匹配引擎 13 | 14 | ```yaml 15 | mode: Script 16 | 17 | # https://lancellc.gitbook.io/clash/clash-config-file/script 18 | script: 19 | code: | 20 | def main(ctx, metadata): 21 | ip = metadata["dst_ip"] = ctx.resolve_ip(metadata["host"]) 22 | if ip == "": 23 | return "DIRECT" 24 | 25 | code = ctx.geoip(ip) 26 | if code == "LAN" or code == "CN": 27 | return "DIRECT" 28 | 29 | return "Proxy" # default policy for requests which are not matched by any other script 30 | ``` 31 | 32 | 如果您想使用 IP 规则 (即: IP-CIDR、GEOIP 等) , 您首先需要手动解析 IP 地址并将其分配给 metadata: 33 | 34 | ```python 35 | def main(ctx, metadata): 36 | # ctx.rule_providers["geoip"].match(metadata) return false 37 | 38 | ip = ctx.resolve_ip(metadata["host"]) 39 | if ip == "": 40 | return "DIRECT" 41 | metadata["dst_ip"] = ip 42 | 43 | # ctx.rule_providers["iprule"].match(metadata) return true 44 | 45 | return "Proxy" 46 | ``` 47 | 48 | Metadata 和 Context 的接口定义: 49 | 50 | ```ts 51 | interface Metadata { 52 | type: string // socks5、http 53 | network: string // tcp 54 | host: string 55 | src_ip: string 56 | src_port: string 57 | dst_ip: string 58 | dst_port: string 59 | inbound_port: number 60 | } 61 | 62 | interface Context { 63 | resolve_ip: (host: string) => string // ip string 64 | resolve_process_name: (metadata: Metadata) => string 65 | resolve_process_path: (metadata: Metadata) => string 66 | geoip: (ip: string) => string // country code 67 | log: (log: string) => void 68 | proxy_providers: Record> 69 | rule_providers: Record boolean }> 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/the-profiling-engine.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: 性能分析引擎" 3 | sidebarOrder: 8 4 | --- 5 | 6 | # 性能分析引擎 7 | 8 | https://github.com/Dreamacro/clash-tracing 9 | 10 | ```yaml 11 | profile: 12 | tracing: true 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/tun-device.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: TUN 设备" 3 | sidebarOrder: 2 4 | --- 5 | 6 | # TUN 设备 7 | 8 | Premium 内核支持 TUN 设备. 作为网络层设备, 它可以用来处理 TCP、UDP、ICMP 流量. 它已经在生产环境中进行了广泛的测试和使用 - 您甚至可以用它来玩竞技游戏. 9 | 10 | 使用 Clash TUN 的最大优势之一是内置支持对操作系统路由表、路由规则和 nftable 的自动管理. 您可以通过选项 `tun.auto-route` 和 `tun.auto-redir` 来启用它. 这个功能替换了古老的配置选项 `redir-port`(TCP), 以方便配置和提高稳定性. 11 | 12 | ::: tip 13 | `tun.auto-route` 仅在 macOS、Windows、Linux 和 Android 上可用, 并且仅接收 IPv4 流量。`tun.auto-redir` 仅在 Linux 上可用(需要内核 netlink 支持)。 14 | ::: 15 | 16 | Clash 有两种可供选择的 TCP/IP 协议栈: `system` or `gvisor`. 为了获得最好的性能, 我们建议您优先使用 `system` 栈, 只有遇到兼容性问题时才使用 `gvisor`. 并且如果你遇到这样的情况, 请立即[提交 Issue](https://github.com/Dreamacro/clash/issues/new/choose). 17 | 18 | ## 技术限制 19 | 20 | * 对于 Android, 控制设备位于 `/dev/tun` 而不是 `/dev/net/tun`, 您需要先创建一个软链接 (i.e. `ln -sf /dev/tun /dev/net/tun`) 21 | 22 | * 如果系统 DNS 位于私有 IP 地址上, DNS 劫持可能会失败 (因为 `auto-route` 不会捕获私有网络流量). 23 | 24 | ## Linux, macOS 和 Windows 25 | 26 | 这是 TUN 功能的示例配置: 27 | 28 | ```yaml 29 | interface-name: en0 # 与 `tun.auto-detect-interface` 冲突 30 | 31 | tun: 32 | enable: true 33 | stack: system # or gvisor 34 | # dns-hijack: 35 | # - 8.8.8.8:53 36 | # - tcp://8.8.8.8:53 37 | # - any:53 38 | # - tcp://any:53 39 | auto-route: true # manage `ip route` and `ip rules` 40 | auto-redir: true # manage nftable REDIRECT 41 | auto-detect-interface: true # 与 `interface-name` 冲突 42 | ``` 43 | 44 | 请注意, 由于使用了 TUN 设备和对系统路由表、nftable 的操作, Clash 在此处将需要超级用户权限来运行. 45 | 46 | ```shell 47 | sudo ./clash 48 | ``` 49 | 50 | 如果您的设备已经有一些 TUN 设备, Clash TUN 可能无法工作 - 您必须手动检查路由表和路由规则. 在这种情况下, `fake-ip-filter` 也许也有帮助. 51 | 52 | ## Windows 53 | 54 | 您需要访问 [WinTUN 网站](https://www.wintun.net) 并下载最新版本. 之后, 将 `wintun.dll` 复制到 Clash 主目录. 示例配置: 55 | 56 | ```yaml 57 | tun: 58 | enable: true 59 | stack: gvisor # or system 60 | dns-hijack: 61 | - 198.18.0.2:53 # 当 `fake-ip-range` 是 198.18.0.1/16, 应该劫持 198.18.0.2:53 62 | auto-route: true # 为 Windows 自动设置全局路由 63 | # 推荐使用 `interface-name` 64 | auto-detect-interface: true # 自动检测接口, 与 `interface-name` 冲突 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/zh_CN/premium/userspace-wireguard.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarTitle: "功能: 用户空间 Wireguard" 3 | sidebarOrder: 7 4 | --- 5 | 6 | # 用户空间 Wireguard 7 | 8 | 由于依赖 gvisor TCP/IP 栈, 用户空间 Wireguard 目前仅在 Premium 内核中可用. 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 # 远程解析 DNS, 使用 `dns` 字段, 默认为 true 22 | # dns: [1.1.1.1, 8.8.8.8] 23 | # mtu: 1420 24 | udp: true 25 | ``` 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Dreamacro/clash 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 7 | github.com/dlclark/regexp2 v1.10.0 8 | github.com/go-chi/chi/v5 v5.0.10 9 | github.com/go-chi/cors v1.2.1 10 | github.com/go-chi/render v1.0.3 11 | github.com/gofrs/uuid/v5 v5.0.0 12 | github.com/gorilla/websocket v1.5.0 13 | github.com/insomniacslk/dhcp v0.0.0-20230816195147-b3ca2534940d 14 | github.com/mdlayher/netlink v1.7.2 15 | github.com/miekg/dns v1.1.55 16 | github.com/oschwald/geoip2-golang v1.9.0 17 | github.com/samber/lo v1.38.1 18 | github.com/sirupsen/logrus v1.9.3 19 | github.com/stretchr/testify v1.8.4 20 | github.com/vishvananda/netlink v1.2.1-beta.2.0.20230420174744-55c8b9515a01 21 | go.etcd.io/bbolt v1.3.7 22 | go.uber.org/atomic v1.11.0 23 | go.uber.org/automaxprocs v1.5.3 24 | golang.org/x/crypto v0.12.0 25 | golang.org/x/net v0.14.0 26 | golang.org/x/sync v0.3.0 27 | golang.org/x/sys v0.11.0 28 | gopkg.in/yaml.v3 v3.0.1 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/go-cmp v0.5.9 // indirect 35 | github.com/josharian/native v1.1.0 // indirect 36 | github.com/kr/text v0.2.0 // indirect 37 | github.com/mdlayher/socket v0.4.1 // indirect 38 | github.com/oschwald/maxminddb-golang v1.11.0 // indirect 39 | github.com/pierrec/lz4/v4 v4.1.14 // indirect 40 | github.com/pmezard/go-difflib v1.0.0 // indirect 41 | github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect 42 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect 43 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect 44 | golang.org/x/mod v0.8.0 // indirect 45 | golang.org/x/text v0.12.0 // indirect 46 | golang.org/x/tools v0.6.0 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /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/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/inbounds.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/listener" 8 | "github.com/Dreamacro/clash/tunnel" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/go-chi/render" 12 | ) 13 | 14 | func inboundRouter() http.Handler { 15 | r := chi.NewRouter() 16 | r.Get("/", getInbounds) 17 | r.Put("/", updateInbounds) 18 | return r 19 | } 20 | 21 | func getInbounds(w http.ResponseWriter, r *http.Request) { 22 | inbounds := listener.GetInbounds() 23 | render.JSON(w, r, render.M{ 24 | "inbounds": inbounds, 25 | }) 26 | } 27 | 28 | func updateInbounds(w http.ResponseWriter, r *http.Request) { 29 | var req []C.Inbound 30 | if err := render.DecodeJSON(r.Body, &req); err != nil { 31 | render.Status(r, http.StatusBadRequest) 32 | render.JSON(w, r, ErrBadRequest) 33 | return 34 | } 35 | tcpIn := tunnel.TCPIn() 36 | udpIn := tunnel.UDPIn() 37 | listener.ReCreateListeners(req, tcpIn, udpIn) 38 | render.NoContent(w, r) 39 | } 40 | -------------------------------------------------------------------------------- /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) (C.Listener, error) { 33 | return NewWithAuthenticate(addr, in, true) 34 | } 35 | 36 | func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (C.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) (C.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) (C.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_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/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) (C.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_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/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 | -------------------------------------------------------------------------------- /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 | // Implements C.Rule 10 | var _ C.Rule = (*Domain)(nil) 11 | 12 | type Domain struct { 13 | domain string 14 | adapter string 15 | } 16 | 17 | func (d *Domain) RuleType() C.RuleType { 18 | return C.Domain 19 | } 20 | 21 | func (d *Domain) Match(metadata *C.Metadata) bool { 22 | return metadata.Host == d.domain 23 | } 24 | 25 | func (d *Domain) Adapter() string { 26 | return d.adapter 27 | } 28 | 29 | func (d *Domain) Payload() string { 30 | return d.domain 31 | } 32 | 33 | func (d *Domain) ShouldResolveIP() bool { 34 | return false 35 | } 36 | 37 | func (d *Domain) ShouldFindProcess() bool { 38 | return false 39 | } 40 | 41 | func NewDomain(domain string, adapter string) *Domain { 42 | return &Domain{ 43 | domain: strings.ToLower(domain), 44 | adapter: adapter, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rule/domain_keyword.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | // Implements C.Rule 10 | var _ C.Rule = (*DomainKeyword)(nil) 11 | 12 | type DomainKeyword struct { 13 | keyword string 14 | adapter string 15 | } 16 | 17 | func (dk *DomainKeyword) RuleType() C.RuleType { 18 | return C.DomainKeyword 19 | } 20 | 21 | func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { 22 | return strings.Contains(metadata.Host, dk.keyword) 23 | } 24 | 25 | func (dk *DomainKeyword) Adapter() string { 26 | return dk.adapter 27 | } 28 | 29 | func (dk *DomainKeyword) Payload() string { 30 | return dk.keyword 31 | } 32 | 33 | func (dk *DomainKeyword) ShouldResolveIP() bool { 34 | return false 35 | } 36 | 37 | func (dk *DomainKeyword) ShouldFindProcess() bool { 38 | return false 39 | } 40 | 41 | func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { 42 | return &DomainKeyword{ 43 | keyword: strings.ToLower(keyword), 44 | adapter: adapter, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rule/domain_suffix.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | // Implements C.Rule 10 | var _ C.Rule = (*DomainSuffix)(nil) 11 | 12 | type DomainSuffix struct { 13 | suffix string 14 | adapter string 15 | } 16 | 17 | func (ds *DomainSuffix) RuleType() C.RuleType { 18 | return C.DomainSuffix 19 | } 20 | 21 | func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { 22 | domain := metadata.Host 23 | return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix 24 | } 25 | 26 | func (ds *DomainSuffix) Adapter() string { 27 | return ds.adapter 28 | } 29 | 30 | func (ds *DomainSuffix) Payload() string { 31 | return ds.suffix 32 | } 33 | 34 | func (ds *DomainSuffix) ShouldResolveIP() bool { 35 | return false 36 | } 37 | 38 | func (ds *DomainSuffix) ShouldFindProcess() bool { 39 | return false 40 | } 41 | 42 | func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { 43 | return &DomainSuffix{ 44 | suffix: strings.ToLower(suffix), 45 | adapter: adapter, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rule/final.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | // Implements C.Rule 8 | var _ C.Rule = (*Match)(nil) 9 | 10 | type Match struct { 11 | adapter string 12 | } 13 | 14 | func (f *Match) RuleType() C.RuleType { 15 | return C.MATCH 16 | } 17 | 18 | func (f *Match) Match(metadata *C.Metadata) bool { 19 | return true 20 | } 21 | 22 | func (f *Match) Adapter() string { 23 | return f.adapter 24 | } 25 | 26 | func (f *Match) Payload() string { 27 | return "" 28 | } 29 | 30 | func (f *Match) ShouldResolveIP() bool { 31 | return false 32 | } 33 | 34 | func (f *Match) ShouldFindProcess() bool { 35 | return false 36 | } 37 | 38 | func NewMatch(adapter string) *Match { 39 | return &Match{ 40 | adapter: adapter, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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 | // Implements C.Rule 11 | var _ C.Rule = (*GEOIP)(nil) 12 | 13 | type GEOIP struct { 14 | country string 15 | adapter string 16 | noResolveIP bool 17 | } 18 | 19 | func (g *GEOIP) RuleType() C.RuleType { 20 | return C.GEOIP 21 | } 22 | 23 | func (g *GEOIP) Match(metadata *C.Metadata) bool { 24 | ip := metadata.DstIP 25 | if ip == nil { 26 | return false 27 | } 28 | 29 | if strings.EqualFold(g.country, "LAN") { 30 | return ip.IsPrivate() 31 | } 32 | record, _ := mmdb.Instance().Country(ip) 33 | return strings.EqualFold(record.Country.IsoCode, g.country) 34 | } 35 | 36 | func (g *GEOIP) Adapter() string { 37 | return g.adapter 38 | } 39 | 40 | func (g *GEOIP) Payload() string { 41 | return g.country 42 | } 43 | 44 | func (g *GEOIP) ShouldResolveIP() bool { 45 | return !g.noResolveIP 46 | } 47 | 48 | func (g *GEOIP) ShouldFindProcess() bool { 49 | return false 50 | } 51 | 52 | func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { 53 | geoip := &GEOIP{ 54 | country: country, 55 | adapter: adapter, 56 | noResolveIP: noResolveIP, 57 | } 58 | 59 | return geoip 60 | } 61 | -------------------------------------------------------------------------------- /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 | // Implements C.Rule 24 | var _ C.Rule = (*IPCIDR)(nil) 25 | 26 | type IPCIDR struct { 27 | ipnet *net.IPNet 28 | adapter string 29 | isSourceIP bool 30 | noResolveIP bool 31 | } 32 | 33 | func (i *IPCIDR) RuleType() C.RuleType { 34 | if i.isSourceIP { 35 | return C.SrcIPCIDR 36 | } 37 | return C.IPCIDR 38 | } 39 | 40 | func (i *IPCIDR) Match(metadata *C.Metadata) bool { 41 | ip := metadata.DstIP 42 | if i.isSourceIP { 43 | ip = metadata.SrcIP 44 | } 45 | return ip != nil && i.ipnet.Contains(ip) 46 | } 47 | 48 | func (i *IPCIDR) Adapter() string { 49 | return i.adapter 50 | } 51 | 52 | func (i *IPCIDR) Payload() string { 53 | return i.ipnet.String() 54 | } 55 | 56 | func (i *IPCIDR) ShouldResolveIP() bool { 57 | return !i.noResolveIP 58 | } 59 | 60 | func (i *IPCIDR) ShouldFindProcess() bool { 61 | return false 62 | } 63 | 64 | func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { 65 | _, ipnet, err := net.ParseCIDR(s) 66 | if err != nil { 67 | return nil, errPayload 68 | } 69 | 70 | ipcidr := &IPCIDR{ 71 | ipnet: ipnet, 72 | adapter: adapter, 73 | } 74 | 75 | for _, o := range opts { 76 | o(ipcidr) 77 | } 78 | 79 | return ipcidr, nil 80 | } 81 | -------------------------------------------------------------------------------- /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 | // Implements C.Rule 10 | var _ C.Rule = (*IPSet)(nil) 11 | 12 | type IPSet struct { 13 | name string 14 | adapter string 15 | noResolveIP bool 16 | } 17 | 18 | func (f *IPSet) RuleType() C.RuleType { 19 | return C.IPSet 20 | } 21 | 22 | func (f *IPSet) Match(metadata *C.Metadata) bool { 23 | exist, err := ipset.Test(f.name, metadata.DstIP) 24 | if err != nil { 25 | log.Warnln("check ipset '%s' failed: %s", f.name, err.Error()) 26 | return false 27 | } 28 | return exist 29 | } 30 | 31 | func (f *IPSet) Adapter() string { 32 | return f.adapter 33 | } 34 | 35 | func (f *IPSet) Payload() string { 36 | return f.name 37 | } 38 | 39 | func (f *IPSet) ShouldResolveIP() bool { 40 | return !f.noResolveIP 41 | } 42 | 43 | func (f *IPSet) ShouldFindProcess() bool { 44 | return false 45 | } 46 | 47 | func NewIPSet(name string, adapter string, noResolveIP bool) (*IPSet, error) { 48 | if err := ipset.Verify(name); err != nil { 49 | return nil, err 50 | } 51 | 52 | return &IPSet{ 53 | name: name, 54 | adapter: adapter, 55 | noResolveIP: noResolveIP, 56 | }, nil 57 | } 58 | -------------------------------------------------------------------------------- /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 | ruleConfigType := C.RuleConfig(tp) 16 | 17 | switch ruleConfigType { 18 | case C.RuleConfigDomain: 19 | parsed = NewDomain(payload, target) 20 | case C.RuleConfigDomainSuffix: 21 | parsed = NewDomainSuffix(payload, target) 22 | case C.RuleConfigDomainKeyword: 23 | parsed = NewDomainKeyword(payload, target) 24 | case C.RuleConfigGeoIP: 25 | noResolve := HasNoResolve(params) 26 | parsed = NewGEOIP(payload, target, noResolve) 27 | case C.RuleConfigIPCIDR, C.RuleConfigIPCIDR6: 28 | noResolve := HasNoResolve(params) 29 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) 30 | case C.RuleConfigSrcIPCIDR: 31 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) 32 | case C.RuleConfigSrcPort: 33 | parsed, parseErr = NewPort(payload, target, PortTypeSrc) 34 | case C.RuleConfigDstPort: 35 | parsed, parseErr = NewPort(payload, target, PortTypeDest) 36 | case C.RuleConfigInboundPort: 37 | parsed, parseErr = NewPort(payload, target, PortTypeInbound) 38 | case C.RuleConfigProcessName: 39 | parsed, parseErr = NewProcess(payload, target, true) 40 | case C.RuleConfigProcessPath: 41 | parsed, parseErr = NewProcess(payload, target, false) 42 | case C.RuleConfigIPSet: 43 | noResolve := HasNoResolve(params) 44 | parsed, parseErr = NewIPSet(payload, target, noResolve) 45 | case C.RuleConfigMatch: 46 | parsed = NewMatch(target) 47 | case C.RuleConfigRuleSet, C.RuleConfigScript: 48 | parseErr = fmt.Errorf("unsupported rule type %s", tp) 49 | default: 50 | parseErr = fmt.Errorf("unsupported rule type %s", tp) 51 | } 52 | 53 | return parsed, parseErr 54 | } 55 | -------------------------------------------------------------------------------- /rule/port.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type PortType int 11 | 12 | const ( 13 | PortTypeSrc PortType = iota 14 | PortTypeDest 15 | PortTypeInbound 16 | ) 17 | 18 | // Implements C.Rule 19 | var _ C.Rule = (*Port)(nil) 20 | 21 | type Port struct { 22 | adapter string 23 | port C.Port 24 | portType PortType 25 | } 26 | 27 | func (p *Port) RuleType() C.RuleType { 28 | switch p.portType { 29 | case PortTypeSrc: 30 | return C.SrcPort 31 | case PortTypeDest: 32 | return C.DstPort 33 | case PortTypeInbound: 34 | return C.InboundPort 35 | default: 36 | panic(fmt.Errorf("unknown port type: %v", p.portType)) 37 | } 38 | } 39 | 40 | func (p *Port) Match(metadata *C.Metadata) bool { 41 | switch p.portType { 42 | case PortTypeSrc: 43 | return metadata.SrcPort == p.port 44 | case PortTypeDest: 45 | return metadata.DstPort == p.port 46 | case PortTypeInbound: 47 | return metadata.OriginDst.Port() == uint16(p.port) 48 | default: 49 | panic(fmt.Errorf("unknown port type: %v", p.portType)) 50 | } 51 | } 52 | 53 | func (p *Port) Adapter() string { 54 | return p.adapter 55 | } 56 | 57 | func (p *Port) Payload() string { 58 | return p.port.String() 59 | } 60 | 61 | func (p *Port) ShouldResolveIP() bool { 62 | return false 63 | } 64 | 65 | func (p *Port) ShouldFindProcess() bool { 66 | return false 67 | } 68 | 69 | func NewPort(port string, adapter string, portType PortType) (*Port, error) { 70 | p, err := strconv.ParseUint(port, 10, 16) 71 | if err != nil { 72 | return nil, errPayload 73 | } 74 | return &Port{ 75 | adapter: adapter, 76 | port: C.Port(p), 77 | portType: portType, 78 | }, nil 79 | } 80 | -------------------------------------------------------------------------------- /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 | // Implements C.Rule 11 | var _ C.Rule = (*Process)(nil) 12 | 13 | type Process struct { 14 | adapter string 15 | process string 16 | nameOnly bool 17 | } 18 | 19 | func (ps *Process) RuleType() C.RuleType { 20 | if ps.nameOnly { 21 | return C.Process 22 | } 23 | 24 | return C.ProcessPath 25 | } 26 | 27 | func (ps *Process) Match(metadata *C.Metadata) bool { 28 | if ps.nameOnly { 29 | return strings.EqualFold(filepath.Base(metadata.ProcessPath), ps.process) 30 | } 31 | 32 | return strings.EqualFold(metadata.ProcessPath, ps.process) 33 | } 34 | 35 | func (ps *Process) Adapter() string { 36 | return ps.adapter 37 | } 38 | 39 | func (ps *Process) Payload() string { 40 | return ps.process 41 | } 42 | 43 | func (ps *Process) ShouldResolveIP() bool { 44 | return false 45 | } 46 | 47 | func (ps *Process) ShouldFindProcess() bool { 48 | return true 49 | } 50 | 51 | func NewProcess(process string, adapter string, nameOnly bool) (*Process, error) { 52 | return &Process{ 53 | adapter: adapter, 54 | process: process, 55 | nameOnly: nameOnly, 56 | }, nil 57 | } 58 | -------------------------------------------------------------------------------- /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.21' 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/vless-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 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 | } 41 | -------------------------------------------------------------------------------- /test/config/vless-http.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "tcp", 17 | "tcpSettings": { 18 | "header": { 19 | "type": "http", 20 | "response": { 21 | "version": "1.1", 22 | "status": "200", 23 | "reason": "OK", 24 | "headers": { 25 | "Content-Type": [ 26 | "application/octet-stream", 27 | "video/mpeg", 28 | "application/x-msdownload", 29 | "text/html", 30 | "application/x-shockwave-flash" 31 | ], 32 | "Transfer-Encoding": [ 33 | "chunked" 34 | ], 35 | "Connection": [ 36 | "keep-alive" 37 | ], 38 | "Pragma": "no-cache" 39 | } 40 | } 41 | } 42 | }, 43 | "security": "none" 44 | } 45 | } 46 | ], 47 | "outbounds": [ 48 | { 49 | "protocol": "freedom" 50 | } 51 | ], 52 | "log": { 53 | "loglevel": "debug" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/config/vless-http2.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "http", 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 | "httpSettings": { 27 | "host": [ 28 | "example.org" 29 | ], 30 | "path": "/test" 31 | } 32 | } 33 | } 34 | ], 35 | "outbounds": [ 36 | { 37 | "protocol": "freedom" 38 | } 39 | ], 40 | "log": { 41 | "loglevel": "debug" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/config/vless-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "tcp", 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 | } 27 | } 28 | ], 29 | "outbounds": [ 30 | { 31 | "protocol": "freedom" 32 | } 33 | ], 34 | "log": { 35 | "loglevel": "debug" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/config/vless-ws-0rtt.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "ws", 17 | "security": "none", 18 | "wsSettings": { 19 | "maxEarlyData": 128, 20 | "earlyDataHeaderName": "Sec-WebSocket-Protocol" 21 | } 22 | } 23 | } 24 | ], 25 | "outbounds": [ 26 | { 27 | "protocol": "freedom" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/config/vless-ws-tls-zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "ws", 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 | } 27 | } 28 | ], 29 | "outbounds": [ 30 | { 31 | "protocol": "freedom" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/config/vless-ws-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "ws", 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 | } 27 | } 28 | ], 29 | "outbounds": [ 30 | { 31 | "protocol": "freedom" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/config/vless-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "ws", 17 | "security": "none" 18 | } 19 | } 20 | ], 21 | "outbounds": [ 22 | { 23 | "protocol": "freedom" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/config/vless.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vless", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ], 13 | "decryption": "none" 14 | }, 15 | "streamSettings": { 16 | "network": "tcp" 17 | } 18 | } 19 | ], 20 | "outbounds": [ 21 | { 22 | "protocol": "freedom" 23 | } 24 | ], 25 | "log": { 26 | "loglevel": "debug" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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 | hostCfg.NetworkMode = "host" 19 | container, err := c.ContainerCreate(context.Background(), cfg, hostCfg, nil, nil, name) 20 | if err != nil { 21 | return "", err 22 | } 23 | 24 | if err = c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil { 25 | return "", err 26 | } 27 | 28 | return container.ID, nil 29 | } 30 | 31 | func cleanContainer(id string) error { 32 | c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 33 | if err != nil { 34 | return err 35 | } 36 | defer c.Close() 37 | 38 | removeOpts := types.ContainerRemoveOptions{Force: true} 39 | return c.ContainerRemove(context.Background(), id, removeOpts) 40 | } 41 | -------------------------------------------------------------------------------- /test/listener_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "testing" 7 | "time" 8 | 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/listener" 11 | "github.com/Dreamacro/clash/tunnel" 12 | 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestClash_Listener(t *testing.T) { 17 | basic := ` 18 | log-level: silent 19 | port: 7890 20 | socks-port: 7891 21 | redir-port: 7892 22 | tproxy-port: 7893 23 | mixed-port: 7894 24 | ` 25 | 26 | err := parseAndApply(basic) 27 | require.NoError(t, err) 28 | defer cleanup() 29 | 30 | time.Sleep(waitTime) 31 | 32 | for i := 7890; i <= 7894; i++ { 33 | require.True(t, TCPing(net.JoinHostPort("127.0.0.1", strconv.Itoa(i))), "tcp port %d", i) 34 | } 35 | } 36 | 37 | func TestClash_ListenerCreate(t *testing.T) { 38 | basic := ` 39 | log-level: silent 40 | ` 41 | err := parseAndApply(basic) 42 | require.NoError(t, err) 43 | defer cleanup() 44 | 45 | time.Sleep(waitTime) 46 | tcpIn := tunnel.TCPIn() 47 | udpIn := tunnel.UDPIn() 48 | 49 | ports := listener.Ports{ 50 | Port: 7890, 51 | } 52 | listener.ReCreatePortsListeners(ports, tcpIn, udpIn) 53 | require.True(t, TCPing("127.0.0.1:7890")) 54 | require.Equal(t, ports, *listener.GetPorts()) 55 | 56 | inbounds := []C.Inbound{ 57 | { 58 | Type: C.InboundTypeHTTP, 59 | BindAddress: "127.0.0.1:7891", 60 | }, 61 | } 62 | listener.ReCreateListeners(inbounds, tcpIn, udpIn) 63 | require.True(t, TCPing("127.0.0.1:7890")) 64 | require.Equal(t, ports, *listener.GetPorts()) 65 | 66 | require.True(t, TCPing("127.0.0.1:7891")) 67 | require.Equal(t, len(inbounds), len(listener.GetInbounds())) 68 | 69 | ports.Port = 0 70 | ports.SocksPort = 7892 71 | listener.ReCreatePortsListeners(ports, tcpIn, udpIn) 72 | require.False(t, TCPing("127.0.0.1:7890")) 73 | require.True(t, TCPing("127.0.0.1:7892")) 74 | require.Equal(t, ports, *listener.GetPorts()) 75 | 76 | require.True(t, TCPing("127.0.0.1:7891")) 77 | require.Equal(t, len(inbounds), len(listener.GetInbounds())) 78 | } 79 | -------------------------------------------------------------------------------- /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/rule_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestClash_RuleInbound(t *testing.T) { 11 | basic := ` 12 | socks-port: 7890 13 | inbounds: 14 | - socks://127.0.0.1:7891 15 | - type: socks 16 | bind-address: 127.0.0.1:7892 17 | rules: 18 | - INBOUND-PORT,7891,REJECT 19 | log-level: silent 20 | ` 21 | 22 | err := parseAndApply(basic) 23 | require.NoError(t, err) 24 | defer cleanup() 25 | 26 | require.True(t, TCPing(net.JoinHostPort("127.0.0.1", "7890"))) 27 | require.True(t, TCPing(net.JoinHostPort("127.0.0.1", "7891"))) 28 | require.True(t, TCPing(net.JoinHostPort("127.0.0.1", "7892"))) 29 | 30 | require.Error(t, testPingPongWithSocksPort(t, 7891)) 31 | require.NoError(t, testPingPongWithSocksPort(t, 7890)) 32 | require.NoError(t, testPingPongWithSocksPort(t, 7892)) 33 | } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | "github.com/Dreamacro/clash/common/util" 13 | ) 14 | 15 | type httpConn struct { 16 | net.Conn 17 | cfg *HTTPConfig 18 | reader *bufio.Reader 19 | whandshake bool 20 | } 21 | 22 | type HTTPConfig struct { 23 | Method string 24 | Host string 25 | Path []string 26 | Headers map[string][]string 27 | } 28 | 29 | // Read implements net.Conn.Read() 30 | func (hc *httpConn) Read(b []byte) (int, error) { 31 | if hc.reader != nil { 32 | n, err := hc.reader.Read(b) 33 | return n, err 34 | } 35 | 36 | reader := textproto.NewConn(hc.Conn) 37 | // First line: GET /index.html HTTP/1.0 38 | if _, err := reader.ReadLine(); err != nil { 39 | return 0, err 40 | } 41 | 42 | if _, err := reader.ReadMIMEHeader(); err != nil { 43 | return 0, err 44 | } 45 | 46 | hc.reader = reader.R 47 | return reader.R.Read(b) 48 | } 49 | 50 | // Write implements io.Writer. 51 | func (hc *httpConn) Write(b []byte) (int, error) { 52 | if hc.whandshake { 53 | return hc.Conn.Write(b) 54 | } 55 | 56 | path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] 57 | host := hc.cfg.Host 58 | if header := hc.cfg.Headers["Host"]; len(header) != 0 { 59 | host = header[rand.Intn(len(header))] 60 | } 61 | 62 | u := fmt.Sprintf("http://%s%s", host, path) 63 | req, _ := http.NewRequest(util.EmptyOr(hc.cfg.Method, http.MethodGet), u, bytes.NewBuffer(b)) 64 | for key, list := range hc.cfg.Headers { 65 | req.Header.Set(key, list[rand.Intn(len(list))]) 66 | } 67 | req.ContentLength = int64(len(b)) 68 | if err := req.Write(hc.Conn); err != nil { 69 | return 0, err 70 | } 71 | hc.whandshake = true 72 | return len(b), nil 73 | } 74 | 75 | func (hc *httpConn) Close() error { 76 | return hc.Conn.Close() 77 | } 78 | 79 | func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn { 80 | return &httpConn{ 81 | Conn: conn, 82 | cfg: cfg, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------