├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── docker.yml │ └── go.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── adapters ├── inbound │ ├── http.go │ ├── https.go │ ├── packet.go │ ├── socket.go │ └── util.go ├── outbound │ ├── base.go │ ├── direct.go │ ├── http.go │ ├── parser.go │ ├── reject.go │ ├── shadowsocks.go │ ├── shadowsocksr.go │ ├── snell.go │ ├── socks5.go │ ├── trojan.go │ ├── util.go │ └── vmess.go ├── outboundgroup │ ├── common.go │ ├── fallback.go │ ├── loadbalance.go │ ├── parser.go │ ├── relay.go │ ├── selector.go │ ├── urltest.go │ └── util.go └── provider │ ├── fetcher.go │ ├── healthcheck.go │ ├── parser.go │ ├── provider.go │ └── vehicle.go ├── common ├── cache │ ├── cache.go │ ├── cache_test.go │ ├── lrucache.go │ └── lrucache_test.go ├── murmur3 │ ├── murmur.go │ └── murmur32.go ├── observable │ ├── iterable.go │ ├── observable.go │ ├── observable_test.go │ └── subscriber.go ├── picker │ ├── picker.go │ └── picker_test.go ├── pool │ ├── alloc.go │ ├── alloc_test.go │ └── pool.go ├── queue │ └── queue.go ├── singledo │ ├── singledo.go │ └── singledo_test.go ├── sockopt │ ├── reuseaddr_linux.go │ └── reuseaddr_other.go └── structure │ ├── structure.go │ └── structure_test.go ├── component ├── auth │ └── auth.go ├── dialer │ ├── dialer.go │ └── hook.go ├── fakeip │ ├── pool.go │ └── pool_test.go ├── mmdb │ └── mmdb.go ├── nat │ └── table.go ├── process │ ├── process.go │ ├── process_darwin.go │ ├── process_freebsd_amd64.go │ ├── process_linux.go │ ├── process_other.go │ └── process_windows.go ├── resolver │ └── resolver.go ├── simple-obfs │ ├── http.go │ └── tls.go ├── snell │ ├── cipher.go │ └── snell.go ├── socks5 │ └── socks5.go ├── trie │ ├── domain.go │ ├── domain_test.go │ └── node.go ├── trojan │ └── trojan.go ├── v2ray-plugin │ ├── mux.go │ └── websocket.go └── vmess │ ├── aead.go │ ├── chunk.go │ ├── conn.go │ ├── http.go │ ├── tls.go │ ├── user.go │ ├── vmess.go │ └── websocket.go ├── config ├── config.go ├── initial.go └── utils.go ├── constant ├── adapters.go ├── metadata.go ├── path.go ├── rule.go └── version.go ├── dns ├── client.go ├── doh.go ├── filters.go ├── middleware.go ├── resolver.go ├── server.go └── util.go ├── docs └── logo.png ├── go.mod ├── go.sum ├── hub ├── executor │ └── executor.go ├── hub.go └── route │ ├── common.go │ ├── configs.go │ ├── connections.go │ ├── ctxkeys.go │ ├── errors.go │ ├── provider.go │ ├── proxies.go │ ├── rules.go │ └── server.go ├── log ├── level.go └── log.go ├── main.go ├── proxy ├── auth │ └── auth.go ├── http │ └── server.go ├── listener.go ├── mixed │ ├── conn.go │ └── mixed.go ├── redir │ ├── tcp.go │ ├── tcp_darwin.go │ ├── tcp_freebsd.go │ ├── tcp_linux.go │ ├── tcp_linux_386.go │ ├── tcp_linux_other.go │ ├── tcp_windows.go │ ├── udp.go │ ├── udp_linux.go │ ├── udp_other.go │ ├── utils.go │ ├── utils_linux.go │ └── utils_other.go └── socks │ ├── tcp.go │ ├── udp.go │ └── utils.go ├── rules ├── base.go ├── domain.go ├── domain_keyword.go ├── domain_suffix.go ├── final.go ├── geoip.go ├── ipcidr.go ├── parser.go ├── port.go └── process.go └── tunnel ├── connection.go ├── manager.go ├── mode.go ├── tracker.go └── tunnel.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 感谢你向 Clash Core 提交 issue! 12 | 在提交之前,请确认: 13 | 14 | - [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题 15 | - [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 Openclash、Koolclash 等)的特定问题 16 | - [ ] 我已经使用 Clash core 的 dev 分支版本测试过,问题依旧存在 17 | - [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧! 18 | 19 | 请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。 20 | 21 | 32 | 33 | 我都确认过了,我要继续提交。 34 | 35 | ------------------------------------------------------------------ 36 | 37 | 请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。 38 | 39 | 40 | ### clash core config 41 | 45 | ``` 46 | …… 47 | ``` 48 | 49 | ### Clash log 50 | 54 | ``` 55 | …… 56 | ``` 57 | 58 | ### 环境 Environment 59 | 60 | * Clash Core 的操作系统 (the OS that the Clash core is running on) 61 | …… 62 | * 使用者的操作系统 (the OS running on the client) 63 | …… 64 | * 网路环境或拓扑 (network conditions/topology) 65 | …… 66 | * iptables,如果适用 (if applicable) 67 | …… 68 | * ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) 69 | …… 70 | * 其他 71 | …… 72 | 73 | ### 说明 Description 74 | 75 | 78 | 79 | ### 重现问题的具体布骤 Steps to Reproduce 80 | 81 | 1. [First Step] 82 | 2. [Second Step] 83 | 3. …… 84 | 85 | **我预期会发生……?** 86 | 87 | 88 | **实际上发生了什麽?** 89 | 90 | 91 | ### 可能的解决方案 Possible Solution 92 | 93 | 94 | 95 | 96 | ### 更多信息 More Information 97 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 感谢你向 Clash Core 提交 Feature Request! 12 | 在提交之前,请确认: 13 | 14 | - [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求 15 | 16 | 请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。 17 | 18 | 26 | 27 | 我都确认过了,我要继续提交。 28 | 29 | ------------------------------------------------------------------ 30 | 31 | 请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。 32 | 33 | 34 | ### Clash core config 35 | 39 | ``` 40 | …… 41 | ``` 42 | 43 | ### Clash log 44 | 48 | ``` 49 | …… 50 | ``` 51 | 52 | ### 环境 Environment 53 | 54 | * Clash Core 的操作系统 (the OS that the Clash core is running on) 55 | …… 56 | * 使用者的操作系统 (the OS running on the client) 57 | …… 58 | * 网路环境或拓扑 (network conditions/topology) 59 | …… 60 | * iptables,如果适用 (if applicable) 61 | …… 62 | * ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) 63 | …… 64 | * 其他 65 | …… 66 | 67 | ### 说明 Description 68 | 69 | 72 | 73 | ### 可能的解决方案 Possible Solution 74 | 75 | 76 | 77 | 78 | ### 更多信息 More Information 79 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | on: 3 | push: 4 | branches: 5 | - dev 6 | tags: 7 | - '*' 8 | jobs: 9 | 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up docker buildx 21 | id: buildx 22 | uses: crazy-max/ghaction-docker-buildx@v2 23 | with: 24 | buildx-version: latest 25 | skip-cache: false 26 | qemu-version: latest 27 | 28 | - name: Docker login 29 | env: 30 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 31 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 32 | run: | 33 | echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin 34 | 35 | - name: Docker buildx image and push on dev branch 36 | if: github.ref == 'refs/heads/dev' 37 | run: | 38 | docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev . 39 | 40 | - name: Replace tag without `v` 41 | if: startsWith(github.ref, 'refs/tags/') 42 | uses: actions/github-script@v1 43 | id: version 44 | with: 45 | script: | 46 | return context.payload.ref.replace(/\/refs\/tags\/v/, '') 47 | result-encoding: string 48 | 49 | - name: Docker buildx image and push on release 50 | if: startsWith(github.ref, 'refs/tags/') 51 | run: | 52 | docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} . 53 | docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest . 54 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push, pull_request] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Setup Go 10 | uses: actions/setup-go@v2 11 | with: 12 | go-version: 1.14.x 13 | 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v2 16 | 17 | - name: Cache go module 18 | uses: actions/cache@v2 19 | with: 20 | path: ~/go/pkg/mod 21 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 22 | restore-keys: | 23 | ${{ runner.os }}-go- 24 | 25 | - name: Get dependencies and run test 26 | run: | 27 | go test ./... 28 | 29 | - name: Build 30 | if: startsWith(github.ref, 'refs/tags/') 31 | env: 32 | NAME: clash 33 | BINDIR: bin 34 | run: make -j releases 35 | 36 | - name: Upload Release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | files: bin/* 43 | draft: true 44 | prerelease: true 45 | -------------------------------------------------------------------------------- /.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 | # dep 16 | vendor 17 | 18 | # GoLand 19 | .idea/* 20 | 21 | # macOS file 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | RUN apk add --no-cache make git && \ 4 | wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb 5 | WORKDIR /clash-src 6 | COPY . /clash-src 7 | RUN go mod download && \ 8 | make linux-amd64 && \ 9 | mv ./bin/clash-linux-amd64 /clash 10 | 11 | FROM alpine:latest 12 | 13 | RUN apk add --no-cache ca-certificates 14 | COPY --from=builder /Country.mmdb /root/.config/clash/ 15 | COPY --from=builder /clash / 16 | ENTRYPOINT ["/clash"] 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=clash 2 | BINDIR=bin 3 | VERSION=$(shell git describe --tags || echo "unknown version") 4 | BUILDTIME=$(shell date -u) 5 | GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ 6 | -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ 7 | -w -s' 8 | 9 | PLATFORM_LIST = \ 10 | darwin-amd64 \ 11 | linux-386 \ 12 | linux-amd64 \ 13 | linux-armv5 \ 14 | linux-armv6 \ 15 | linux-armv7 \ 16 | linux-armv8 \ 17 | linux-mips-softfloat \ 18 | linux-mips-hardfloat \ 19 | linux-mipsle-softfloat \ 20 | linux-mipsle-hardfloat \ 21 | linux-mips64 \ 22 | linux-mips64le \ 23 | freebsd-386 \ 24 | freebsd-amd64 25 | 26 | WINDOWS_ARCH_LIST = \ 27 | windows-386 \ 28 | windows-amd64 29 | 30 | all: linux-amd64 darwin-amd64 windows-amd64 # Most used 31 | 32 | darwin-amd64: 33 | GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 34 | 35 | linux-386: 36 | GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 37 | 38 | linux-amd64: 39 | GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 40 | 41 | linux-armv5: 42 | GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 43 | 44 | linux-armv6: 45 | GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 46 | 47 | linux-armv7: 48 | GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 49 | 50 | linux-armv8: 51 | GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 52 | 53 | linux-mips-softfloat: 54 | GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 55 | 56 | linux-mips-hardfloat: 57 | GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 58 | 59 | linux-mipsle-softfloat: 60 | GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 61 | 62 | linux-mipsle-hardfloat: 63 | GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 64 | 65 | linux-mips64: 66 | GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 67 | 68 | linux-mips64le: 69 | GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 70 | 71 | freebsd-386: 72 | GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 73 | 74 | freebsd-amd64: 75 | GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 76 | 77 | windows-386: 78 | GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe 79 | 80 | windows-amd64: 81 | GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe 82 | 83 | gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) 84 | zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) 85 | 86 | $(gz_releases): %.gz : % 87 | chmod +x $(BINDIR)/$(NAME)-$(basename $@) 88 | gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@) 89 | 90 | $(zip_releases): %.zip : % 91 | zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe 92 | 93 | all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) 94 | 95 | releases: $(gz_releases) $(zip_releases) 96 | clean: 97 | rm $(BINDIR)/* 98 | -------------------------------------------------------------------------------- /adapters/inbound/http.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | // HTTPAdapter is a adapter for HTTP connection 12 | type HTTPAdapter struct { 13 | net.Conn 14 | metadata *C.Metadata 15 | R *http.Request 16 | } 17 | 18 | // Metadata return destination metadata 19 | func (h *HTTPAdapter) Metadata() *C.Metadata { 20 | return h.metadata 21 | } 22 | 23 | // NewHTTP is HTTPAdapter generator 24 | func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { 25 | metadata := parseHTTPAddr(request) 26 | metadata.Type = C.HTTP 27 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 28 | metadata.SrcIP = ip 29 | metadata.SrcPort = port 30 | } 31 | return &HTTPAdapter{ 32 | metadata: metadata, 33 | R: request, 34 | Conn: conn, 35 | } 36 | } 37 | 38 | // RemoveHopByHopHeaders remove hop-by-hop header 39 | func RemoveHopByHopHeaders(header http.Header) { 40 | // Strip hop-by-hop header based on RFC: 41 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 42 | // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do 43 | 44 | header.Del("Proxy-Connection") 45 | header.Del("Proxy-Authenticate") 46 | header.Del("Proxy-Authorization") 47 | header.Del("TE") 48 | header.Del("Trailers") 49 | header.Del("Transfer-Encoding") 50 | header.Del("Upgrade") 51 | 52 | connections := header.Get("Connection") 53 | header.Del("Connection") 54 | if len(connections) == 0 { 55 | return 56 | } 57 | for _, h := range strings.Split(connections, ",") { 58 | header.Del(strings.TrimSpace(h)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /adapters/inbound/https.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | // NewHTTPS is HTTPAdapter generator 11 | func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { 12 | metadata := parseHTTPAddr(request) 13 | metadata.Type = C.HTTPCONNECT 14 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 15 | metadata.SrcIP = ip 16 | metadata.SrcPort = port 17 | } 18 | return &SocketAdapter{ 19 | metadata: metadata, 20 | Conn: conn, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /adapters/inbound/packet.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/socks5" 5 | C "github.com/Dreamacro/clash/constant" 6 | ) 7 | 8 | // PacketAdapter is a UDP Packet adapter for socks/redir/tun 9 | type PacketAdapter struct { 10 | C.UDPPacket 11 | metadata *C.Metadata 12 | } 13 | 14 | // Metadata returns destination metadata 15 | func (s *PacketAdapter) Metadata() *C.Metadata { 16 | return s.metadata 17 | } 18 | 19 | // NewPacket is PacketAdapter generator 20 | func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter { 21 | metadata := parseSocksAddr(target) 22 | metadata.NetWork = C.UDP 23 | metadata.Type = source 24 | if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil { 25 | metadata.SrcIP = ip 26 | metadata.SrcPort = port 27 | } 28 | 29 | return &PacketAdapter{ 30 | UDPPacket: packet, 31 | metadata: metadata, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /adapters/inbound/socket.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/component/socks5" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | // SocketAdapter is a adapter for socks and redir connection 11 | type SocketAdapter struct { 12 | net.Conn 13 | metadata *C.Metadata 14 | } 15 | 16 | // Metadata return destination metadata 17 | func (s *SocketAdapter) Metadata() *C.Metadata { 18 | return s.metadata 19 | } 20 | 21 | // NewSocket is SocketAdapter generator 22 | func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter { 23 | metadata := parseSocksAddr(target) 24 | metadata.NetWork = C.TCP 25 | metadata.Type = source 26 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 27 | metadata.SrcIP = ip 28 | metadata.SrcPort = port 29 | } 30 | 31 | return &SocketAdapter{ 32 | Conn: conn, 33 | metadata: metadata, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /adapters/inbound/util.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/Dreamacro/clash/component/socks5" 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | func parseSocksAddr(target socks5.Addr) *C.Metadata { 14 | metadata := &C.Metadata{ 15 | AddrType: int(target[0]), 16 | } 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 = strconv.Itoa((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 = strconv.Itoa((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 = strconv.Itoa((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 := request.URL.Port() 39 | if port == "" { 40 | port = "80" 41 | } 42 | 43 | // trim FQDN (#737) 44 | host = strings.TrimRight(host, ".") 45 | 46 | metadata := &C.Metadata{ 47 | NetWork: C.TCP, 48 | AddrType: C.AtypDomainName, 49 | Host: host, 50 | DstIP: nil, 51 | DstPort: port, 52 | } 53 | 54 | ip := net.ParseIP(host) 55 | if ip != nil { 56 | switch { 57 | case ip.To4() == nil: 58 | metadata.AddrType = C.AtypIPv6 59 | default: 60 | metadata.AddrType = C.AtypIPv4 61 | } 62 | metadata.DstIP = ip 63 | } 64 | 65 | return metadata 66 | } 67 | 68 | func parseAddr(addr string) (net.IP, string, error) { 69 | host, port, err := net.SplitHostPort(addr) 70 | if err != nil { 71 | return nil, "", err 72 | } 73 | 74 | ip := net.ParseIP(host) 75 | return ip, port, nil 76 | } 77 | -------------------------------------------------------------------------------- /adapters/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 | func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 16 | address := net.JoinHostPort(metadata.String(), metadata.DstPort) 17 | 18 | c, err := dialer.DialContext(ctx, "tcp", address) 19 | if err != nil { 20 | return nil, err 21 | } 22 | tcpKeepAlive(c) 23 | return NewConn(c, d), nil 24 | } 25 | 26 | func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 27 | pc, err := dialer.ListenPacket("udp", "") 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 | -------------------------------------------------------------------------------- /adapters/outbound/http.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | "strconv" 15 | 16 | "github.com/Dreamacro/clash/component/dialer" 17 | C "github.com/Dreamacro/clash/constant" 18 | ) 19 | 20 | type Http struct { 21 | *Base 22 | user string 23 | pass string 24 | tlsConfig *tls.Config 25 | } 26 | 27 | type HttpOption struct { 28 | Name string `proxy:"name"` 29 | Server string `proxy:"server"` 30 | Port int `proxy:"port"` 31 | UserName string `proxy:"username,omitempty"` 32 | Password string `proxy:"password,omitempty"` 33 | TLS bool `proxy:"tls,omitempty"` 34 | SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 35 | } 36 | 37 | func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 38 | if h.tlsConfig != nil { 39 | cc := tls.Client(c, h.tlsConfig) 40 | err := cc.Handshake() 41 | c = cc 42 | if err != nil { 43 | return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 44 | } 45 | } 46 | 47 | if err := h.shakeHand(metadata, c); err != nil { 48 | return nil, err 49 | } 50 | return c, nil 51 | } 52 | 53 | func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 54 | c, err := dialer.DialContext(ctx, "tcp", h.addr) 55 | if err != nil { 56 | return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 57 | } 58 | tcpKeepAlive(c) 59 | 60 | c, err = h.StreamConn(c, metadata) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return NewConn(c, h), nil 66 | } 67 | 68 | func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 69 | addr := metadata.RemoteAddress() 70 | req := &http.Request{ 71 | Method: http.MethodConnect, 72 | URL: &url.URL{ 73 | Host: addr, 74 | }, 75 | Host: addr, 76 | Header: http.Header{ 77 | "Proxy-Connection": []string{"Keep-Alive"}, 78 | }, 79 | } 80 | 81 | if h.user != "" && h.pass != "" { 82 | auth := h.user + ":" + h.pass 83 | req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 84 | } 85 | 86 | if err := req.Write(rw); err != nil { 87 | return err 88 | } 89 | 90 | resp, err := http.ReadResponse(bufio.NewReader(rw), req) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | if resp.StatusCode == http.StatusOK { 96 | return nil 97 | } 98 | 99 | if resp.StatusCode == http.StatusProxyAuthRequired { 100 | return errors.New("HTTP need auth") 101 | } 102 | 103 | if resp.StatusCode == http.StatusMethodNotAllowed { 104 | return errors.New("CONNECT method not allowed by proxy") 105 | } 106 | 107 | if resp.StatusCode >= http.StatusInternalServerError { 108 | return errors.New(resp.Status) 109 | } 110 | 111 | return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 112 | } 113 | 114 | func NewHttp(option HttpOption) *Http { 115 | var tlsConfig *tls.Config 116 | if option.TLS { 117 | tlsConfig = &tls.Config{ 118 | InsecureSkipVerify: option.SkipCertVerify, 119 | ClientSessionCache: getClientSessionCache(), 120 | ServerName: option.Server, 121 | } 122 | } 123 | 124 | return &Http{ 125 | Base: &Base{ 126 | name: option.Name, 127 | addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 128 | tp: C.Http, 129 | }, 130 | user: option.UserName, 131 | pass: option.Password, 132 | tlsConfig: tlsConfig, 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /adapters/outbound/parser.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Dreamacro/clash/common/structure" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { 11 | decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) 12 | proxyType, existType := mapping["type"].(string) 13 | if !existType { 14 | return nil, fmt.Errorf("Missing type") 15 | } 16 | 17 | var proxy C.ProxyAdapter 18 | err := fmt.Errorf("Cannot parse") 19 | switch proxyType { 20 | case "ss": 21 | ssOption := &ShadowSocksOption{} 22 | err = decoder.Decode(mapping, ssOption) 23 | if err != nil { 24 | break 25 | } 26 | proxy, err = NewShadowSocks(*ssOption) 27 | case "ssr": 28 | ssrOption := &ShadowSocksROption{} 29 | err = decoder.Decode(mapping, ssrOption) 30 | if err != nil { 31 | break 32 | } 33 | proxy, err = NewShadowSocksR(*ssrOption) 34 | case "socks5": 35 | socksOption := &Socks5Option{} 36 | err = decoder.Decode(mapping, socksOption) 37 | if err != nil { 38 | break 39 | } 40 | proxy = NewSocks5(*socksOption) 41 | case "http": 42 | httpOption := &HttpOption{} 43 | err = decoder.Decode(mapping, httpOption) 44 | if err != nil { 45 | break 46 | } 47 | proxy = NewHttp(*httpOption) 48 | case "vmess": 49 | vmessOption := &VmessOption{ 50 | HTTPOpts: HTTPOptions{ 51 | Method: "GET", 52 | Path: []string{"/"}, 53 | }, 54 | } 55 | err = decoder.Decode(mapping, vmessOption) 56 | if err != nil { 57 | break 58 | } 59 | proxy, err = NewVmess(*vmessOption) 60 | case "snell": 61 | snellOption := &SnellOption{} 62 | err = decoder.Decode(mapping, snellOption) 63 | if err != nil { 64 | break 65 | } 66 | proxy, err = NewSnell(*snellOption) 67 | case "trojan": 68 | trojanOption := &TrojanOption{} 69 | err = decoder.Decode(mapping, trojanOption) 70 | if err != nil { 71 | break 72 | } 73 | proxy, err = NewTrojan(*trojanOption) 74 | default: 75 | return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) 76 | } 77 | 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | return NewProxy(proxy), nil 83 | } 84 | -------------------------------------------------------------------------------- /adapters/outbound/reject.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "net" 8 | "time" 9 | 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | type Reject struct { 14 | *Base 15 | } 16 | 17 | func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 18 | return NewConn(&NopConn{}, r), nil 19 | } 20 | 21 | func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 22 | return nil, errors.New("match reject rule") 23 | } 24 | 25 | func NewReject() *Reject { 26 | return &Reject{ 27 | Base: &Base{ 28 | name: "REJECT", 29 | tp: C.Reject, 30 | udp: true, 31 | }, 32 | } 33 | } 34 | 35 | type NopConn struct{} 36 | 37 | func (rw *NopConn) Read(b []byte) (int, error) { 38 | return 0, io.EOF 39 | } 40 | 41 | func (rw *NopConn) Write(b []byte) (int, error) { 42 | return 0, io.EOF 43 | } 44 | 45 | // Close is fake function for net.Conn 46 | func (rw *NopConn) Close() error { return nil } 47 | 48 | // LocalAddr is fake function for net.Conn 49 | func (rw *NopConn) LocalAddr() net.Addr { return nil } 50 | 51 | // RemoteAddr is fake function for net.Conn 52 | func (rw *NopConn) RemoteAddr() net.Addr { return nil } 53 | 54 | // SetDeadline is fake function for net.Conn 55 | func (rw *NopConn) SetDeadline(time.Time) error { return nil } 56 | 57 | // SetReadDeadline is fake function for net.Conn 58 | func (rw *NopConn) SetReadDeadline(time.Time) error { return nil } 59 | 60 | // SetWriteDeadline is fake function for net.Conn 61 | func (rw *NopConn) SetWriteDeadline(time.Time) error { return nil } 62 | -------------------------------------------------------------------------------- /adapters/outbound/snell.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | 9 | "github.com/Dreamacro/clash/common/structure" 10 | "github.com/Dreamacro/clash/component/dialer" 11 | obfs "github.com/Dreamacro/clash/component/simple-obfs" 12 | "github.com/Dreamacro/clash/component/snell" 13 | C "github.com/Dreamacro/clash/constant" 14 | ) 15 | 16 | type Snell struct { 17 | *Base 18 | psk []byte 19 | obfsOption *simpleObfsOption 20 | } 21 | 22 | type SnellOption struct { 23 | Name string `proxy:"name"` 24 | Server string `proxy:"server"` 25 | Port int `proxy:"port"` 26 | Psk string `proxy:"psk"` 27 | ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` 28 | } 29 | 30 | func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 31 | switch s.obfsOption.Mode { 32 | case "tls": 33 | c = obfs.NewTLSObfs(c, s.obfsOption.Host) 34 | case "http": 35 | _, port, _ := net.SplitHostPort(s.addr) 36 | c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) 37 | } 38 | c = snell.StreamConn(c, s.psk) 39 | port, _ := strconv.Atoi(metadata.DstPort) 40 | err := snell.WriteHeader(c, metadata.String(), uint(port)) 41 | return c, err 42 | } 43 | 44 | func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 45 | c, err := dialer.DialContext(ctx, "tcp", s.addr) 46 | if err != nil { 47 | return nil, fmt.Errorf("%s connect error: %w", s.addr, err) 48 | } 49 | tcpKeepAlive(c) 50 | 51 | c, err = s.StreamConn(c, metadata) 52 | return NewConn(c, s), err 53 | } 54 | 55 | func NewSnell(option SnellOption) (*Snell, error) { 56 | addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 57 | psk := []byte(option.Psk) 58 | 59 | decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) 60 | obfsOption := &simpleObfsOption{Host: "bing.com"} 61 | if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { 62 | return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) 63 | } 64 | 65 | if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { 66 | return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) 67 | } 68 | 69 | return &Snell{ 70 | Base: &Base{ 71 | name: option.Name, 72 | addr: addr, 73 | tp: C.Snell, 74 | }, 75 | psk: psk, 76 | obfsOption: obfsOption, 77 | }, nil 78 | } 79 | -------------------------------------------------------------------------------- /adapters/outbound/trojan.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "strconv" 9 | 10 | "github.com/Dreamacro/clash/component/dialer" 11 | "github.com/Dreamacro/clash/component/trojan" 12 | C "github.com/Dreamacro/clash/constant" 13 | ) 14 | 15 | type Trojan struct { 16 | *Base 17 | instance *trojan.Trojan 18 | } 19 | 20 | type TrojanOption struct { 21 | Name string `proxy:"name"` 22 | Server string `proxy:"server"` 23 | Port int `proxy:"port"` 24 | Password string `proxy:"password"` 25 | ALPN []string `proxy:"alpn,omitempty"` 26 | SNI string `proxy:"sni,omitempty"` 27 | SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 28 | UDP bool `proxy:"udp,omitempty"` 29 | } 30 | 31 | func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 32 | c, err := t.instance.StreamConn(c) 33 | if err != nil { 34 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 35 | } 36 | 37 | err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) 38 | return c, err 39 | } 40 | 41 | func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 42 | c, err := dialer.DialContext(ctx, "tcp", t.addr) 43 | if err != nil { 44 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 45 | } 46 | tcpKeepAlive(c) 47 | c, err = t.StreamConn(c, metadata) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return NewConn(c, t), err 53 | } 54 | 55 | func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 56 | ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 57 | defer cancel() 58 | c, err := dialer.DialContext(ctx, "tcp", t.addr) 59 | if err != nil { 60 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 61 | } 62 | tcpKeepAlive(c) 63 | c, err = t.instance.StreamConn(c) 64 | if err != nil { 65 | return nil, fmt.Errorf("%s connect error: %w", t.addr, err) 66 | } 67 | 68 | err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | pc := t.instance.PacketConn(c) 74 | return newPacketConn(pc, t), err 75 | } 76 | 77 | func (t *Trojan) MarshalJSON() ([]byte, error) { 78 | return json.Marshal(map[string]string{ 79 | "type": t.Type().String(), 80 | }) 81 | } 82 | 83 | func NewTrojan(option TrojanOption) (*Trojan, error) { 84 | addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 85 | 86 | tOption := &trojan.Option{ 87 | Password: option.Password, 88 | ALPN: option.ALPN, 89 | ServerName: option.Server, 90 | SkipCertVerify: option.SkipCertVerify, 91 | ClientSessionCache: getClientSessionCache(), 92 | } 93 | 94 | if option.SNI != "" { 95 | tOption.ServerName = option.SNI 96 | } 97 | 98 | return &Trojan{ 99 | Base: &Base{ 100 | name: option.Name, 101 | addr: addr, 102 | tp: C.Trojan, 103 | udp: option.UDP, 104 | }, 105 | instance: trojan.New(tOption), 106 | }, nil 107 | } 108 | -------------------------------------------------------------------------------- /adapters/outbound/util.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "strconv" 10 | "sync" 11 | "time" 12 | 13 | "github.com/Dreamacro/clash/component/resolver" 14 | "github.com/Dreamacro/clash/component/socks5" 15 | C "github.com/Dreamacro/clash/constant" 16 | ) 17 | 18 | const ( 19 | tcpTimeout = 5 * time.Second 20 | ) 21 | 22 | var ( 23 | globalClientSessionCache tls.ClientSessionCache 24 | once sync.Once 25 | ) 26 | 27 | func urlToMetadata(rawURL string) (addr C.Metadata, err error) { 28 | u, err := url.Parse(rawURL) 29 | if err != nil { 30 | return 31 | } 32 | 33 | port := u.Port() 34 | if port == "" { 35 | switch u.Scheme { 36 | case "https": 37 | port = "443" 38 | case "http": 39 | port = "80" 40 | default: 41 | err = fmt.Errorf("%s scheme not Support", rawURL) 42 | return 43 | } 44 | } 45 | 46 | addr = C.Metadata{ 47 | AddrType: C.AtypDomainName, 48 | Host: u.Hostname(), 49 | DstIP: nil, 50 | DstPort: port, 51 | } 52 | return 53 | } 54 | 55 | func tcpKeepAlive(c net.Conn) { 56 | // Disable tcp keep alive to save battery 57 | // if tcp, ok := c.(*net.TCPConn); ok { 58 | // tcp.SetKeepAlive(true) 59 | // tcp.SetKeepAlivePeriod(30 * time.Second) 60 | // } 61 | } 62 | 63 | func getClientSessionCache() tls.ClientSessionCache { 64 | once.Do(func() { 65 | globalClientSessionCache = tls.NewLRUClientSessionCache(128) 66 | }) 67 | return globalClientSessionCache 68 | } 69 | 70 | func serializesSocksAddr(metadata *C.Metadata) []byte { 71 | var buf [][]byte 72 | aType := uint8(metadata.AddrType) 73 | p, _ := strconv.Atoi(metadata.DstPort) 74 | port := []byte{uint8(p >> 8), uint8(p & 0xff)} 75 | switch metadata.AddrType { 76 | case socks5.AtypDomainName: 77 | len := uint8(len(metadata.Host)) 78 | host := []byte(metadata.Host) 79 | buf = [][]byte{{aType, len}, host, port} 80 | case socks5.AtypIPv4: 81 | host := metadata.DstIP.To4() 82 | buf = [][]byte{{aType}, host, port} 83 | case socks5.AtypIPv6: 84 | host := metadata.DstIP.To16() 85 | buf = [][]byte{{aType}, host, port} 86 | } 87 | return bytes.Join(buf, nil) 88 | } 89 | 90 | func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { 91 | host, port, err := net.SplitHostPort(address) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | ip, err := resolver.ResolveIP(host) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 101 | } 102 | -------------------------------------------------------------------------------- /adapters/outboundgroup/common.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Dreamacro/clash/adapters/provider" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | const ( 11 | defaultGetProxiesDuration = time.Second * 5 12 | ) 13 | 14 | type ProxyGroup interface { 15 | C.ProxyAdapter 16 | GetProxyProviders() []provider.ProxyProvider 17 | Now() string 18 | } 19 | 20 | func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy { 21 | proxies := []C.Proxy{} 22 | for _, provider := range providers { 23 | proxies = append(proxies, provider.Proxies()...) 24 | } 25 | return proxies 26 | } 27 | -------------------------------------------------------------------------------- /adapters/outboundgroup/fallback.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/Dreamacro/clash/adapters/outbound" 8 | "github.com/Dreamacro/clash/adapters/provider" 9 | "github.com/Dreamacro/clash/common/singledo" 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | type Fallback struct { 14 | *outbound.Base 15 | single *singledo.Single 16 | providers []provider.ProxyProvider 17 | } 18 | 19 | func (f *Fallback) GetProxyProviders() []provider.ProxyProvider { 20 | return f.providers 21 | } 22 | 23 | func (f *Fallback) Now() string { 24 | proxy := f.findAliveProxy() 25 | return proxy.Name() 26 | } 27 | 28 | func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 29 | proxy := f.findAliveProxy() 30 | c, err := proxy.DialContext(ctx, metadata) 31 | if err == nil { 32 | c.AppendToChains(f) 33 | } 34 | return c, err 35 | } 36 | 37 | func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 38 | proxy := f.findAliveProxy() 39 | pc, err := proxy.DialUDP(metadata) 40 | if err == nil { 41 | pc.AppendToChains(f) 42 | } 43 | return pc, err 44 | } 45 | 46 | func (f *Fallback) SupportUDP() bool { 47 | proxy := f.findAliveProxy() 48 | return proxy.SupportUDP() 49 | } 50 | 51 | func (f *Fallback) MarshalJSON() ([]byte, error) { 52 | var all []string 53 | for _, proxy := range f.proxies() { 54 | all = append(all, proxy.Name()) 55 | } 56 | return json.Marshal(map[string]interface{}{ 57 | "type": f.Type().String(), 58 | "now": f.Now(), 59 | "all": all, 60 | }) 61 | } 62 | 63 | func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { 64 | proxy := f.findAliveProxy() 65 | return proxy 66 | } 67 | 68 | func (f *Fallback) proxies() []C.Proxy { 69 | elm, _, _ := f.single.Do(func() (interface{}, error) { 70 | return getProvidersProxies(f.providers), nil 71 | }) 72 | 73 | return elm.([]C.Proxy) 74 | } 75 | 76 | func (f *Fallback) findAliveProxy() C.Proxy { 77 | proxies := f.proxies() 78 | for _, proxy := range proxies { 79 | if proxy.Alive() { 80 | return proxy 81 | } 82 | } 83 | 84 | return f.proxies()[0] 85 | } 86 | 87 | func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { 88 | return &Fallback{ 89 | Base: outbound.NewBase(name, "", C.Fallback, false), 90 | single: singledo.NewSingle(defaultGetProxiesDuration), 91 | providers: providers, 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /adapters/outboundgroup/loadbalance.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net" 7 | 8 | "github.com/Dreamacro/clash/adapters/outbound" 9 | "github.com/Dreamacro/clash/adapters/provider" 10 | "github.com/Dreamacro/clash/common/murmur3" 11 | "github.com/Dreamacro/clash/common/singledo" 12 | C "github.com/Dreamacro/clash/constant" 13 | 14 | "golang.org/x/net/publicsuffix" 15 | ) 16 | 17 | type LoadBalance struct { 18 | *outbound.Base 19 | single *singledo.Single 20 | maxRetry int 21 | providers []provider.ProxyProvider 22 | } 23 | 24 | func (lb *LoadBalance) GetProxyProviders() []provider.ProxyProvider { 25 | return lb.providers 26 | } 27 | 28 | func (lb *LoadBalance) Now() string { 29 | return "" 30 | } 31 | 32 | func getKey(metadata *C.Metadata) string { 33 | if metadata.Host != "" { 34 | // ip host 35 | if ip := net.ParseIP(metadata.Host); ip != nil { 36 | return metadata.Host 37 | } 38 | 39 | if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil { 40 | return etld 41 | } 42 | } 43 | 44 | if metadata.DstIP == nil { 45 | return "" 46 | } 47 | 48 | return metadata.DstIP.String() 49 | } 50 | 51 | func jumpHash(key uint64, buckets int32) int32 { 52 | var b, j int64 53 | 54 | for j < int64(buckets) { 55 | b = j 56 | key = key*2862933555777941757 + 1 57 | j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) 58 | } 59 | 60 | return int32(b) 61 | } 62 | 63 | func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { 64 | defer func() { 65 | if err == nil { 66 | c.AppendToChains(lb) 67 | } 68 | }() 69 | 70 | proxy := lb.Unwrap(metadata) 71 | 72 | c, err = proxy.DialContext(ctx, metadata) 73 | return 74 | } 75 | 76 | func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { 77 | defer func() { 78 | if err == nil { 79 | pc.AppendToChains(lb) 80 | } 81 | }() 82 | 83 | proxy := lb.Unwrap(metadata) 84 | 85 | return proxy.DialUDP(metadata) 86 | } 87 | 88 | func (lb *LoadBalance) SupportUDP() bool { 89 | return true 90 | } 91 | 92 | func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { 93 | key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) 94 | proxies := lb.proxies() 95 | buckets := int32(len(proxies)) 96 | for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { 97 | idx := jumpHash(key, buckets) 98 | proxy := proxies[idx] 99 | if proxy.Alive() { 100 | return proxy 101 | } 102 | } 103 | 104 | return proxies[0] 105 | } 106 | 107 | func (lb *LoadBalance) proxies() []C.Proxy { 108 | elm, _, _ := lb.single.Do(func() (interface{}, error) { 109 | return getProvidersProxies(lb.providers), nil 110 | }) 111 | 112 | return elm.([]C.Proxy) 113 | } 114 | 115 | func (lb *LoadBalance) MarshalJSON() ([]byte, error) { 116 | var all []string 117 | for _, proxy := range lb.proxies() { 118 | all = append(all, proxy.Name()) 119 | } 120 | return json.Marshal(map[string]interface{}{ 121 | "type": lb.Type().String(), 122 | "all": all, 123 | }) 124 | } 125 | 126 | func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { 127 | return &LoadBalance{ 128 | Base: outbound.NewBase(name, "", C.LoadBalance, false), 129 | single: singledo.NewSingle(defaultGetProxiesDuration), 130 | maxRetry: 3, 131 | providers: providers, 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /adapters/outboundgroup/relay.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/Dreamacro/clash/adapters/outbound" 10 | "github.com/Dreamacro/clash/adapters/provider" 11 | "github.com/Dreamacro/clash/common/singledo" 12 | "github.com/Dreamacro/clash/component/dialer" 13 | C "github.com/Dreamacro/clash/constant" 14 | ) 15 | 16 | type Relay struct { 17 | *outbound.Base 18 | single *singledo.Single 19 | providers []provider.ProxyProvider 20 | } 21 | 22 | func (r *Relay) GetProxyProviders() []provider.ProxyProvider { 23 | return r.providers 24 | } 25 | 26 | func (r *Relay) Now() string { 27 | return "" 28 | } 29 | 30 | func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 31 | proxies := r.proxies(metadata) 32 | if len(proxies) == 0 { 33 | return nil, errors.New("Proxy does not exist") 34 | } 35 | first := proxies[0] 36 | last := proxies[len(proxies)-1] 37 | 38 | c, err := dialer.DialContext(ctx, "tcp", first.Addr()) 39 | if err != nil { 40 | return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) 41 | } 42 | tcpKeepAlive(c) 43 | 44 | var currentMeta *C.Metadata 45 | for _, proxy := range proxies[1:] { 46 | currentMeta, err = addrToMetadata(proxy.Addr()) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | c, err = first.StreamConn(c, currentMeta) 52 | if err != nil { 53 | return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) 54 | } 55 | 56 | first = proxy 57 | } 58 | 59 | c, err = last.StreamConn(c, metadata) 60 | if err != nil { 61 | return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) 62 | } 63 | 64 | return outbound.NewConn(c, r), nil 65 | } 66 | 67 | func (r *Relay) MarshalJSON() ([]byte, error) { 68 | var all []string 69 | for _, proxy := range r.rawProxies() { 70 | all = append(all, proxy.Name()) 71 | } 72 | return json.Marshal(map[string]interface{}{ 73 | "type": r.Type().String(), 74 | "all": all, 75 | }) 76 | } 77 | 78 | func (r *Relay) rawProxies() []C.Proxy { 79 | elm, _, _ := r.single.Do(func() (interface{}, error) { 80 | return getProvidersProxies(r.providers), nil 81 | }) 82 | 83 | return elm.([]C.Proxy) 84 | } 85 | 86 | func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy { 87 | proxies := r.rawProxies() 88 | 89 | for n, proxy := range proxies { 90 | subproxy := proxy.Unwrap(metadata) 91 | for subproxy != nil { 92 | proxies[n] = subproxy 93 | subproxy = subproxy.Unwrap(metadata) 94 | } 95 | } 96 | 97 | return proxies 98 | } 99 | 100 | func NewRelay(name string, providers []provider.ProxyProvider) *Relay { 101 | return &Relay{ 102 | Base: outbound.NewBase(name, "", C.Relay, false), 103 | single: singledo.NewSingle(defaultGetProxiesDuration), 104 | providers: providers, 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /adapters/outboundgroup/selector.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/Dreamacro/clash/adapters/outbound" 9 | "github.com/Dreamacro/clash/adapters/provider" 10 | "github.com/Dreamacro/clash/common/singledo" 11 | C "github.com/Dreamacro/clash/constant" 12 | ) 13 | 14 | type Selector struct { 15 | *outbound.Base 16 | single *singledo.Single 17 | selected string 18 | providers []provider.ProxyProvider 19 | } 20 | 21 | func (s *Selector) GetProxyProviders() []provider.ProxyProvider { 22 | return s.providers 23 | } 24 | 25 | func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 26 | c, err := s.selectedProxy().DialContext(ctx, metadata) 27 | if err == nil { 28 | c.AppendToChains(s) 29 | } 30 | return c, err 31 | } 32 | 33 | func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 34 | pc, err := s.selectedProxy().DialUDP(metadata) 35 | if err == nil { 36 | pc.AppendToChains(s) 37 | } 38 | return pc, err 39 | } 40 | 41 | func (s *Selector) SupportUDP() bool { 42 | return s.selectedProxy().SupportUDP() 43 | } 44 | 45 | func (s *Selector) MarshalJSON() ([]byte, error) { 46 | var all []string 47 | for _, proxy := range getProvidersProxies(s.providers) { 48 | all = append(all, proxy.Name()) 49 | } 50 | 51 | return json.Marshal(map[string]interface{}{ 52 | "type": s.Type().String(), 53 | "now": s.Now(), 54 | "all": all, 55 | }) 56 | } 57 | 58 | func (s *Selector) Now() string { 59 | return s.selectedProxy().Name() 60 | } 61 | 62 | func (s *Selector) Set(name string) error { 63 | for _, proxy := range getProvidersProxies(s.providers) { 64 | if proxy.Name() == name { 65 | s.selected = name 66 | s.single.Reset() 67 | return nil 68 | } 69 | } 70 | 71 | return errors.New("Proxy does not exist") 72 | } 73 | 74 | func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { 75 | return s.selectedProxy() 76 | } 77 | 78 | func (s *Selector) selectedProxy() C.Proxy { 79 | elm, _, _ := s.single.Do(func() (interface{}, error) { 80 | proxies := getProvidersProxies(s.providers) 81 | for _, proxy := range proxies { 82 | if proxy.Name() == s.selected { 83 | return proxy, nil 84 | } 85 | } 86 | 87 | return proxies[0], nil 88 | }) 89 | 90 | return elm.(C.Proxy) 91 | } 92 | 93 | func NewSelector(name string, providers []provider.ProxyProvider) *Selector { 94 | selected := providers[0].Proxies()[0].Name() 95 | return &Selector{ 96 | Base: outbound.NewBase(name, "", C.Selector, false), 97 | single: singledo.NewSingle(defaultGetProxiesDuration), 98 | providers: providers, 99 | selected: selected, 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /adapters/outboundgroup/urltest.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/adapters/outbound" 9 | "github.com/Dreamacro/clash/adapters/provider" 10 | "github.com/Dreamacro/clash/common/singledo" 11 | C "github.com/Dreamacro/clash/constant" 12 | ) 13 | 14 | type urlTestOption func(*URLTest) 15 | 16 | func urlTestWithTolerance(tolerance uint16) urlTestOption { 17 | return func(u *URLTest) { 18 | u.tolerance = tolerance 19 | } 20 | } 21 | 22 | type URLTest struct { 23 | *outbound.Base 24 | tolerance uint16 25 | lastDelay uint16 26 | fastNode C.Proxy 27 | single *singledo.Single 28 | fastSingle *singledo.Single 29 | providers []provider.ProxyProvider 30 | } 31 | 32 | func (u *URLTest) GetProxyProviders() []provider.ProxyProvider { 33 | return u.providers 34 | } 35 | 36 | func (u *URLTest) Now() string { 37 | return u.fast().Name() 38 | } 39 | 40 | func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { 41 | c, err = u.fast().DialContext(ctx, metadata) 42 | if err == nil { 43 | c.AppendToChains(u) 44 | } 45 | return c, err 46 | } 47 | 48 | func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 49 | pc, err := u.fast().DialUDP(metadata) 50 | if err == nil { 51 | pc.AppendToChains(u) 52 | } 53 | return pc, err 54 | } 55 | 56 | func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { 57 | return u.fast() 58 | } 59 | 60 | func (u *URLTest) proxies() []C.Proxy { 61 | elm, _, _ := u.single.Do(func() (interface{}, error) { 62 | return getProvidersProxies(u.providers), nil 63 | }) 64 | 65 | return elm.([]C.Proxy) 66 | } 67 | 68 | func (u *URLTest) fast() C.Proxy { 69 | elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { 70 | // tolerance 71 | if u.tolerance != 0 && u.fastNode != nil { 72 | if u.fastNode.LastDelay() < u.lastDelay+u.tolerance { 73 | return u.fastNode, nil 74 | } 75 | } 76 | 77 | proxies := u.proxies() 78 | fast := proxies[0] 79 | min := fast.LastDelay() 80 | for _, proxy := range proxies[1:] { 81 | if !proxy.Alive() { 82 | continue 83 | } 84 | 85 | delay := proxy.LastDelay() 86 | if delay < min { 87 | fast = proxy 88 | min = delay 89 | } 90 | } 91 | 92 | u.fastNode = fast 93 | u.lastDelay = fast.LastDelay() 94 | return fast, nil 95 | }) 96 | 97 | return elm.(C.Proxy) 98 | } 99 | 100 | func (u *URLTest) SupportUDP() bool { 101 | return u.fast().SupportUDP() 102 | } 103 | 104 | func (u *URLTest) MarshalJSON() ([]byte, error) { 105 | var all []string 106 | for _, proxy := range u.proxies() { 107 | all = append(all, proxy.Name()) 108 | } 109 | return json.Marshal(map[string]interface{}{ 110 | "type": u.Type().String(), 111 | "now": u.Now(), 112 | "all": all, 113 | }) 114 | } 115 | 116 | func parseURLTestOption(config map[string]interface{}) []urlTestOption { 117 | opts := []urlTestOption{} 118 | 119 | // tolerance 120 | if elm, ok := config["tolerance"]; ok { 121 | if tolerance, ok := elm.(int); ok { 122 | opts = append(opts, urlTestWithTolerance(uint16(tolerance))) 123 | } 124 | } 125 | 126 | return opts 127 | } 128 | 129 | func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { 130 | urlTest := &URLTest{ 131 | Base: outbound.NewBase(name, "", C.URLTest, false), 132 | single: singledo.NewSingle(defaultGetProxiesDuration), 133 | fastSingle: singledo.NewSingle(time.Second * 10), 134 | providers: providers, 135 | } 136 | 137 | for _, option := range options { 138 | option(urlTest) 139 | } 140 | 141 | return urlTest 142 | } 143 | -------------------------------------------------------------------------------- /adapters/outboundgroup/util.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "fmt" 5 | C "github.com/Dreamacro/clash/constant" 6 | "net" 7 | ) 8 | 9 | func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { 10 | host, port, err := net.SplitHostPort(rawAddress) 11 | if err != nil { 12 | err = fmt.Errorf("addrToMetadata failed: %w", err) 13 | return 14 | } 15 | 16 | ip := net.ParseIP(host) 17 | if ip != nil { 18 | if ip.To4() != nil { 19 | addr = &C.Metadata{ 20 | AddrType: C.AtypIPv4, 21 | Host: "", 22 | DstIP: ip, 23 | DstPort: port, 24 | } 25 | return 26 | } else { 27 | addr = &C.Metadata{ 28 | AddrType: C.AtypIPv6, 29 | Host: "", 30 | DstIP: ip, 31 | DstPort: port, 32 | } 33 | return 34 | } 35 | } else { 36 | addr = &C.Metadata{ 37 | AddrType: C.AtypDomainName, 38 | Host: host, 39 | DstIP: nil, 40 | DstPort: port, 41 | } 42 | return 43 | } 44 | } 45 | 46 | func tcpKeepAlive(c net.Conn) { 47 | // Disable tcp keep alive to save battery 48 | // if tcp, ok := c.(*net.TCPConn); ok { 49 | // tcp.SetKeepAlive(true) 50 | // tcp.SetKeepAlivePeriod(30 * time.Second) 51 | // } 52 | } 53 | -------------------------------------------------------------------------------- /adapters/provider/healthcheck.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | const ( 11 | defaultURLTestTimeout = time.Second * 3 12 | defaultURLTestURL = "https://www.gstatic.com/generate_204" 13 | ) 14 | 15 | type HealthCheckOption struct { 16 | URL string 17 | Interval uint 18 | } 19 | 20 | type HealthCheck struct { 21 | url string 22 | proxies []C.Proxy 23 | interval uint 24 | done chan struct{} 25 | } 26 | 27 | func (hc *HealthCheck) process() { 28 | ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) 29 | 30 | go hc.check() 31 | for { 32 | select { 33 | case <-ticker.C: 34 | hc.check() 35 | case <-hc.done: 36 | ticker.Stop() 37 | return 38 | } 39 | } 40 | } 41 | 42 | func (hc *HealthCheck) setProxy(proxies []C.Proxy) { 43 | hc.proxies = proxies 44 | } 45 | 46 | func (hc *HealthCheck) auto() bool { 47 | return hc.interval != 0 48 | } 49 | 50 | func (hc *HealthCheck) check() { 51 | ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) 52 | for _, proxy := range hc.proxies { 53 | go proxy.URLTest(ctx, hc.url) 54 | } 55 | 56 | <-ctx.Done() 57 | cancel() 58 | } 59 | 60 | func (hc *HealthCheck) close() { 61 | hc.done <- struct{}{} 62 | } 63 | 64 | func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck { 65 | if url == "" { 66 | url = defaultURLTestURL 67 | } 68 | 69 | return &HealthCheck{ 70 | proxies: proxies, 71 | url: url, 72 | interval: interval, 73 | done: make(chan struct{}, 1), 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /adapters/provider/parser.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/common/structure" 9 | C "github.com/Dreamacro/clash/constant" 10 | ) 11 | 12 | var ( 13 | errVehicleType = errors.New("unsupport vehicle type") 14 | ) 15 | 16 | type healthCheckSchema struct { 17 | Enable bool `provider:"enable"` 18 | URL string `provider:"url"` 19 | Interval int `provider:"interval"` 20 | } 21 | 22 | type proxyProviderSchema struct { 23 | Type string `provider:"type"` 24 | Path string `provider:"path"` 25 | URL string `provider:"url,omitempty"` 26 | Interval int `provider:"interval,omitempty"` 27 | HealthCheck healthCheckSchema `provider:"health-check,omitempty"` 28 | } 29 | 30 | func ParseProxyProvider(name string, mapping map[string]interface{}, baseDir string) (ProxyProvider, error) { 31 | decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) 32 | 33 | schema := &proxyProviderSchema{} 34 | if err := decoder.Decode(mapping, schema); err != nil { 35 | return nil, err 36 | } 37 | 38 | var hcInterval uint = 0 39 | if schema.HealthCheck.Enable { 40 | hcInterval = uint(schema.HealthCheck.Interval) 41 | } 42 | hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval) 43 | 44 | path := baseDir + "/" + schema.Path 45 | 46 | var vehicle Vehicle 47 | switch schema.Type { 48 | case "file": 49 | vehicle = NewFileVehicle(path) 50 | case "http": 51 | vehicle = NewHTTPVehicle(schema.URL, path) 52 | default: 53 | return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) 54 | } 55 | 56 | interval := time.Duration(uint(schema.Interval)) * time.Second 57 | return NewProxySetProvider(name, interval, vehicle, hc), nil 58 | } 59 | -------------------------------------------------------------------------------- /adapters/provider/vehicle.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | // Vehicle Type 12 | const ( 13 | File VehicleType = iota 14 | HTTP 15 | Compatible 16 | ) 17 | 18 | // VehicleType defined 19 | type VehicleType int 20 | 21 | func (v VehicleType) String() string { 22 | switch v { 23 | case File: 24 | return "File" 25 | case HTTP: 26 | return "HTTP" 27 | case Compatible: 28 | return "Compatible" 29 | default: 30 | return "Unknown" 31 | } 32 | } 33 | 34 | type Vehicle interface { 35 | Read() ([]byte, error) 36 | Path() string 37 | Type() VehicleType 38 | } 39 | 40 | type FileVehicle struct { 41 | path string 42 | } 43 | 44 | func (f *FileVehicle) Type() VehicleType { 45 | return File 46 | } 47 | 48 | func (f *FileVehicle) Path() string { 49 | return f.path 50 | } 51 | 52 | func (f *FileVehicle) Read() ([]byte, error) { 53 | return ioutil.ReadFile(f.path) 54 | } 55 | 56 | func NewFileVehicle(path string) *FileVehicle { 57 | return &FileVehicle{path: path} 58 | } 59 | 60 | type HTTPVehicle struct { 61 | url string 62 | path string 63 | } 64 | 65 | func (h *HTTPVehicle) Type() VehicleType { 66 | return HTTP 67 | } 68 | 69 | func (h *HTTPVehicle) Path() string { 70 | return h.path 71 | } 72 | 73 | func (h *HTTPVehicle) Read() ([]byte, error) { 74 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 75 | defer cancel() 76 | 77 | uri, err := url.Parse(h.url) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | req, err := http.NewRequest(http.MethodGet, uri.String(), nil) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | if user := uri.User; user != nil { 88 | password, _ := user.Password() 89 | req.SetBasicAuth(user.Username(), password) 90 | } 91 | 92 | req = req.WithContext(ctx) 93 | 94 | transport := &http.Transport{ 95 | // from http.DefaultTransport 96 | MaxIdleConns: 100, 97 | IdleConnTimeout: 90 * time.Second, 98 | TLSHandshakeTimeout: 10 * time.Second, 99 | ExpectContinueTimeout: 1 * time.Second, 100 | } 101 | 102 | client := http.Client{Transport: transport} 103 | resp, err := client.Do(req) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | buf, err := ioutil.ReadAll(resp.Body) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | return buf, nil 114 | } 115 | 116 | func NewHTTPVehicle(url string, path string) *HTTPVehicle { 117 | return &HTTPVehicle{url, path} 118 | } 119 | -------------------------------------------------------------------------------- /common/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Cache store element with a expired time 10 | type Cache struct { 11 | *cache 12 | } 13 | 14 | type cache struct { 15 | mapping sync.Map 16 | janitor *janitor 17 | } 18 | 19 | type element struct { 20 | Expired time.Time 21 | Payload interface{} 22 | } 23 | 24 | // Put element in Cache with its ttl 25 | func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) { 26 | c.mapping.Store(key, &element{ 27 | Payload: payload, 28 | Expired: time.Now().Add(ttl), 29 | }) 30 | } 31 | 32 | // Get element in Cache, and drop when it expired 33 | func (c *cache) Get(key interface{}) interface{} { 34 | item, exist := c.mapping.Load(key) 35 | if !exist { 36 | return nil 37 | } 38 | elm := item.(*element) 39 | // expired 40 | if time.Since(elm.Expired) > 0 { 41 | c.mapping.Delete(key) 42 | return nil 43 | } 44 | return elm.Payload 45 | } 46 | 47 | // GetWithExpire element in Cache with Expire Time 48 | func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) { 49 | item, exist := c.mapping.Load(key) 50 | if !exist { 51 | return 52 | } 53 | elm := item.(*element) 54 | // expired 55 | if time.Since(elm.Expired) > 0 { 56 | c.mapping.Delete(key) 57 | return 58 | } 59 | return elm.Payload, elm.Expired 60 | } 61 | 62 | func (c *cache) cleanup() { 63 | c.mapping.Range(func(k, v interface{}) bool { 64 | key := k.(string) 65 | elm := v.(*element) 66 | if time.Since(elm.Expired) > 0 { 67 | c.mapping.Delete(key) 68 | } 69 | return true 70 | }) 71 | } 72 | 73 | type janitor struct { 74 | interval time.Duration 75 | stop chan struct{} 76 | } 77 | 78 | func (j *janitor) process(c *cache) { 79 | ticker := time.NewTicker(j.interval) 80 | for { 81 | select { 82 | case <-ticker.C: 83 | c.cleanup() 84 | case <-j.stop: 85 | ticker.Stop() 86 | return 87 | } 88 | } 89 | } 90 | 91 | func stopJanitor(c *Cache) { 92 | c.janitor.stop <- struct{}{} 93 | } 94 | 95 | // New return *Cache 96 | func New(interval time.Duration) *Cache { 97 | j := &janitor{ 98 | interval: interval, 99 | stop: make(chan struct{}), 100 | } 101 | c := &cache{janitor: j} 102 | go j.process(c) 103 | C := &Cache{c} 104 | runtime.SetFinalizer(C, stopJanitor) 105 | return C 106 | } 107 | -------------------------------------------------------------------------------- /common/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCache_Basic(t *testing.T) { 12 | interval := 200 * time.Millisecond 13 | ttl := 20 * time.Millisecond 14 | c := New(interval) 15 | c.Put("int", 1, ttl) 16 | c.Put("string", "a", ttl) 17 | 18 | i := c.Get("int") 19 | assert.Equal(t, i.(int), 1, "should recv 1") 20 | 21 | s := c.Get("string") 22 | assert.Equal(t, s.(string), "a", "should recv 'a'") 23 | } 24 | 25 | func TestCache_TTL(t *testing.T) { 26 | interval := 200 * time.Millisecond 27 | ttl := 20 * time.Millisecond 28 | now := time.Now() 29 | c := New(interval) 30 | c.Put("int", 1, ttl) 31 | c.Put("int2", 2, ttl) 32 | 33 | i := c.Get("int") 34 | _, expired := c.GetWithExpire("int2") 35 | assert.Equal(t, i.(int), 1, "should recv 1") 36 | assert.True(t, now.Before(expired)) 37 | 38 | time.Sleep(ttl * 2) 39 | i = c.Get("int") 40 | j, _ := c.GetWithExpire("int2") 41 | assert.Nil(t, i, "should recv nil") 42 | assert.Nil(t, j, "should recv nil") 43 | } 44 | 45 | func TestCache_AutoCleanup(t *testing.T) { 46 | interval := 10 * time.Millisecond 47 | ttl := 15 * time.Millisecond 48 | c := New(interval) 49 | c.Put("int", 1, ttl) 50 | 51 | time.Sleep(ttl * 2) 52 | i := c.Get("int") 53 | j, _ := c.GetWithExpire("int") 54 | assert.Nil(t, i, "should recv nil") 55 | assert.Nil(t, j, "should recv nil") 56 | } 57 | 58 | func TestCache_AutoGC(t *testing.T) { 59 | sign := make(chan struct{}) 60 | go func() { 61 | interval := 10 * time.Millisecond 62 | ttl := 15 * time.Millisecond 63 | c := New(interval) 64 | c.Put("int", 1, ttl) 65 | sign <- struct{}{} 66 | }() 67 | 68 | <-sign 69 | runtime.GC() 70 | } 71 | -------------------------------------------------------------------------------- /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/murmur3/murmur32.go: -------------------------------------------------------------------------------- 1 | package murmur3 2 | 3 | // https://github.com/spaolacci/murmur3/blob/master/murmur32.go 4 | 5 | import ( 6 | "hash" 7 | "math/bits" 8 | "unsafe" 9 | ) 10 | 11 | // Make sure interfaces are correctly implemented. 12 | var ( 13 | _ hash.Hash32 = new(digest32) 14 | _ bmixer = new(digest32) 15 | ) 16 | 17 | const ( 18 | c1_32 uint32 = 0xcc9e2d51 19 | c2_32 uint32 = 0x1b873593 20 | ) 21 | 22 | // digest32 represents a partial evaluation of a 32 bites hash. 23 | type digest32 struct { 24 | digest 25 | h1 uint32 // Unfinalized running hash. 26 | } 27 | 28 | // New32 returns new 32-bit hasher 29 | func New32() hash.Hash32 { return New32WithSeed(0) } 30 | 31 | // New32WithSeed returns new 32-bit hasher set with explicit seed value 32 | func New32WithSeed(seed uint32) hash.Hash32 { 33 | d := new(digest32) 34 | d.seed = seed 35 | d.bmixer = d 36 | d.Reset() 37 | return d 38 | } 39 | 40 | func (d *digest32) Size() int { return 4 } 41 | 42 | func (d *digest32) reset() { d.h1 = d.seed } 43 | 44 | func (d *digest32) Sum(b []byte) []byte { 45 | h := d.Sum32() 46 | return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h)) 47 | } 48 | 49 | // Digest as many blocks as possible. 50 | func (d *digest32) bmix(p []byte) (tail []byte) { 51 | h1 := d.h1 52 | 53 | nblocks := len(p) / 4 54 | for i := 0; i < nblocks; i++ { 55 | k1 := *(*uint32)(unsafe.Pointer(&p[i*4])) 56 | 57 | k1 *= c1_32 58 | k1 = bits.RotateLeft32(k1, 15) 59 | k1 *= c2_32 60 | 61 | h1 ^= k1 62 | h1 = bits.RotateLeft32(h1, 13) 63 | h1 = h1*4 + h1 + 0xe6546b64 64 | } 65 | d.h1 = h1 66 | return p[nblocks*d.Size():] 67 | } 68 | 69 | func (d *digest32) Sum32() (h1 uint32) { 70 | 71 | h1 = d.h1 72 | 73 | var k1 uint32 74 | switch len(d.tail) & 3 { 75 | case 3: 76 | k1 ^= uint32(d.tail[2]) << 16 77 | fallthrough 78 | case 2: 79 | k1 ^= uint32(d.tail[1]) << 8 80 | fallthrough 81 | case 1: 82 | k1 ^= uint32(d.tail[0]) 83 | k1 *= c1_32 84 | k1 = bits.RotateLeft32(k1, 15) 85 | k1 *= c2_32 86 | h1 ^= k1 87 | } 88 | 89 | h1 ^= uint32(d.clen) 90 | 91 | h1 ^= h1 >> 16 92 | h1 *= 0x85ebca6b 93 | h1 ^= h1 >> 13 94 | h1 *= 0xc2b2ae35 95 | h1 ^= h1 >> 16 96 | 97 | return h1 98 | } 99 | 100 | func Sum32(data []byte) uint32 { return Sum32WithSeed(data, 0) } 101 | 102 | func Sum32WithSeed(data []byte, seed uint32) uint32 { 103 | h1 := seed 104 | 105 | nblocks := len(data) / 4 106 | for i := 0; i < nblocks; i++ { 107 | k1 := *(*uint32)(unsafe.Pointer(&data[i*4])) 108 | 109 | k1 *= c1_32 110 | k1 = bits.RotateLeft32(k1, 15) 111 | k1 *= c2_32 112 | 113 | h1 ^= k1 114 | h1 = bits.RotateLeft32(h1, 13) 115 | h1 = h1*4 + h1 + 0xe6546b64 116 | } 117 | 118 | tail := data[nblocks*4:] 119 | 120 | var k1 uint32 121 | switch len(tail) & 3 { 122 | case 3: 123 | k1 ^= uint32(tail[2]) << 16 124 | fallthrough 125 | case 2: 126 | k1 ^= uint32(tail[1]) << 8 127 | fallthrough 128 | case 1: 129 | k1 ^= uint32(tail[0]) 130 | k1 *= c1_32 131 | k1 = bits.RotateLeft32(k1, 15) 132 | k1 *= c2_32 133 | h1 ^= k1 134 | } 135 | 136 | h1 ^= uint32(len(data)) 137 | 138 | h1 ^= h1 >> 16 139 | h1 *= 0x85ebca6b 140 | h1 ^= h1 >> 13 141 | h1 *= 0xc2b2ae35 142 | h1 ^= h1 >> 16 143 | 144 | return h1 145 | } 146 | -------------------------------------------------------------------------------- /common/observable/iterable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | type Iterable <-chan interface{} 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/observable_test.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func iterator(item []interface{}) chan interface{} { 13 | ch := make(chan interface{}) 14 | go func() { 15 | time.Sleep(100 * time.Millisecond) 16 | for _, elm := range item { 17 | ch <- elm 18 | } 19 | close(ch) 20 | }() 21 | return ch 22 | } 23 | 24 | func TestObservable(t *testing.T) { 25 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 26 | src := NewObservable(iter) 27 | data, err := src.Subscribe() 28 | assert.Nil(t, err) 29 | count := 0 30 | for range data { 31 | count++ 32 | } 33 | assert.Equal(t, count, 5) 34 | } 35 | 36 | func TestObservable_MutilSubscribe(t *testing.T) { 37 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 38 | src := NewObservable(iter) 39 | ch1, _ := src.Subscribe() 40 | ch2, _ := src.Subscribe() 41 | count := 0 42 | 43 | var wg sync.WaitGroup 44 | wg.Add(2) 45 | waitCh := func(ch <-chan interface{}) { 46 | for range ch { 47 | count++ 48 | } 49 | wg.Done() 50 | } 51 | go waitCh(ch1) 52 | go waitCh(ch2) 53 | wg.Wait() 54 | assert.Equal(t, count, 10) 55 | } 56 | 57 | func TestObservable_UnSubscribe(t *testing.T) { 58 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 59 | src := NewObservable(iter) 60 | data, err := src.Subscribe() 61 | assert.Nil(t, err) 62 | src.UnSubscribe(data) 63 | _, open := <-data 64 | assert.False(t, open) 65 | } 66 | 67 | func TestObservable_SubscribeClosedSource(t *testing.T) { 68 | iter := iterator([]interface{}{1}) 69 | src := NewObservable(iter) 70 | data, _ := src.Subscribe() 71 | <-data 72 | 73 | _, closed := src.Subscribe() 74 | assert.NotNil(t, closed) 75 | } 76 | 77 | func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { 78 | sub := Subscription(make(chan interface{})) 79 | iter := iterator([]interface{}{1}) 80 | src := NewObservable(iter) 81 | src.UnSubscribe(sub) 82 | } 83 | 84 | func TestObservable_SubscribeGoroutineLeak(t *testing.T) { 85 | // waiting for other goroutine recycle 86 | time.Sleep(120 * time.Millisecond) 87 | init := runtime.NumGoroutine() 88 | iter := iterator([]interface{}{1, 2, 3, 4, 5}) 89 | src := NewObservable(iter) 90 | max := 100 91 | 92 | var list []Subscription 93 | for i := 0; i < max; i++ { 94 | ch, _ := src.Subscribe() 95 | list = append(list, ch) 96 | } 97 | 98 | var wg sync.WaitGroup 99 | wg.Add(max) 100 | waitCh := func(ch <-chan interface{}) { 101 | for range ch { 102 | } 103 | wg.Done() 104 | } 105 | 106 | for _, ch := range list { 107 | go waitCh(ch) 108 | } 109 | wg.Wait() 110 | now := runtime.NumGoroutine() 111 | assert.Equal(t, init, now) 112 | } 113 | -------------------------------------------------------------------------------- /common/observable/subscriber.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "sync" 5 | 6 | "gopkg.in/eapache/channels.v1" 7 | ) 8 | 9 | type Subscription <-chan interface{} 10 | 11 | type Subscriber struct { 12 | buffer *channels.InfiniteChannel 13 | once sync.Once 14 | } 15 | 16 | func (s *Subscriber) Emit(item interface{}) { 17 | s.buffer.In() <- item 18 | } 19 | 20 | func (s *Subscriber) Out() Subscription { 21 | return s.buffer.Out() 22 | } 23 | 24 | func (s *Subscriber) Close() { 25 | s.once.Do(func() { 26 | s.buffer.Close() 27 | }) 28 | } 29 | 30 | func newSubscriber() *Subscriber { 31 | sub := &Subscriber{ 32 | buffer: channels.NewInfiniteChannel(), 33 | } 34 | return sub 35 | } 36 | -------------------------------------------------------------------------------- /common/picker/picker.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Picker provides synchronization, and Context cancelation 10 | // for groups of goroutines working on subtasks of a common task. 11 | // Inspired by errGroup 12 | type Picker struct { 13 | ctx context.Context 14 | cancel func() 15 | 16 | wg sync.WaitGroup 17 | 18 | once sync.Once 19 | errOnce sync.Once 20 | result interface{} 21 | err error 22 | } 23 | 24 | func newPicker(ctx context.Context, cancel func()) *Picker { 25 | return &Picker{ 26 | ctx: ctx, 27 | cancel: cancel, 28 | } 29 | } 30 | 31 | // WithContext returns a new Picker and an associated Context derived from ctx. 32 | // and cancel when first element return. 33 | func WithContext(ctx context.Context) (*Picker, context.Context) { 34 | ctx, cancel := context.WithCancel(ctx) 35 | return newPicker(ctx, cancel), ctx 36 | } 37 | 38 | // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. 39 | func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { 40 | ctx, cancel := context.WithTimeout(ctx, timeout) 41 | return newPicker(ctx, cancel), ctx 42 | } 43 | 44 | // Wait blocks until all function calls from the Go method have returned, 45 | // then returns the first nil error result (if any) from them. 46 | func (p *Picker) Wait() interface{} { 47 | p.wg.Wait() 48 | if p.cancel != nil { 49 | p.cancel() 50 | } 51 | return p.result 52 | } 53 | 54 | // Error return the first error (if all success return nil) 55 | func (p *Picker) Error() error { 56 | return p.err 57 | } 58 | 59 | // Go calls the given function in a new goroutine. 60 | // The first call to return a nil error cancels the group; its result will be returned by Wait. 61 | func (p *Picker) Go(f func() (interface{}, error)) { 62 | p.wg.Add(1) 63 | 64 | go func() { 65 | defer p.wg.Done() 66 | 67 | if ret, err := f(); err == nil { 68 | p.once.Do(func() { 69 | p.result = ret 70 | if p.cancel != nil { 71 | p.cancel() 72 | } 73 | }) 74 | } else { 75 | p.errOnce.Do(func() { 76 | p.err = err 77 | }) 78 | } 79 | }() 80 | } 81 | -------------------------------------------------------------------------------- /common/picker/picker_test.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func sleepAndSend(ctx context.Context, delay int, input interface{}) func() (interface{}, error) { 12 | return func() (interface{}, 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 *Allocator 12 | 13 | func init() { 14 | defaultAllocator = NewAllocator() 15 | } 16 | 17 | // Allocator for incoming frames, optimized to prevent overwriting after zeroing 18 | type Allocator struct { 19 | buffers []sync.Pool 20 | } 21 | 22 | // NewAllocator initiates a []byte allocator for frames less than 65536 bytes, 23 | // the waste(memory fragmentation) of space allocation is guaranteed to be 24 | // no more than 50%. 25 | func NewAllocator() *Allocator { 26 | alloc := new(Allocator) 27 | alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K 28 | for k := range alloc.buffers { 29 | i := k 30 | alloc.buffers[k].New = func() interface{} { 31 | return make([]byte, 1< 65536 { 40 | return nil 41 | } 42 | 43 | bits := msb(size) 44 | if size == 1< 65536 || cap(buf) != 1< host on head of linked list 31 | n := ipToUint(ip.To4()) 32 | offset := n - p.min + 1 33 | p.cache.Get(offset) 34 | return ip 35 | } 36 | 37 | ip := p.get(host) 38 | p.cache.Set(host, ip) 39 | return ip 40 | } 41 | 42 | // LookBack return host with the fake ip 43 | func (p *Pool) LookBack(ip net.IP) (string, bool) { 44 | p.mux.Lock() 45 | defer p.mux.Unlock() 46 | 47 | if ip = ip.To4(); ip == nil { 48 | return "", false 49 | } 50 | 51 | n := ipToUint(ip.To4()) 52 | offset := n - p.min + 1 53 | 54 | if elm, exist := p.cache.Get(offset); exist { 55 | host := elm.(string) 56 | 57 | // ensure host --> ip on head of linked list 58 | p.cache.Get(host) 59 | return host, true 60 | } 61 | 62 | return "", false 63 | } 64 | 65 | // LookupHost return if domain in host 66 | func (p *Pool) LookupHost(domain string) bool { 67 | if p.host == nil { 68 | return false 69 | } 70 | return p.host.Search(domain) != nil 71 | } 72 | 73 | // Exist returns if given ip exists in fake-ip pool 74 | func (p *Pool) Exist(ip net.IP) bool { 75 | p.mux.Lock() 76 | defer p.mux.Unlock() 77 | 78 | if ip = ip.To4(); ip == nil { 79 | return false 80 | } 81 | 82 | n := ipToUint(ip.To4()) 83 | offset := n - p.min + 1 84 | return p.cache.Exist(offset) 85 | } 86 | 87 | // Gateway return gateway ip 88 | func (p *Pool) Gateway() net.IP { 89 | return uintToIP(p.gateway) 90 | } 91 | 92 | // OverrideHostFrom override p host by r.host 93 | func (p *Pool) OverrideHostFrom(r *Pool) { 94 | p.host = r.host 95 | } 96 | 97 | func (p *Pool) get(host string) net.IP { 98 | current := p.offset 99 | for { 100 | p.offset = (p.offset + 1) % (p.max - p.min) 101 | // Avoid infinite loops 102 | if p.offset == current { 103 | break 104 | } 105 | 106 | if !p.cache.Exist(p.offset) { 107 | break 108 | } 109 | } 110 | ip := uintToIP(p.min + p.offset - 1) 111 | p.cache.Set(p.offset, host) 112 | return ip 113 | } 114 | 115 | func ipToUint(ip net.IP) uint32 { 116 | v := uint32(ip[0]) << 24 117 | v += uint32(ip[1]) << 16 118 | v += uint32(ip[2]) << 8 119 | v += uint32(ip[3]) 120 | return v 121 | } 122 | 123 | func uintToIP(v uint32) net.IP { 124 | return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 125 | } 126 | 127 | // New return Pool instance 128 | func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { 129 | min := ipToUint(ipnet.IP) + 2 130 | 131 | ones, bits := ipnet.Mask.Size() 132 | total := 1< 0 && isIPv4: 63 | // ipv4 64 | srcIP = net.IP(buf[inp+76 : inp+80]) 65 | case flag&0x2 > 0 && !isIPv4: 66 | // ipv6 67 | srcIP = net.IP(buf[inp+64 : inp+80]) 68 | default: 69 | continue 70 | } 71 | 72 | if !ip.Equal(srcIP) { 73 | continue 74 | } 75 | 76 | // xsocket_n.so_last_pid 77 | pid := readNativeUint32(buf[so+68 : so+72]) 78 | return getExecPathFromPID(pid) 79 | } 80 | 81 | return "", ErrNotFound 82 | } 83 | 84 | func getExecPathFromPID(pid uint32) (string, error) { 85 | buf := make([]byte, procpidpathinfosize) 86 | _, _, errno := syscall.Syscall6( 87 | syscall.SYS_PROC_INFO, 88 | proccallnumpidinfo, 89 | uintptr(pid), 90 | procpidpathinfo, 91 | 0, 92 | uintptr(unsafe.Pointer(&buf[0])), 93 | procpidpathinfosize) 94 | if errno != 0 { 95 | return "", errno 96 | } 97 | firstZero := bytes.IndexByte(buf, 0) 98 | if firstZero <= 0 { 99 | return "", nil 100 | } 101 | 102 | return filepath.Base(string(buf[:firstZero])), nil 103 | } 104 | 105 | func readNativeUint32(b []byte) uint32 { 106 | return *(*uint32)(unsafe.Pointer(&b[0])) 107 | } 108 | -------------------------------------------------------------------------------- /component/process/process_other.go: -------------------------------------------------------------------------------- 1 | // +build !darwin,!linux,!windows 2 | // +build !freebsd !amd64 3 | 4 | package process 5 | 6 | import "net" 7 | 8 | func findProcessName(network string, ip net.IP, srcPort int) (string, error) { 9 | return "", ErrPlatformNotSupport 10 | } 11 | -------------------------------------------------------------------------------- /component/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/component/trie" 9 | ) 10 | 11 | var ( 12 | // DefaultResolver aim to resolve ip 13 | DefaultResolver Resolver 14 | 15 | // DefaultHosts aim to resolve hosts 16 | DefaultHosts = trie.New() 17 | ) 18 | 19 | var ( 20 | ErrIPNotFound = errors.New("couldn't find ip") 21 | ErrIPVersion = errors.New("ip version error") 22 | ) 23 | 24 | type Resolver interface { 25 | ResolveIP(host string) (ip net.IP, err error) 26 | ResolveIPv4(host string) (ip net.IP, err error) 27 | ResolveIPv6(host string) (ip net.IP, err error) 28 | } 29 | 30 | // ResolveIPv4 with a host, return ipv4 31 | func ResolveIPv4(host string) (net.IP, error) { 32 | if node := DefaultHosts.Search(host); node != nil { 33 | if ip := node.Data.(net.IP).To4(); ip != nil { 34 | return ip, nil 35 | } 36 | } 37 | 38 | ip := net.ParseIP(host) 39 | if ip != nil { 40 | if !strings.Contains(host, ":") { 41 | return ip, nil 42 | } 43 | return nil, ErrIPVersion 44 | } 45 | 46 | if DefaultResolver != nil { 47 | return DefaultResolver.ResolveIPv4(host) 48 | } 49 | 50 | ipAddrs, err := net.LookupIP(host) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | for _, ip := range ipAddrs { 56 | if ip4 := ip.To4(); ip4 != nil { 57 | return ip4, nil 58 | } 59 | } 60 | 61 | return nil, ErrIPNotFound 62 | } 63 | 64 | // ResolveIPv6 with a host, return ipv6 65 | func ResolveIPv6(host string) (net.IP, error) { 66 | if node := DefaultHosts.Search(host); node != nil { 67 | if ip := node.Data.(net.IP).To16(); ip != nil { 68 | return ip, nil 69 | } 70 | } 71 | 72 | ip := net.ParseIP(host) 73 | if ip != nil { 74 | if strings.Contains(host, ":") { 75 | return ip, nil 76 | } 77 | return nil, ErrIPVersion 78 | } 79 | 80 | if DefaultResolver != nil { 81 | return DefaultResolver.ResolveIPv6(host) 82 | } 83 | 84 | ipAddrs, err := net.LookupIP(host) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | for _, ip := range ipAddrs { 90 | if ip.To4() == nil { 91 | return ip, nil 92 | } 93 | } 94 | 95 | return nil, ErrIPNotFound 96 | } 97 | 98 | // ResolveIP with a host, return ip 99 | func ResolveIP(host string) (net.IP, error) { 100 | if node := DefaultHosts.Search(host); node != nil { 101 | return node.Data.(net.IP), nil 102 | } 103 | 104 | if DefaultResolver != nil { 105 | return DefaultResolver.ResolveIP(host) 106 | } 107 | 108 | ip := net.ParseIP(host) 109 | if ip != nil { 110 | return ip, nil 111 | } 112 | 113 | ipAddr, err := net.ResolveIPAddr("ip", host) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | return ipAddr.IP, nil 119 | } 120 | -------------------------------------------------------------------------------- /component/simple-obfs/http.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | 12 | "github.com/Dreamacro/clash/common/pool" 13 | ) 14 | 15 | // HTTPObfs is shadowsocks http simple-obfs implementation 16 | type HTTPObfs struct { 17 | net.Conn 18 | host string 19 | port string 20 | buf []byte 21 | offset int 22 | firstRequest bool 23 | firstResponse bool 24 | } 25 | 26 | func (ho *HTTPObfs) Read(b []byte) (int, error) { 27 | if ho.buf != nil { 28 | n := copy(b, ho.buf[ho.offset:]) 29 | ho.offset += n 30 | if ho.offset == len(ho.buf) { 31 | ho.buf = nil 32 | } 33 | return n, nil 34 | } 35 | 36 | if ho.firstResponse { 37 | buf := pool.Get(pool.RelayBufferSize) 38 | n, err := ho.Conn.Read(buf) 39 | if err != nil { 40 | pool.Put(buf) 41 | return 0, err 42 | } 43 | idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) 44 | if idx == -1 { 45 | pool.Put(buf) 46 | return 0, io.EOF 47 | } 48 | ho.firstResponse = false 49 | length := n - (idx + 4) 50 | n = copy(b, buf[idx+4:n]) 51 | if length > n { 52 | ho.buf = buf[:idx+4+length] 53 | ho.offset = idx + 4 + n 54 | } else { 55 | pool.Put(buf) 56 | } 57 | return n, nil 58 | } 59 | return ho.Conn.Read(b) 60 | } 61 | 62 | func (ho *HTTPObfs) Write(b []byte) (int, error) { 63 | if ho.firstRequest { 64 | randBytes := make([]byte, 16) 65 | rand.Read(randBytes) 66 | req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:])) 67 | req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2)) 68 | req.Header.Set("Upgrade", "websocket") 69 | req.Header.Set("Connection", "Upgrade") 70 | req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) 71 | req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) 72 | req.ContentLength = int64(len(b)) 73 | err := req.Write(ho.Conn) 74 | ho.firstRequest = false 75 | return len(b), err 76 | } 77 | 78 | return ho.Conn.Write(b) 79 | } 80 | 81 | // NewHTTPObfs return a HTTPObfs 82 | func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { 83 | return &HTTPObfs{ 84 | Conn: conn, 85 | firstRequest: true, 86 | firstResponse: true, 87 | host: host, 88 | port: port, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /component/snell/cipher.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "crypto/cipher" 5 | 6 | "golang.org/x/crypto/argon2" 7 | ) 8 | 9 | type snellCipher struct { 10 | psk []byte 11 | makeAEAD func(key []byte) (cipher.AEAD, error) 12 | } 13 | 14 | func (sc *snellCipher) KeySize() int { return 32 } 15 | func (sc *snellCipher) SaltSize() int { return 16 } 16 | func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { 17 | return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) 18 | } 19 | func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { 20 | return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) 21 | } 22 | -------------------------------------------------------------------------------- /component/snell/snell.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "net" 9 | "sync" 10 | 11 | "github.com/Dreamacro/go-shadowsocks2/shadowaead" 12 | "golang.org/x/crypto/chacha20poly1305" 13 | ) 14 | 15 | const ( 16 | CommandPing byte = 0 17 | CommandConnect byte = 1 18 | 19 | CommandTunnel byte = 0 20 | CommandError byte = 2 21 | 22 | Version byte = 1 23 | ) 24 | 25 | var ( 26 | bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} 27 | ) 28 | 29 | type Snell struct { 30 | net.Conn 31 | buffer [1]byte 32 | reply bool 33 | } 34 | 35 | func (s *Snell) Read(b []byte) (int, error) { 36 | if s.reply { 37 | return s.Conn.Read(b) 38 | } 39 | 40 | s.reply = true 41 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { 42 | return 0, err 43 | } 44 | 45 | if s.buffer[0] == CommandTunnel { 46 | return s.Conn.Read(b) 47 | } else if s.buffer[0] != CommandError { 48 | return 0, errors.New("Command not support") 49 | } 50 | 51 | // CommandError 52 | if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { 53 | return 0, err 54 | } 55 | 56 | length := int(s.buffer[0]) 57 | msg := make([]byte, length) 58 | 59 | if _, err := io.ReadFull(s.Conn, msg); err != nil { 60 | return 0, err 61 | } 62 | 63 | return 0, errors.New(string(msg)) 64 | } 65 | 66 | func WriteHeader(conn net.Conn, host string, port uint) error { 67 | buf := bufferPool.Get().(*bytes.Buffer) 68 | buf.Reset() 69 | defer bufferPool.Put(buf) 70 | buf.WriteByte(Version) 71 | buf.WriteByte(CommandConnect) 72 | 73 | // clientID length & id 74 | buf.WriteByte(0) 75 | 76 | // host & port 77 | buf.WriteByte(uint8(len(host))) 78 | buf.WriteString(host) 79 | binary.Write(buf, binary.BigEndian, uint16(port)) 80 | 81 | if _, err := conn.Write(buf.Bytes()); err != nil { 82 | return err 83 | } 84 | 85 | return nil 86 | } 87 | 88 | func StreamConn(conn net.Conn, psk []byte) net.Conn { 89 | cipher := &snellCipher{psk, chacha20poly1305.New} 90 | return &Snell{Conn: shadowaead.NewConn(conn, cipher)} 91 | } 92 | -------------------------------------------------------------------------------- /component/trie/domain.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | wildcard = "*" 10 | dotWildcard = "" 11 | complexWildcard = "+" 12 | domainStep = "." 13 | ) 14 | 15 | var ( 16 | // ErrInvalidDomain means insert domain is invalid 17 | ErrInvalidDomain = errors.New("invalid domain") 18 | ) 19 | 20 | // DomainTrie contains the main logic for adding and searching nodes for domain segments. 21 | // support wildcard domain (e.g *.google.com) 22 | type DomainTrie struct { 23 | root *Node 24 | } 25 | 26 | func validAndSplitDomain(domain string) ([]string, bool) { 27 | if domain != "" && domain[len(domain)-1] == '.' { 28 | return nil, false 29 | } 30 | 31 | parts := strings.Split(domain, domainStep) 32 | if len(parts) == 1 { 33 | if parts[0] == "" { 34 | return nil, false 35 | } 36 | 37 | return parts, true 38 | } 39 | 40 | for _, part := range parts[1:] { 41 | if part == "" { 42 | return nil, false 43 | } 44 | } 45 | 46 | return parts, true 47 | } 48 | 49 | // Insert adds a node to the trie. 50 | // Support 51 | // 1. www.example.com 52 | // 2. *.example.com 53 | // 3. subdomain.*.example.com 54 | // 4. .example.com 55 | // 5. +.example.com 56 | func (t *DomainTrie) Insert(domain string, data interface{}) error { 57 | parts, valid := validAndSplitDomain(domain) 58 | if !valid { 59 | return ErrInvalidDomain 60 | } 61 | 62 | if parts[0] == complexWildcard { 63 | t.insert(parts[1:], data) 64 | parts[0] = dotWildcard 65 | t.insert(parts, data) 66 | } else { 67 | t.insert(parts, data) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func (t *DomainTrie) insert(parts []string, data interface{}) { 74 | node := t.root 75 | // reverse storage domain part to save space 76 | for i := len(parts) - 1; i >= 0; i-- { 77 | part := parts[i] 78 | if !node.hasChild(part) { 79 | node.addChild(part, newNode(nil)) 80 | } 81 | 82 | node = node.getChild(part) 83 | } 84 | 85 | node.Data = data 86 | } 87 | 88 | // Search is the most important part of the Trie. 89 | // Priority as: 90 | // 1. static part 91 | // 2. wildcard domain 92 | // 2. dot wildcard domain 93 | func (t *DomainTrie) Search(domain string) *Node { 94 | parts, valid := validAndSplitDomain(domain) 95 | if !valid || parts[0] == "" { 96 | return nil 97 | } 98 | 99 | n := t.root 100 | var dotWildcardNode *Node 101 | var wildcardNode *Node 102 | for i := len(parts) - 1; i >= 0; i-- { 103 | part := parts[i] 104 | 105 | if node := n.getChild(dotWildcard); node != nil { 106 | dotWildcardNode = node 107 | } 108 | 109 | child := n.getChild(part) 110 | if child == nil && wildcardNode != nil { 111 | child = wildcardNode.getChild(part) 112 | } 113 | wildcardNode = n.getChild(wildcard) 114 | 115 | n = child 116 | if n == nil { 117 | n = wildcardNode 118 | wildcardNode = nil 119 | } 120 | 121 | if n == nil { 122 | break 123 | } 124 | } 125 | 126 | if n == nil { 127 | if dotWildcardNode != nil { 128 | return dotWildcardNode 129 | } 130 | return nil 131 | } 132 | 133 | if n.Data == nil { 134 | return nil 135 | } 136 | 137 | return n 138 | } 139 | 140 | // New returns a new, empty Trie. 141 | func New() *DomainTrie { 142 | return &DomainTrie{root: newNode(nil)} 143 | } 144 | -------------------------------------------------------------------------------- /component/trie/domain_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var localIP = net.IP{127, 0, 0, 1} 11 | 12 | func TestTrie_Basic(t *testing.T) { 13 | tree := New() 14 | domains := []string{ 15 | "example.com", 16 | "google.com", 17 | "localhost", 18 | } 19 | 20 | for _, domain := range domains { 21 | tree.Insert(domain, localIP) 22 | } 23 | 24 | node := tree.Search("example.com") 25 | assert.NotNil(t, node) 26 | assert.True(t, node.Data.(net.IP).Equal(localIP)) 27 | assert.NotNil(t, tree.Insert("", localIP)) 28 | assert.Nil(t, tree.Search("")) 29 | assert.NotNil(t, tree.Search("localhost")) 30 | } 31 | 32 | func TestTrie_Wildcard(t *testing.T) { 33 | tree := New() 34 | domains := []string{ 35 | "*.example.com", 36 | "sub.*.example.com", 37 | "*.dev", 38 | ".org", 39 | ".example.net", 40 | ".apple.*", 41 | "+.foo.com", 42 | } 43 | 44 | for _, domain := range domains { 45 | tree.Insert(domain, localIP) 46 | } 47 | 48 | assert.NotNil(t, tree.Search("sub.example.com")) 49 | assert.NotNil(t, tree.Search("sub.foo.example.com")) 50 | assert.NotNil(t, tree.Search("test.org")) 51 | assert.NotNil(t, tree.Search("test.example.net")) 52 | assert.NotNil(t, tree.Search("test.apple.com")) 53 | assert.NotNil(t, tree.Search("test.foo.com")) 54 | assert.NotNil(t, tree.Search("foo.com")) 55 | assert.Nil(t, tree.Search("foo.sub.example.com")) 56 | assert.Nil(t, tree.Search("foo.example.dev")) 57 | assert.Nil(t, tree.Search("example.com")) 58 | } 59 | 60 | func TestTrie_Priority(t *testing.T) { 61 | tree := New() 62 | domains := []string{ 63 | ".dev", 64 | "example.dev", 65 | "*.example.dev", 66 | "test.example.dev", 67 | } 68 | 69 | assertFn := func(domain string, data int) { 70 | node := tree.Search(domain) 71 | assert.NotNil(t, node) 72 | assert.Equal(t, data, node.Data) 73 | } 74 | 75 | for idx, domain := range domains { 76 | tree.Insert(domain, idx) 77 | } 78 | 79 | assertFn("test.dev", 0) 80 | assertFn("foo.bar.dev", 0) 81 | assertFn("example.dev", 1) 82 | assertFn("foo.example.dev", 2) 83 | assertFn("test.example.dev", 3) 84 | } 85 | 86 | func TestTrie_Boundary(t *testing.T) { 87 | tree := New() 88 | tree.Insert("*.dev", localIP) 89 | 90 | assert.NotNil(t, tree.Insert(".", localIP)) 91 | assert.NotNil(t, tree.Insert("..dev", localIP)) 92 | assert.Nil(t, tree.Search("dev")) 93 | } 94 | -------------------------------------------------------------------------------- /component/trie/node.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | // Node is the trie's node 4 | type Node struct { 5 | Data interface{} 6 | children map[string]*Node 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 interface{}) *Node { 22 | return &Node{ 23 | Data: data, 24 | children: map[string]*Node{}, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /component/v2ray-plugin/websocket.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/Dreamacro/clash/component/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 | SessionCache tls.ClientSessionCache 20 | Mux bool 21 | } 22 | 23 | // NewV2rayObfs return a HTTPObfs 24 | func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { 25 | header := http.Header{} 26 | for k, v := range option.Headers { 27 | header.Add(k, v) 28 | } 29 | 30 | config := &vmess.WebsocketConfig{ 31 | Host: option.Host, 32 | Port: option.Port, 33 | Path: option.Path, 34 | TLS: option.TLS, 35 | Headers: header, 36 | SkipCertVerify: option.SkipCertVerify, 37 | SessionCache: option.SessionCache, 38 | } 39 | 40 | var err error 41 | conn, err = vmess.StreamWebsocketConn(conn, config) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | if option.Mux { 47 | conn = NewMux(conn, MuxOption{ 48 | ID: [2]byte{0, 0}, 49 | Host: "127.0.0.1", 50 | Port: 0, 51 | }) 52 | } 53 | return conn, nil 54 | } 55 | -------------------------------------------------------------------------------- /component/vmess/aead.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/cipher" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | 9 | "github.com/Dreamacro/clash/common/pool" 10 | ) 11 | 12 | type aeadWriter struct { 13 | io.Writer 14 | cipher.AEAD 15 | nonce [32]byte 16 | count uint16 17 | iv []byte 18 | } 19 | 20 | func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter { 21 | return &aeadWriter{Writer: w, AEAD: aead, iv: iv} 22 | } 23 | 24 | func (w *aeadWriter) Write(b []byte) (n int, err error) { 25 | buf := pool.Get(pool.RelayBufferSize) 26 | defer pool.Put(buf) 27 | length := len(b) 28 | for { 29 | if length == 0 { 30 | break 31 | } 32 | readLen := chunkSize - w.Overhead() 33 | if length < readLen { 34 | readLen = length 35 | } 36 | payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()] 37 | copy(payloadBuf, b[n:n+readLen]) 38 | 39 | binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen+w.Overhead())) 40 | binary.BigEndian.PutUint16(w.nonce[:2], w.count) 41 | copy(w.nonce[2:], w.iv[2:12]) 42 | 43 | w.Seal(payloadBuf[:0], w.nonce[:w.NonceSize()], payloadBuf[:readLen], nil) 44 | w.count++ 45 | 46 | _, err = w.Writer.Write(buf[:lenSize+readLen+w.Overhead()]) 47 | if err != nil { 48 | break 49 | } 50 | n += readLen 51 | length -= readLen 52 | } 53 | return 54 | } 55 | 56 | type aeadReader struct { 57 | io.Reader 58 | cipher.AEAD 59 | nonce [32]byte 60 | buf []byte 61 | offset int 62 | iv []byte 63 | sizeBuf []byte 64 | count uint16 65 | } 66 | 67 | func newAEADReader(r io.Reader, aead cipher.AEAD, iv []byte) *aeadReader { 68 | return &aeadReader{Reader: r, AEAD: aead, iv: iv, sizeBuf: make([]byte, lenSize)} 69 | } 70 | 71 | func (r *aeadReader) Read(b []byte) (int, error) { 72 | if r.buf != nil { 73 | n := copy(b, r.buf[r.offset:]) 74 | r.offset += n 75 | if r.offset == len(r.buf) { 76 | pool.Put(r.buf) 77 | r.buf = nil 78 | } 79 | return n, nil 80 | } 81 | 82 | _, err := io.ReadFull(r.Reader, r.sizeBuf) 83 | if err != nil { 84 | return 0, err 85 | } 86 | 87 | size := int(binary.BigEndian.Uint16(r.sizeBuf)) 88 | if size > maxSize { 89 | return 0, errors.New("Buffer is larger than standard") 90 | } 91 | 92 | buf := pool.Get(size) 93 | _, err = io.ReadFull(r.Reader, buf[:size]) 94 | if err != nil { 95 | pool.Put(buf) 96 | return 0, err 97 | } 98 | 99 | binary.BigEndian.PutUint16(r.nonce[:2], r.count) 100 | copy(r.nonce[2:], r.iv[2:12]) 101 | 102 | _, err = r.Open(buf[:0], r.nonce[:r.NonceSize()], buf[:size], nil) 103 | r.count++ 104 | if err != nil { 105 | return 0, err 106 | } 107 | realLen := size - r.Overhead() 108 | n := copy(b, buf[:realLen]) 109 | if len(b) >= realLen { 110 | pool.Put(buf) 111 | return n, nil 112 | } 113 | 114 | r.offset = n 115 | r.buf = buf[:realLen] 116 | return n, nil 117 | } 118 | -------------------------------------------------------------------------------- /component/vmess/chunk.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | 8 | "github.com/Dreamacro/clash/common/pool" 9 | ) 10 | 11 | const ( 12 | lenSize = 2 13 | chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 14 | maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() 15 | ) 16 | 17 | type chunkReader struct { 18 | io.Reader 19 | buf []byte 20 | sizeBuf []byte 21 | offset int 22 | } 23 | 24 | func newChunkReader(reader io.Reader) *chunkReader { 25 | return &chunkReader{Reader: reader, sizeBuf: make([]byte, lenSize)} 26 | } 27 | 28 | func newChunkWriter(writer io.WriteCloser) *chunkWriter { 29 | return &chunkWriter{Writer: writer} 30 | } 31 | 32 | func (cr *chunkReader) Read(b []byte) (int, error) { 33 | if cr.buf != nil { 34 | n := copy(b, cr.buf[cr.offset:]) 35 | cr.offset += n 36 | if cr.offset == len(cr.buf) { 37 | pool.Put(cr.buf) 38 | cr.buf = nil 39 | } 40 | return n, nil 41 | } 42 | 43 | _, err := io.ReadFull(cr.Reader, cr.sizeBuf) 44 | if err != nil { 45 | return 0, err 46 | } 47 | 48 | size := int(binary.BigEndian.Uint16(cr.sizeBuf)) 49 | if size > maxSize { 50 | return 0, errors.New("Buffer is larger than standard") 51 | } 52 | 53 | if len(b) >= size { 54 | _, err := io.ReadFull(cr.Reader, b[:size]) 55 | if err != nil { 56 | return 0, err 57 | } 58 | 59 | return size, nil 60 | } 61 | 62 | buf := pool.Get(size) 63 | _, err = io.ReadFull(cr.Reader, buf) 64 | if err != nil { 65 | pool.Put(buf) 66 | return 0, err 67 | } 68 | n := copy(b, buf) 69 | cr.offset = n 70 | cr.buf = buf 71 | return n, nil 72 | } 73 | 74 | type chunkWriter struct { 75 | io.Writer 76 | } 77 | 78 | func (cw *chunkWriter) Write(b []byte) (n int, err error) { 79 | buf := pool.Get(pool.RelayBufferSize) 80 | defer pool.Put(buf) 81 | length := len(b) 82 | for { 83 | if length == 0 { 84 | break 85 | } 86 | readLen := chunkSize 87 | if length < chunkSize { 88 | readLen = length 89 | } 90 | payloadBuf := buf[lenSize : lenSize+chunkSize] 91 | copy(payloadBuf, b[n:n+readLen]) 92 | 93 | binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen)) 94 | _, err = cw.Writer.Write(buf[:lenSize+readLen]) 95 | if err != nil { 96 | break 97 | } 98 | n += readLen 99 | length -= readLen 100 | } 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /component/vmess/http.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "net/http" 9 | "net/textproto" 10 | ) 11 | 12 | type httpConn struct { 13 | net.Conn 14 | cfg *HTTPConfig 15 | rhandshake bool 16 | whandshake bool 17 | } 18 | 19 | type HTTPConfig struct { 20 | Method string 21 | Host string 22 | Path []string 23 | Headers map[string][]string 24 | } 25 | 26 | // Read implements net.Conn.Read() 27 | func (hc *httpConn) Read(b []byte) (int, error) { 28 | if hc.rhandshake { 29 | n, err := hc.Conn.Read(b) 30 | return n, err 31 | } 32 | 33 | reader := textproto.NewConn(hc.Conn) 34 | // First line: GET /index.html HTTP/1.0 35 | if _, err := reader.ReadLine(); err != nil { 36 | return 0, err 37 | } 38 | 39 | if _, err := reader.ReadMIMEHeader(); err != nil { 40 | return 0, err 41 | } 42 | 43 | hc.rhandshake = true 44 | return hc.Conn.Read(b) 45 | } 46 | 47 | // Write implements io.Writer. 48 | func (hc *httpConn) Write(b []byte) (int, error) { 49 | if hc.whandshake { 50 | return hc.Conn.Write(b) 51 | } 52 | 53 | path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] 54 | u := fmt.Sprintf("http://%s%s", hc.cfg.Host, path) 55 | req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b)) 56 | for key, list := range hc.cfg.Headers { 57 | req.Header.Set(key, list[rand.Intn(len(list))]) 58 | } 59 | req.ContentLength = int64(len(b)) 60 | if err := req.Write(hc.Conn); err != nil { 61 | return 0, err 62 | } 63 | hc.whandshake = true 64 | return len(b), nil 65 | } 66 | 67 | func (hc *httpConn) Close() error { 68 | return hc.Conn.Close() 69 | } 70 | 71 | func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn { 72 | return &httpConn{ 73 | Conn: conn, 74 | cfg: cfg, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /component/vmess/tls.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | ) 7 | 8 | type TLSConfig struct { 9 | Host string 10 | SkipCertVerify bool 11 | SessionCache tls.ClientSessionCache 12 | } 13 | 14 | func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { 15 | tlsConfig := &tls.Config{ 16 | ServerName: cfg.Host, 17 | InsecureSkipVerify: cfg.SkipCertVerify, 18 | ClientSessionCache: cfg.SessionCache, 19 | } 20 | 21 | tlsConn := tls.Client(conn, tlsConfig) 22 | err := tlsConn.Handshake() 23 | return tlsConn, err 24 | } 25 | -------------------------------------------------------------------------------- /component/vmess/user.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | 7 | "github.com/gofrs/uuid" 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 | -------------------------------------------------------------------------------- /component/vmess/vmess.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "runtime" 9 | "sync" 10 | 11 | "github.com/gofrs/uuid" 12 | ) 13 | 14 | // Version of vmess 15 | const Version byte = 1 16 | 17 | // Request Options 18 | const ( 19 | OptionChunkStream byte = 1 20 | OptionChunkMasking byte = 4 21 | ) 22 | 23 | // Security type vmess 24 | type Security = byte 25 | 26 | // Cipher types 27 | const ( 28 | SecurityAES128GCM Security = 3 29 | SecurityCHACHA20POLY1305 Security = 4 30 | SecurityNone Security = 5 31 | ) 32 | 33 | // CipherMapping return 34 | var CipherMapping = map[string]byte{ 35 | "none": SecurityNone, 36 | "aes-128-gcm": SecurityAES128GCM, 37 | "chacha20-poly1305": SecurityCHACHA20POLY1305, 38 | } 39 | 40 | var ( 41 | clientSessionCache tls.ClientSessionCache 42 | once sync.Once 43 | ) 44 | 45 | // Command types 46 | const ( 47 | CommandTCP byte = 1 48 | CommandUDP byte = 2 49 | ) 50 | 51 | // Addr types 52 | const ( 53 | AtypIPv4 byte = 1 54 | AtypDomainName byte = 2 55 | AtypIPv6 byte = 3 56 | ) 57 | 58 | // DstAddr store destination address 59 | type DstAddr struct { 60 | UDP bool 61 | AddrType byte 62 | Addr []byte 63 | Port uint 64 | } 65 | 66 | // Client is vmess connection generator 67 | type Client struct { 68 | user []*ID 69 | uuid *uuid.UUID 70 | security Security 71 | } 72 | 73 | // Config of vmess 74 | type Config struct { 75 | UUID string 76 | AlterID uint16 77 | Security string 78 | Port string 79 | HostName string 80 | } 81 | 82 | // StreamConn return a Conn with net.Conn and DstAddr 83 | func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { 84 | r := rand.Intn(len(c.user)) 85 | return newConn(conn, c.user[r], dst, c.security) 86 | } 87 | 88 | // NewClient return Client instance 89 | func NewClient(config Config) (*Client, error) { 90 | uid, err := uuid.FromString(config.UUID) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | var security Security 96 | switch config.Security { 97 | case "aes-128-gcm": 98 | security = SecurityAES128GCM 99 | case "chacha20-poly1305": 100 | security = SecurityCHACHA20POLY1305 101 | case "none": 102 | security = SecurityNone 103 | case "auto": 104 | security = SecurityCHACHA20POLY1305 105 | if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { 106 | security = SecurityAES128GCM 107 | } 108 | default: 109 | return nil, fmt.Errorf("Unknown security type: %s", config.Security) 110 | } 111 | 112 | return &Client{ 113 | user: newAlterIDs(newID(&uid), config.AlterID), 114 | uuid: &uid, 115 | security: security, 116 | }, nil 117 | } 118 | -------------------------------------------------------------------------------- /config/initial.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/Dreamacro/clash/component/mmdb" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | ) 13 | 14 | func downloadMMDB(path string) (err error) { 15 | resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb") 16 | if err != nil { 17 | return 18 | } 19 | defer resp.Body.Close() 20 | 21 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) 22 | if err != nil { 23 | return err 24 | } 25 | defer f.Close() 26 | _, err = io.Copy(f, resp.Body) 27 | 28 | return err 29 | } 30 | 31 | func initMMDB() error { 32 | if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { 33 | log.Infoln("Can't find MMDB, start download") 34 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 35 | return fmt.Errorf("Can't download MMDB: %s", err.Error()) 36 | } 37 | } 38 | 39 | if !mmdb.Verify() { 40 | log.Warnln("MMDB invalid, remove and download") 41 | if err := os.Remove(C.Path.MMDB()); err != nil { 42 | return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error()) 43 | } 44 | 45 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 46 | return fmt.Errorf("Can't download MMDB: %s", err.Error()) 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // Init prepare necessary files 54 | func Init(dir string) error { 55 | // initial homedir 56 | if _, err := os.Stat(dir); os.IsNotExist(err) { 57 | if err := os.MkdirAll(dir, 0777); err != nil { 58 | return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error()) 59 | } 60 | } 61 | 62 | // initial config.yaml 63 | if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { 64 | log.Infoln("Can't find config, create a initial config file") 65 | f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) 66 | if err != nil { 67 | return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error()) 68 | } 69 | f.Write([]byte(`port: 7890`)) 70 | f.Close() 71 | } 72 | 73 | // initial mmdb 74 | if err := initMMDB(); err != nil { 75 | return fmt.Errorf("Can't initial MMDB: %w", err) 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /constant/adapters.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | // Adapter Type 11 | const ( 12 | Direct AdapterType = iota 13 | Reject 14 | 15 | Shadowsocks 16 | ShadowsocksR 17 | Snell 18 | Socks5 19 | Http 20 | Vmess 21 | Trojan 22 | 23 | Relay 24 | Selector 25 | Fallback 26 | URLTest 27 | LoadBalance 28 | ) 29 | 30 | type ServerAdapter interface { 31 | net.Conn 32 | Metadata() *Metadata 33 | } 34 | 35 | type Connection interface { 36 | Chains() Chain 37 | AppendToChains(adapter ProxyAdapter) 38 | } 39 | 40 | type Chain []string 41 | 42 | func (c Chain) String() string { 43 | switch len(c) { 44 | case 0: 45 | return "" 46 | case 1: 47 | return c[0] 48 | default: 49 | return fmt.Sprintf("%s[%s]", c[len(c)-1], c[0]) 50 | } 51 | } 52 | 53 | type Conn interface { 54 | net.Conn 55 | Connection 56 | } 57 | 58 | type PacketConn interface { 59 | net.PacketConn 60 | Connection 61 | // Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed 62 | // WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error) 63 | } 64 | 65 | type ProxyAdapter interface { 66 | Name() string 67 | Type() AdapterType 68 | StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error) 69 | DialContext(ctx context.Context, metadata *Metadata) (Conn, error) 70 | DialUDP(metadata *Metadata) (PacketConn, error) 71 | SupportUDP() bool 72 | MarshalJSON() ([]byte, error) 73 | Addr() string 74 | // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. 75 | Unwrap(metadata *Metadata) Proxy 76 | } 77 | 78 | type DelayHistory struct { 79 | Time time.Time `json:"time"` 80 | Delay uint16 `json:"delay"` 81 | } 82 | 83 | type Proxy interface { 84 | ProxyAdapter 85 | Alive() bool 86 | DelayHistory() []DelayHistory 87 | Dial(metadata *Metadata) (Conn, error) 88 | LastDelay() uint16 89 | URLTest(ctx context.Context, url string) (uint16, error) 90 | } 91 | 92 | // AdapterType is enum of adapter type 93 | type AdapterType int 94 | 95 | func (at AdapterType) String() string { 96 | switch at { 97 | case Direct: 98 | return "Direct" 99 | case Reject: 100 | return "Reject" 101 | 102 | case Shadowsocks: 103 | return "Shadowsocks" 104 | case ShadowsocksR: 105 | return "ShadowsocksR" 106 | case Snell: 107 | return "Snell" 108 | case Socks5: 109 | return "Socks5" 110 | case Http: 111 | return "Http" 112 | case Vmess: 113 | return "Vmess" 114 | case Trojan: 115 | return "Trojan" 116 | 117 | case Relay: 118 | return "Relay" 119 | case Selector: 120 | return "Selector" 121 | case Fallback: 122 | return "Fallback" 123 | case URLTest: 124 | return "URLTest" 125 | case LoadBalance: 126 | return "LoadBalance" 127 | 128 | default: 129 | return "Unknown" 130 | } 131 | } 132 | 133 | // UDPPacket contains the data of UDP packet, and offers control/info of UDP packet's source 134 | type UDPPacket interface { 135 | // Data get the payload of UDP Packet 136 | Data() []byte 137 | 138 | // WriteBack writes the payload with source IP/Port equals addr 139 | // - variable source IP/Port is important to STUN 140 | // - if addr is not provided, WriteBack will wirte out UDP packet with SourceIP/Prot equals to origional Target, 141 | // this is important when using Fake-IP. 142 | WriteBack(b []byte, addr net.Addr) (n int, err error) 143 | 144 | // Drop call after packet is used, could recycle buffer in this function. 145 | Drop() 146 | 147 | // LocalAddr returns the source IP/Port of packet 148 | LocalAddr() net.Addr 149 | } 150 | -------------------------------------------------------------------------------- /constant/metadata.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | // Socks addr type 10 | const ( 11 | AtypIPv4 = 1 12 | AtypDomainName = 3 13 | AtypIPv6 = 4 14 | 15 | TCP NetWork = iota 16 | UDP 17 | 18 | HTTP Type = iota 19 | HTTPCONNECT 20 | SOCKS 21 | REDIR 22 | ) 23 | 24 | type NetWork int 25 | 26 | func (n NetWork) String() string { 27 | if n == TCP { 28 | return "tcp" 29 | } 30 | return "udp" 31 | } 32 | 33 | func (n NetWork) MarshalJSON() ([]byte, error) { 34 | return json.Marshal(n.String()) 35 | } 36 | 37 | type Type int 38 | 39 | func (t Type) String() string { 40 | switch t { 41 | case HTTP: 42 | return "HTTP" 43 | case HTTPCONNECT: 44 | return "HTTP Connect" 45 | case SOCKS: 46 | return "Socks5" 47 | case REDIR: 48 | return "Redir" 49 | default: 50 | return "Unknown" 51 | } 52 | } 53 | 54 | func (t Type) MarshalJSON() ([]byte, error) { 55 | return json.Marshal(t.String()) 56 | } 57 | 58 | // Metadata is used to store connection address 59 | type Metadata struct { 60 | NetWork NetWork `json:"network"` 61 | Type Type `json:"type"` 62 | SrcIP net.IP `json:"sourceIP"` 63 | DstIP net.IP `json:"destinationIP"` 64 | SrcPort string `json:"sourcePort"` 65 | DstPort string `json:"destinationPort"` 66 | AddrType int `json:"-"` 67 | Host string `json:"host"` 68 | } 69 | 70 | func (m *Metadata) RemoteAddress() string { 71 | return net.JoinHostPort(m.String(), m.DstPort) 72 | } 73 | 74 | func (m *Metadata) SourceAddress() string { 75 | return net.JoinHostPort(m.SrcIP.String(), m.SrcPort) 76 | } 77 | 78 | func (m *Metadata) Resolved() bool { 79 | return m.DstIP != nil 80 | } 81 | 82 | func (m *Metadata) UDPAddr() *net.UDPAddr { 83 | if m.NetWork != UDP || m.DstIP == nil { 84 | return nil 85 | } 86 | port, _ := strconv.Atoi(m.DstPort) 87 | return &net.UDPAddr{ 88 | IP: m.DstIP, 89 | Port: port, 90 | } 91 | } 92 | 93 | func (m *Metadata) String() string { 94 | if m.Host != "" { 95 | return m.Host 96 | } else if m.DstIP != nil { 97 | return m.DstIP.String() 98 | } else { 99 | return "" 100 | } 101 | } 102 | 103 | func (m *Metadata) Valid() bool { 104 | return m.Host != "" || m.DstIP != nil 105 | } 106 | -------------------------------------------------------------------------------- /constant/path.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "os" 5 | P "path" 6 | "path/filepath" 7 | ) 8 | 9 | const Name = "clash" 10 | 11 | // Path is used to get the configuration path 12 | var Path *path 13 | 14 | type path struct { 15 | homeDir string 16 | configFile string 17 | } 18 | 19 | func init() { 20 | homeDir, err := os.UserHomeDir() 21 | if err != nil { 22 | homeDir, _ = os.Getwd() 23 | } 24 | 25 | homeDir = P.Join(homeDir, ".config", Name) 26 | Path = &path{homeDir: homeDir, configFile: "config.yaml"} 27 | } 28 | 29 | // SetHomeDir is used to set the configuration path 30 | func SetHomeDir(root string) { 31 | Path.homeDir = root 32 | } 33 | 34 | // SetConfig is used to set the configuration file 35 | func SetConfig(file string) { 36 | Path.configFile = file 37 | } 38 | 39 | func (p *path) HomeDir() string { 40 | return p.homeDir 41 | } 42 | 43 | func (p *path) Config() string { 44 | return p.configFile 45 | } 46 | 47 | // Resolve return a absolute path or a relative path with homedir 48 | func (p *path) Resolve(path string) string { 49 | if !filepath.IsAbs(path) { 50 | return filepath.Join(p.HomeDir(), path) 51 | } 52 | 53 | return path 54 | } 55 | 56 | func (p *path) MMDB() string { 57 | return P.Join(p.homeDir, "Country.mmdb") 58 | } 59 | -------------------------------------------------------------------------------- /constant/rule.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // Rule Type 4 | const ( 5 | Domain RuleType = iota 6 | DomainSuffix 7 | DomainKeyword 8 | GEOIP 9 | IPCIDR 10 | SrcIPCIDR 11 | SrcPort 12 | DstPort 13 | Process 14 | MATCH 15 | ) 16 | 17 | type RuleType int 18 | 19 | func (rt RuleType) String() string { 20 | switch rt { 21 | case Domain: 22 | return "Domain" 23 | case DomainSuffix: 24 | return "DomainSuffix" 25 | case DomainKeyword: 26 | return "DomainKeyword" 27 | case GEOIP: 28 | return "GeoIP" 29 | case IPCIDR: 30 | return "IPCIDR" 31 | case SrcIPCIDR: 32 | return "SrcIPCIDR" 33 | case SrcPort: 34 | return "SrcPort" 35 | case DstPort: 36 | return "DstPort" 37 | case Process: 38 | return "Process" 39 | case MATCH: 40 | return "Match" 41 | default: 42 | return "Unknown" 43 | } 44 | } 45 | 46 | type Rule interface { 47 | RuleType() RuleType 48 | Match(metadata *Metadata) bool 49 | Adapter() string 50 | Payload() string 51 | NoResolveIP() bool 52 | } 53 | -------------------------------------------------------------------------------- /constant/version.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var ( 4 | Version = "unknown version" 5 | BuildTime = "unknown time" 6 | ) 7 | -------------------------------------------------------------------------------- /dns/client.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | "github.com/Dreamacro/clash/component/dialer" 10 | 11 | D "github.com/miekg/dns" 12 | ) 13 | 14 | type client struct { 15 | *D.Client 16 | r *Resolver 17 | port string 18 | host string 19 | } 20 | 21 | func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { 22 | return c.ExchangeContext(context.Background(), m) 23 | } 24 | 25 | func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 26 | var ip net.IP 27 | if c.r == nil { 28 | // a default ip dns 29 | ip = net.ParseIP(c.host) 30 | } else { 31 | var err error 32 | if ip, err = c.r.ResolveIP(c.host); err != nil { 33 | return nil, fmt.Errorf("use default dns resolve failed: %w", err) 34 | } 35 | } 36 | 37 | d, err := dialer.Dialer() 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if dialer.DialHook != nil { 43 | network := "udp" 44 | if strings.HasPrefix(c.Client.Net, "tcp") { 45 | network = "tcp" 46 | } 47 | if err := dialer.DialHook(d, network, ip); err != nil { 48 | return nil, err 49 | } 50 | } 51 | 52 | c.Client.Dialer = d 53 | 54 | // miekg/dns ExchangeContext doesn't respond to context cancel. 55 | // this is a workaround 56 | type result struct { 57 | msg *D.Msg 58 | err error 59 | } 60 | ch := make(chan result, 1) 61 | go func() { 62 | msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) 63 | ch <- result{msg, err} 64 | }() 65 | 66 | select { 67 | case <-ctx.Done(): 68 | return nil, ctx.Err() 69 | case ret := <-ch: 70 | return ret.msg, ret.err 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /dns/doh.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | 11 | "github.com/Dreamacro/clash/component/dialer" 12 | 13 | D "github.com/miekg/dns" 14 | ) 15 | 16 | const ( 17 | // dotMimeType is the DoH mimetype that should be used. 18 | dotMimeType = "application/dns-message" 19 | ) 20 | 21 | type dohClient struct { 22 | url string 23 | transport *http.Transport 24 | } 25 | 26 | func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { 27 | return dc.ExchangeContext(context.Background(), m) 28 | } 29 | 30 | func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { 31 | req, err := dc.newRequest(m) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | req = req.WithContext(ctx) 37 | return dc.doRequest(req) 38 | } 39 | 40 | // newRequest returns a new DoH request given a dns.Msg. 41 | func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) { 42 | buf, err := m.Pack() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf)) 48 | if err != nil { 49 | return req, err 50 | } 51 | 52 | req.Header.Set("content-type", dotMimeType) 53 | req.Header.Set("accept", dotMimeType) 54 | return req, nil 55 | } 56 | 57 | func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { 58 | client := &http.Client{Transport: dc.transport} 59 | resp, err := client.Do(req) 60 | if err != nil { 61 | return nil, err 62 | } 63 | defer resp.Body.Close() 64 | 65 | buf, err := ioutil.ReadAll(resp.Body) 66 | if err != nil { 67 | return nil, err 68 | } 69 | msg = &D.Msg{} 70 | err = msg.Unpack(buf) 71 | return msg, err 72 | } 73 | 74 | func newDoHClient(url string, r *Resolver) *dohClient { 75 | return &dohClient{ 76 | url: url, 77 | transport: &http.Transport{ 78 | TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, 79 | ForceAttemptHTTP2: true, 80 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 81 | host, port, err := net.SplitHostPort(addr) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | ip, err := r.ResolveIPv4(host) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) 92 | }, 93 | }, 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /dns/filters.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/component/mmdb" 7 | ) 8 | 9 | type fallbackFilter interface { 10 | Match(net.IP) bool 11 | } 12 | 13 | type geoipFilter struct{} 14 | 15 | func (gf *geoipFilter) Match(ip net.IP) bool { 16 | record, _ := mmdb.Instance().Country(ip) 17 | return record.Country.IsoCode != "CN" && record.Country.IsoCode != "" 18 | } 19 | 20 | type ipnetFilter struct { 21 | ipnet *net.IPNet 22 | } 23 | 24 | func (inf *ipnetFilter) Match(ip net.IP) bool { 25 | return inf.ipnet.Contains(ip) 26 | } 27 | -------------------------------------------------------------------------------- /dns/middleware.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Dreamacro/clash/component/fakeip" 7 | "github.com/Dreamacro/clash/log" 8 | 9 | D "github.com/miekg/dns" 10 | ) 11 | 12 | type Handler func(w D.ResponseWriter, r *D.Msg) 13 | type middleware func(next Handler) Handler 14 | 15 | func withFakeIP(fakePool *fakeip.Pool) middleware { 16 | return func(next Handler) Handler { 17 | return func(w D.ResponseWriter, r *D.Msg) { 18 | q := r.Question[0] 19 | 20 | if q.Qtype == D.TypeAAAA { 21 | msg := &D.Msg{} 22 | msg.Answer = []D.RR{} 23 | 24 | msg.SetRcode(r, D.RcodeSuccess) 25 | msg.Authoritative = true 26 | msg.RecursionAvailable = true 27 | 28 | w.WriteMsg(msg) 29 | return 30 | } else if q.Qtype != D.TypeA { 31 | next(w, r) 32 | return 33 | } 34 | 35 | host := strings.TrimRight(q.Name, ".") 36 | if fakePool.LookupHost(host) { 37 | next(w, r) 38 | return 39 | } 40 | 41 | rr := &D.A{} 42 | rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} 43 | ip := fakePool.Lookup(host) 44 | rr.A = ip 45 | msg := r.Copy() 46 | msg.Answer = []D.RR{rr} 47 | 48 | setMsgTTL(msg, 1) 49 | msg.SetRcode(r, D.RcodeSuccess) 50 | msg.Authoritative = true 51 | msg.RecursionAvailable = true 52 | 53 | w.WriteMsg(msg) 54 | return 55 | } 56 | } 57 | } 58 | 59 | func withResolver(resolver *Resolver) Handler { 60 | return func(w D.ResponseWriter, r *D.Msg) { 61 | msg, err := resolver.Exchange(r) 62 | if err != nil { 63 | q := r.Question[0] 64 | log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err) 65 | D.HandleFailed(w, r) 66 | return 67 | } 68 | msg.SetRcode(r, msg.Rcode) 69 | msg.Authoritative = true 70 | w.WriteMsg(msg) 71 | return 72 | } 73 | } 74 | 75 | func compose(middlewares []middleware, endpoint Handler) Handler { 76 | length := len(middlewares) 77 | h := endpoint 78 | for i := length - 1; i >= 0; i-- { 79 | middleware := middlewares[i] 80 | h = middleware(h) 81 | } 82 | 83 | return h 84 | } 85 | 86 | func NewHandler(resolver *Resolver) Handler { 87 | middlewares := []middleware{} 88 | 89 | if resolver.FakeIPEnabled() { 90 | middlewares = append(middlewares, withFakeIP(resolver.pool)) 91 | } 92 | 93 | return compose(middlewares, withResolver(resolver)) 94 | } 95 | -------------------------------------------------------------------------------- /dns/server.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/sockopt" 7 | 8 | D "github.com/miekg/dns" 9 | ) 10 | 11 | var ( 12 | address string 13 | server = &Server{} 14 | 15 | dnsDefaultTTL uint32 = 600 16 | ) 17 | 18 | type Server struct { 19 | *D.Server 20 | handler Handler 21 | } 22 | 23 | func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { 24 | if len(r.Question) == 0 { 25 | D.HandleFailed(w, r) 26 | return 27 | } 28 | 29 | s.handler(w, r) 30 | } 31 | 32 | func (s *Server) setHandler(handler Handler) { 33 | s.handler = handler 34 | } 35 | 36 | func ReCreateServer(addr string, resolver *Resolver) error { 37 | if addr == address && resolver != nil { 38 | handler := NewHandler(resolver) 39 | server.setHandler(handler) 40 | return nil 41 | } 42 | 43 | if server.Server != nil { 44 | server.Shutdown() 45 | address = "" 46 | } 47 | 48 | _, port, err := net.SplitHostPort(addr) 49 | if port == "0" || port == "" || err != nil { 50 | return nil 51 | } 52 | 53 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | p, err := net.ListenUDP("udp", udpAddr) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | err = sockopt.UDPReuseaddr(p) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | address = addr 69 | handler := NewHandler(resolver) 70 | server = &Server{handler: handler} 71 | server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} 72 | 73 | go func() { 74 | server.ActivateAndServe() 75 | }() 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /dns/util.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "errors" 7 | "net" 8 | "time" 9 | 10 | "github.com/Dreamacro/clash/common/cache" 11 | "github.com/Dreamacro/clash/log" 12 | 13 | D "github.com/miekg/dns" 14 | ) 15 | 16 | var ( 17 | // EnhancedModeMapping is a mapping for EnhancedMode enum 18 | EnhancedModeMapping = map[string]EnhancedMode{ 19 | NORMAL.String(): NORMAL, 20 | FAKEIP.String(): FAKEIP, 21 | MAPPING.String(): MAPPING, 22 | } 23 | ) 24 | 25 | const ( 26 | NORMAL EnhancedMode = iota 27 | FAKEIP 28 | MAPPING 29 | ) 30 | 31 | type EnhancedMode int 32 | 33 | // UnmarshalYAML unserialize EnhancedMode with yaml 34 | func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error { 35 | var tp string 36 | if err := unmarshal(&tp); err != nil { 37 | return err 38 | } 39 | mode, exist := EnhancedModeMapping[tp] 40 | if !exist { 41 | return errors.New("invalid mode") 42 | } 43 | *e = mode 44 | return nil 45 | } 46 | 47 | // MarshalYAML serialize EnhancedMode with yaml 48 | func (e EnhancedMode) MarshalYAML() (interface{}, error) { 49 | return e.String(), nil 50 | } 51 | 52 | // UnmarshalJSON unserialize EnhancedMode with json 53 | func (e *EnhancedMode) UnmarshalJSON(data []byte) error { 54 | var tp string 55 | json.Unmarshal(data, &tp) 56 | mode, exist := EnhancedModeMapping[tp] 57 | if !exist { 58 | return errors.New("invalid mode") 59 | } 60 | *e = mode 61 | return nil 62 | } 63 | 64 | // MarshalJSON serialize EnhancedMode with json 65 | func (e EnhancedMode) MarshalJSON() ([]byte, error) { 66 | return json.Marshal(e.String()) 67 | } 68 | 69 | func (e EnhancedMode) String() string { 70 | switch e { 71 | case NORMAL: 72 | return "normal" 73 | case FAKEIP: 74 | return "fake-ip" 75 | case MAPPING: 76 | return "redir-host" 77 | default: 78 | return "unknown" 79 | } 80 | } 81 | 82 | func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) { 83 | var ttl uint32 84 | switch { 85 | case len(msg.Answer) != 0: 86 | ttl = msg.Answer[0].Header().Ttl 87 | case len(msg.Ns) != 0: 88 | ttl = msg.Ns[0].Header().Ttl 89 | case len(msg.Extra) != 0: 90 | ttl = msg.Extra[0].Header().Ttl 91 | default: 92 | log.Debugln("[DNS] response msg empty: %#v", msg) 93 | return 94 | } 95 | 96 | c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl))) 97 | } 98 | 99 | func setMsgTTL(msg *D.Msg, ttl uint32) { 100 | for _, answer := range msg.Answer { 101 | answer.Header().Ttl = ttl 102 | } 103 | 104 | for _, ns := range msg.Ns { 105 | ns.Header().Ttl = ttl 106 | } 107 | 108 | for _, extra := range msg.Extra { 109 | extra.Header().Ttl = ttl 110 | } 111 | } 112 | 113 | func isIPRequest(q D.Question) bool { 114 | return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) 115 | } 116 | 117 | func transform(servers []NameServer, resolver *Resolver) []dnsClient { 118 | ret := []dnsClient{} 119 | for _, s := range servers { 120 | if s.Net == "https" { 121 | ret = append(ret, newDoHClient(s.Addr, resolver)) 122 | continue 123 | } 124 | 125 | host, port, _ := net.SplitHostPort(s.Addr) 126 | ret = append(ret, &client{ 127 | Client: &D.Client{ 128 | Net: s.Net, 129 | TLSConfig: &tls.Config{ 130 | ClientSessionCache: globalSessionCache, 131 | // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 132 | NextProtos: []string{"dns"}, 133 | ServerName: host, 134 | }, 135 | UDPSize: 4096, 136 | Timeout: 5 * time.Second, 137 | }, 138 | port: port, 139 | host: host, 140 | r: resolver, 141 | }) 142 | } 143 | return ret 144 | } 145 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naicfeng/clash/de2d66dcc686a559e7d94b21aaef85cfcbaada9a/docs/logo.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Dreamacro/clash 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Dreamacro/go-shadowsocks2 v0.1.5 7 | github.com/eapache/queue v1.1.0 // indirect 8 | github.com/go-chi/chi v4.1.2+incompatible 9 | github.com/go-chi/cors v1.1.1 10 | github.com/go-chi/render v1.0.1 11 | github.com/gofrs/uuid v3.3.0+incompatible 12 | github.com/gorilla/websocket v1.4.2 13 | github.com/miekg/dns v1.1.29 14 | github.com/oschwald/geoip2-golang v1.4.0 15 | github.com/sirupsen/logrus v1.6.0 16 | github.com/stretchr/testify v1.6.1 17 | github.com/v2rayA/shadowsocksR v1.0.3 18 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c 19 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb 20 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 21 | golang.org/x/sys v0.0.0-20201202213521-69691e467435 22 | gopkg.in/eapache/channels.v1 v1.1.0 23 | gopkg.in/yaml.v2 v2.3.0 24 | ) 25 | -------------------------------------------------------------------------------- /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" 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/configs.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | "path/filepath" 6 | 7 | "github.com/Dreamacro/clash/config" 8 | "github.com/Dreamacro/clash/hub/executor" 9 | "github.com/Dreamacro/clash/log" 10 | P "github.com/Dreamacro/clash/proxy" 11 | "github.com/Dreamacro/clash/tunnel" 12 | 13 | "github.com/go-chi/chi" 14 | "github.com/go-chi/render" 15 | ) 16 | 17 | func configRouter() http.Handler { 18 | r := chi.NewRouter() 19 | r.Get("/", getConfigs) 20 | r.Put("/", updateConfigs) 21 | r.Patch("/", patchConfigs) 22 | return r 23 | } 24 | 25 | type configSchema struct { 26 | Port *int `json:"port"` 27 | SocksPort *int `json:"socks-port"` 28 | RedirPort *int `json:"redir-port"` 29 | MixedPort *int `json:"mixed-port"` 30 | AllowLan *bool `json:"allow-lan"` 31 | BindAddress *string `json:"bind-address"` 32 | Mode *tunnel.TunnelMode `json:"mode"` 33 | LogLevel *log.LogLevel `json:"log-level"` 34 | } 35 | 36 | func getConfigs(w http.ResponseWriter, r *http.Request) { 37 | general := executor.GetGeneral() 38 | render.JSON(w, r, general) 39 | } 40 | 41 | func pointerOrDefault(p *int, def int) int { 42 | if p != nil { 43 | return *p 44 | } 45 | 46 | return def 47 | } 48 | 49 | func patchConfigs(w http.ResponseWriter, r *http.Request) { 50 | general := &configSchema{} 51 | if err := render.DecodeJSON(r.Body, general); err != nil { 52 | render.Status(r, http.StatusBadRequest) 53 | render.JSON(w, r, ErrBadRequest) 54 | return 55 | } 56 | 57 | if general.AllowLan != nil { 58 | P.SetAllowLan(*general.AllowLan) 59 | } 60 | 61 | if general.BindAddress != nil { 62 | P.SetBindAddress(*general.BindAddress) 63 | } 64 | 65 | ports := P.GetPorts() 66 | P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) 67 | P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) 68 | P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) 69 | P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) 70 | 71 | if general.Mode != nil { 72 | tunnel.SetMode(*general.Mode) 73 | } 74 | 75 | if general.LogLevel != nil { 76 | log.SetLevel(*general.LogLevel) 77 | } 78 | 79 | render.NoContent(w, r) 80 | } 81 | 82 | type updateConfigRequest struct { 83 | Path string `json:"path"` 84 | Payload string `json:"payload"` 85 | } 86 | 87 | func updateConfigs(w http.ResponseWriter, r *http.Request) { 88 | req := updateConfigRequest{} 89 | if err := render.DecodeJSON(r.Body, &req); err != nil { 90 | render.Status(r, http.StatusBadRequest) 91 | render.JSON(w, r, ErrBadRequest) 92 | return 93 | } 94 | 95 | force := r.URL.Query().Get("force") == "true" 96 | var cfg *config.Config 97 | var err error 98 | 99 | if req.Payload != "" { 100 | cfg, err = executor.ParseWithBytes([]byte(req.Payload)) 101 | if err != nil { 102 | render.Status(r, http.StatusBadRequest) 103 | render.JSON(w, r, newError(err.Error())) 104 | return 105 | } 106 | } else { 107 | if !filepath.IsAbs(req.Path) { 108 | render.Status(r, http.StatusBadRequest) 109 | render.JSON(w, r, newError("path is not a absoluted path")) 110 | return 111 | } 112 | 113 | cfg, err = executor.ParseWithPath(req.Path) 114 | if err != nil { 115 | render.Status(r, http.StatusBadRequest) 116 | render.JSON(w, r, newError(err.Error())) 117 | return 118 | } 119 | } 120 | 121 | executor.ApplyConfig(cfg, force) 122 | render.NoContent(w, r) 123 | } 124 | -------------------------------------------------------------------------------- /hub/route/connections.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | T "github.com/Dreamacro/clash/tunnel" 11 | "github.com/gorilla/websocket" 12 | 13 | "github.com/go-chi/chi" 14 | "github.com/go-chi/render" 15 | ) 16 | 17 | func connectionRouter() http.Handler { 18 | r := chi.NewRouter() 19 | r.Get("/", getConnections) 20 | r.Delete("/", closeAllConnections) 21 | r.Delete("/{id}", closeConnection) 22 | return r 23 | } 24 | 25 | func getConnections(w http.ResponseWriter, r *http.Request) { 26 | if !websocket.IsWebSocketUpgrade(r) { 27 | snapshot := T.DefaultManager.Snapshot() 28 | render.JSON(w, r, snapshot) 29 | return 30 | } 31 | 32 | conn, err := upgrader.Upgrade(w, r, nil) 33 | if err != nil { 34 | return 35 | } 36 | 37 | intervalStr := r.URL.Query().Get("interval") 38 | interval := 1000 39 | if intervalStr != "" { 40 | t, err := strconv.Atoi(intervalStr) 41 | if err != nil { 42 | render.Status(r, http.StatusBadRequest) 43 | render.JSON(w, r, ErrBadRequest) 44 | return 45 | } 46 | 47 | interval = t 48 | } 49 | 50 | buf := &bytes.Buffer{} 51 | sendSnapshot := func() error { 52 | buf.Reset() 53 | snapshot := T.DefaultManager.Snapshot() 54 | if err := json.NewEncoder(buf).Encode(snapshot); err != nil { 55 | return err 56 | } 57 | 58 | return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) 59 | } 60 | 61 | if err := sendSnapshot(); err != nil { 62 | return 63 | } 64 | 65 | tick := time.NewTicker(time.Millisecond * time.Duration(interval)) 66 | for range tick.C { 67 | if err := sendSnapshot(); err != nil { 68 | break 69 | } 70 | } 71 | } 72 | 73 | func closeConnection(w http.ResponseWriter, r *http.Request) { 74 | id := chi.URLParam(r, "id") 75 | snapshot := T.DefaultManager.Snapshot() 76 | for _, c := range snapshot.Connections { 77 | if id == c.ID() { 78 | c.Close() 79 | break 80 | } 81 | } 82 | render.NoContent(w, r) 83 | } 84 | 85 | func closeAllConnections(w http.ResponseWriter, r *http.Request) { 86 | snapshot := T.DefaultManager.Snapshot() 87 | for _, c := range snapshot.Connections { 88 | c.Close() 89 | } 90 | render.NoContent(w, r) 91 | } 92 | -------------------------------------------------------------------------------- /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/provider.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/Dreamacro/clash/adapters/provider" 8 | "github.com/Dreamacro/clash/tunnel" 9 | 10 | "github.com/go-chi/chi" 11 | "github.com/go-chi/render" 12 | ) 13 | 14 | func proxyProviderRouter() http.Handler { 15 | r := chi.NewRouter() 16 | r.Get("/", getProviders) 17 | 18 | r.Route("/{name}", func(r chi.Router) { 19 | r.Use(parseProviderName, findProviderByName) 20 | r.Get("/", getProvider) 21 | r.Put("/", updateProvider) 22 | r.Get("/healthcheck", healthCheckProvider) 23 | }) 24 | return r 25 | } 26 | 27 | func getProviders(w http.ResponseWriter, r *http.Request) { 28 | providers := tunnel.Providers() 29 | render.JSON(w, r, render.M{ 30 | "providers": providers, 31 | }) 32 | } 33 | 34 | func getProvider(w http.ResponseWriter, r *http.Request) { 35 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 36 | render.JSON(w, r, provider) 37 | } 38 | 39 | func updateProvider(w http.ResponseWriter, r *http.Request) { 40 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 41 | if err := provider.Update(); err != nil { 42 | render.Status(r, http.StatusServiceUnavailable) 43 | render.JSON(w, r, newError(err.Error())) 44 | return 45 | } 46 | render.NoContent(w, r) 47 | } 48 | 49 | func healthCheckProvider(w http.ResponseWriter, r *http.Request) { 50 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 51 | provider.HealthCheck() 52 | render.NoContent(w, r) 53 | } 54 | 55 | func parseProviderName(next http.Handler) http.Handler { 56 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 57 | name := getEscapeParam(r, "name") 58 | ctx := context.WithValue(r.Context(), CtxKeyProviderName, name) 59 | next.ServeHTTP(w, r.WithContext(ctx)) 60 | }) 61 | } 62 | 63 | func findProviderByName(next http.Handler) http.Handler { 64 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 65 | name := r.Context().Value(CtxKeyProviderName).(string) 66 | providers := tunnel.Providers() 67 | provider, exist := providers[name] 68 | if !exist { 69 | render.Status(r, http.StatusNotFound) 70 | render.JSON(w, r, ErrNotFound) 71 | return 72 | } 73 | 74 | ctx := context.WithValue(r.Context(), CtxKeyProvider, provider) 75 | next.ServeHTTP(w, r.WithContext(ctx)) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /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" 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 | -------------------------------------------------------------------------------- /log/level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // LogLevelMapping is a mapping for LogLevel enum 10 | LogLevelMapping = map[string]LogLevel{ 11 | ERROR.String(): ERROR, 12 | WARNING.String(): WARNING, 13 | INFO.String(): INFO, 14 | DEBUG.String(): DEBUG, 15 | SILENT.String(): SILENT, 16 | } 17 | ) 18 | 19 | const ( 20 | DEBUG LogLevel = iota 21 | INFO 22 | WARNING 23 | ERROR 24 | SILENT 25 | ) 26 | 27 | type LogLevel int 28 | 29 | // UnmarshalYAML unserialize LogLevel with yaml 30 | func (l *LogLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { 31 | var tp string 32 | unmarshal(&tp) 33 | level, exist := LogLevelMapping[tp] 34 | if !exist { 35 | return errors.New("invalid mode") 36 | } 37 | *l = level 38 | return nil 39 | } 40 | 41 | // UnmarshalJSON unserialize LogLevel with json 42 | func (l *LogLevel) UnmarshalJSON(data []byte) error { 43 | var tp string 44 | json.Unmarshal(data, &tp) 45 | level, exist := LogLevelMapping[tp] 46 | if !exist { 47 | return errors.New("invalid mode") 48 | } 49 | *l = level 50 | return nil 51 | } 52 | 53 | // MarshalJSON serialize LogLevel with json 54 | func (l LogLevel) MarshalJSON() ([]byte, error) { 55 | return json.Marshal(l.String()) 56 | } 57 | 58 | // MarshalYAML serialize LogLevel with yaml 59 | func (l LogLevel) MarshalYAML() (interface{}, error) { 60 | return l.String(), nil 61 | } 62 | 63 | func (l LogLevel) String() string { 64 | switch l { 65 | case INFO: 66 | return "info" 67 | case WARNING: 68 | return "warning" 69 | case ERROR: 70 | return "error" 71 | case DEBUG: 72 | return "debug" 73 | case SILENT: 74 | return "silent" 75 | default: 76 | return "unknown" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Dreamacro/clash/common/observable" 8 | 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var ( 13 | logCh = make(chan interface{}) 14 | source = observable.NewObservable(logCh) 15 | level = INFO 16 | ) 17 | 18 | func init() { 19 | log.SetOutput(os.Stdout) 20 | log.SetLevel(log.DebugLevel) 21 | } 22 | 23 | type Event struct { 24 | LogLevel LogLevel 25 | Payload string 26 | } 27 | 28 | func (e *Event) Type() string { 29 | return e.LogLevel.String() 30 | } 31 | 32 | func Infoln(format string, v ...interface{}) { 33 | event := newLog(INFO, format, v...) 34 | logCh <- event 35 | print(event) 36 | } 37 | 38 | func Warnln(format string, v ...interface{}) { 39 | event := newLog(WARNING, format, v...) 40 | logCh <- event 41 | print(event) 42 | } 43 | 44 | func Errorln(format string, v ...interface{}) { 45 | event := newLog(ERROR, format, v...) 46 | logCh <- event 47 | print(event) 48 | } 49 | 50 | func Debugln(format string, v ...interface{}) { 51 | event := newLog(DEBUG, format, v...) 52 | logCh <- event 53 | print(event) 54 | } 55 | 56 | func Fatalln(format string, v ...interface{}) { 57 | log.Fatalf(format, v...) 58 | } 59 | 60 | func Subscribe() observable.Subscription { 61 | sub, _ := source.Subscribe() 62 | return sub 63 | } 64 | 65 | func UnSubscribe(sub observable.Subscription) { 66 | source.UnSubscribe(sub) 67 | return 68 | } 69 | 70 | func Level() LogLevel { 71 | return level 72 | } 73 | 74 | func SetLevel(newLevel LogLevel) { 75 | level = newLevel 76 | } 77 | 78 | func print(data *Event) { 79 | if data.LogLevel < level { 80 | return 81 | } 82 | 83 | switch data.LogLevel { 84 | case INFO: 85 | log.Infoln(data.Payload) 86 | case WARNING: 87 | log.Warnln(data.Payload) 88 | case ERROR: 89 | log.Errorln(data.Payload) 90 | case DEBUG: 91 | log.Debugln(data.Payload) 92 | } 93 | } 94 | 95 | func newLog(logLevel LogLevel, format string, v ...interface{}) *Event { 96 | return &Event{ 97 | LogLevel: logLevel, 98 | Payload: fmt.Sprintf(format, v...), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "path/filepath" 9 | "runtime" 10 | "syscall" 11 | 12 | "github.com/Dreamacro/clash/config" 13 | "github.com/Dreamacro/clash/constant" 14 | C "github.com/Dreamacro/clash/constant" 15 | "github.com/Dreamacro/clash/hub" 16 | "github.com/Dreamacro/clash/hub/executor" 17 | "github.com/Dreamacro/clash/log" 18 | ) 19 | 20 | var ( 21 | flagset map[string]bool 22 | version bool 23 | testConfig bool 24 | homeDir string 25 | configFile string 26 | externalUI string 27 | externalController string 28 | secret string 29 | ) 30 | 31 | func init() { 32 | flag.StringVar(&homeDir, "d", "", "set configuration directory") 33 | flag.StringVar(&configFile, "f", "", "specify configuration file") 34 | flag.StringVar(&externalUI, "ext-ui", "", "override external ui directory") 35 | flag.StringVar(&externalController, "ext-ctl", "", "override external controller address") 36 | flag.StringVar(&secret, "secret", "", "override secret for RESTful API") 37 | flag.BoolVar(&version, "v", false, "show current version of clash") 38 | flag.BoolVar(&testConfig, "t", false, "test configuration and exit") 39 | flag.Parse() 40 | 41 | flagset = map[string]bool{} 42 | flag.Visit(func(f *flag.Flag) { 43 | flagset[f.Name] = true 44 | }) 45 | } 46 | 47 | func main() { 48 | if version { 49 | fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime) 50 | return 51 | } 52 | 53 | if homeDir != "" { 54 | if !filepath.IsAbs(homeDir) { 55 | currentDir, _ := os.Getwd() 56 | homeDir = filepath.Join(currentDir, homeDir) 57 | } 58 | C.SetHomeDir(homeDir) 59 | } 60 | 61 | if configFile != "" { 62 | if !filepath.IsAbs(configFile) { 63 | currentDir, _ := os.Getwd() 64 | configFile = filepath.Join(currentDir, configFile) 65 | } 66 | C.SetConfig(configFile) 67 | } else { 68 | configFile := filepath.Join(C.Path.HomeDir(), C.Path.Config()) 69 | C.SetConfig(configFile) 70 | } 71 | 72 | if err := config.Init(C.Path.HomeDir()); err != nil { 73 | log.Fatalln("Initial configuration directory error: %s", err.Error()) 74 | } 75 | 76 | if testConfig { 77 | if _, err := executor.Parse(); err != nil { 78 | log.Errorln(err.Error()) 79 | fmt.Printf("configuration file %s test failed\n", constant.Path.Config()) 80 | os.Exit(1) 81 | } 82 | fmt.Printf("configuration file %s test is successful\n", constant.Path.Config()) 83 | return 84 | } 85 | 86 | var options []hub.Option 87 | if flagset["ext-ui"] { 88 | options = append(options, hub.WithExternalUI(externalUI)) 89 | } 90 | if flagset["ext-ctl"] { 91 | options = append(options, hub.WithExternalController(externalController)) 92 | } 93 | if flagset["secret"] { 94 | options = append(options, hub.WithSecret(secret)) 95 | } 96 | 97 | if err := hub.Parse(options...); err != nil { 98 | log.Fatalln("Parse config error: %s", err.Error()) 99 | } 100 | 101 | sigCh := make(chan os.Signal, 1) 102 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 103 | <-sigCh 104 | } 105 | -------------------------------------------------------------------------------- /proxy/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/auth" 5 | ) 6 | 7 | var ( 8 | authenticator auth.Authenticator 9 | ) 10 | 11 | func Authenticator() auth.Authenticator { 12 | return authenticator 13 | } 14 | 15 | func SetAuthenticator(au auth.Authenticator) { 16 | authenticator = au 17 | } 18 | -------------------------------------------------------------------------------- /proxy/http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base64" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | adapters "github.com/Dreamacro/clash/adapters/inbound" 12 | "github.com/Dreamacro/clash/common/cache" 13 | "github.com/Dreamacro/clash/component/auth" 14 | "github.com/Dreamacro/clash/log" 15 | authStore "github.com/Dreamacro/clash/proxy/auth" 16 | "github.com/Dreamacro/clash/tunnel" 17 | ) 18 | 19 | type HttpListener struct { 20 | net.Listener 21 | address string 22 | closed bool 23 | cache *cache.Cache 24 | } 25 | 26 | func NewHttpProxy(addr string) (*HttpListener, error) { 27 | l, err := net.Listen("tcp", addr) 28 | if err != nil { 29 | return nil, err 30 | } 31 | hl := &HttpListener{l, addr, false, cache.New(30 * time.Second)} 32 | 33 | go func() { 34 | log.Infoln("HTTP proxy listening at: %s", addr) 35 | 36 | for { 37 | c, err := hl.Accept() 38 | if err != nil { 39 | if hl.closed { 40 | break 41 | } 42 | continue 43 | } 44 | go HandleConn(c, hl.cache) 45 | } 46 | }() 47 | 48 | return hl, nil 49 | } 50 | 51 | func (l *HttpListener) Close() { 52 | l.closed = true 53 | l.Listener.Close() 54 | } 55 | 56 | func (l *HttpListener) Address() string { 57 | return l.address 58 | } 59 | 60 | func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) { 61 | if result := cache.Get(loginStr); result != nil { 62 | ret = result.(bool) 63 | } 64 | loginData, err := base64.StdEncoding.DecodeString(loginStr) 65 | login := strings.Split(string(loginData), ":") 66 | ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1]) 67 | 68 | cache.Put(loginStr, ret, time.Minute) 69 | return 70 | } 71 | 72 | func HandleConn(conn net.Conn, cache *cache.Cache) { 73 | br := bufio.NewReader(conn) 74 | request, err := http.ReadRequest(br) 75 | if err != nil || request.URL.Host == "" { 76 | conn.Close() 77 | return 78 | } 79 | 80 | authenticator := authStore.Authenticator() 81 | if authenticator != nil { 82 | if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { 83 | _, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) 84 | conn.Close() 85 | return 86 | } else if !canActivate(authStrings[1], authenticator, cache) { 87 | conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) 88 | log.Infoln("Auth failed from %s", conn.RemoteAddr().String()) 89 | conn.Close() 90 | return 91 | } 92 | } 93 | 94 | if request.Method == http.MethodConnect { 95 | _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) 96 | if err != nil { 97 | return 98 | } 99 | tunnel.Add(adapters.NewHTTPS(request, conn)) 100 | return 101 | } 102 | 103 | tunnel.Add(adapters.NewHTTP(request, conn)) 104 | } 105 | -------------------------------------------------------------------------------- /proxy/mixed/conn.go: -------------------------------------------------------------------------------- 1 | package mixed 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 | return &BufferedConn{bufio.NewReader(c), c} 15 | } 16 | 17 | // Reader returns the internal bufio.Reader. 18 | func (c *BufferedConn) Reader() *bufio.Reader { 19 | return c.r 20 | } 21 | 22 | // Peek returns the next n bytes without advancing the reader. 23 | func (c *BufferedConn) Peek(n int) ([]byte, error) { 24 | return c.r.Peek(n) 25 | } 26 | 27 | func (c *BufferedConn) Read(p []byte) (int, error) { 28 | return c.r.Read(p) 29 | } 30 | 31 | func (c *BufferedConn) ReadByte() (byte, error) { 32 | return c.r.ReadByte() 33 | } 34 | 35 | func (c *BufferedConn) UnreadByte() error { 36 | return c.r.UnreadByte() 37 | } 38 | 39 | func (c *BufferedConn) Buffered() int { 40 | return c.r.Buffered() 41 | } 42 | -------------------------------------------------------------------------------- /proxy/mixed/mixed.go: -------------------------------------------------------------------------------- 1 | package mixed 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/common/cache" 8 | "github.com/Dreamacro/clash/component/socks5" 9 | "github.com/Dreamacro/clash/log" 10 | 11 | "github.com/Dreamacro/clash/proxy/http" 12 | "github.com/Dreamacro/clash/proxy/socks" 13 | ) 14 | 15 | type MixedListener struct { 16 | net.Listener 17 | address string 18 | closed bool 19 | cache *cache.Cache 20 | } 21 | 22 | func NewMixedProxy(addr string) (*MixedListener, error) { 23 | l, err := net.Listen("tcp", addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)} 29 | go func() { 30 | log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr) 31 | 32 | for { 33 | c, err := ml.Accept() 34 | if err != nil { 35 | if ml.closed { 36 | break 37 | } 38 | continue 39 | } 40 | go handleConn(c, ml.cache) 41 | } 42 | }() 43 | 44 | return ml, nil 45 | } 46 | 47 | func (l *MixedListener) Close() { 48 | l.closed = true 49 | l.Listener.Close() 50 | } 51 | 52 | func (l *MixedListener) Address() string { 53 | return l.address 54 | } 55 | 56 | func handleConn(conn net.Conn, cache *cache.Cache) { 57 | bufConn := NewBufferedConn(conn) 58 | head, err := bufConn.Peek(1) 59 | if err != nil { 60 | return 61 | } 62 | 63 | if head[0] == socks5.Version { 64 | socks.HandleSocks(bufConn) 65 | return 66 | } 67 | 68 | http.HandleConn(bufConn, cache) 69 | } 70 | -------------------------------------------------------------------------------- /proxy/redir/tcp.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapters/inbound" 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/log" 9 | "github.com/Dreamacro/clash/tunnel" 10 | ) 11 | 12 | type RedirListener struct { 13 | net.Listener 14 | address string 15 | closed bool 16 | } 17 | 18 | func NewRedirProxy(addr string) (*RedirListener, error) { 19 | l, err := net.Listen("tcp", addr) 20 | if err != nil { 21 | return nil, err 22 | } 23 | rl := &RedirListener{l, addr, false} 24 | 25 | go func() { 26 | log.Infoln("Redir proxy listening at: %s", addr) 27 | for { 28 | c, err := l.Accept() 29 | if err != nil { 30 | if rl.closed { 31 | break 32 | } 33 | continue 34 | } 35 | go handleRedir(c) 36 | } 37 | }() 38 | 39 | return rl, nil 40 | } 41 | 42 | func (l *RedirListener) Close() { 43 | l.closed = true 44 | l.Listener.Close() 45 | } 46 | 47 | func (l *RedirListener) Address() string { 48 | return l.address 49 | } 50 | 51 | func handleRedir(conn net.Conn) { 52 | target, err := parserPacket(conn) 53 | if err != nil { 54 | conn.Close() 55 | return 56 | } 57 | conn.(*net.TCPConn).SetKeepAlive(true) 58 | tunnel.Add(inbound.NewSocket(target, conn, C.REDIR)) 59 | } 60 | -------------------------------------------------------------------------------- /proxy/redir/tcp_darwin.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | "unsafe" 7 | 8 | "github.com/Dreamacro/clash/component/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 | -------------------------------------------------------------------------------- /proxy/redir/tcp_freebsd.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/Dreamacro/clash/component/socks5" 10 | ) 11 | 12 | const ( 13 | SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h 14 | IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h 15 | ) 16 | 17 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 18 | c, ok := conn.(*net.TCPConn) 19 | if !ok { 20 | return nil, errors.New("only work with TCP connection") 21 | } 22 | 23 | rc, err := c.SyscallConn() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | var addr socks5.Addr 29 | 30 | rc.Control(func(fd uintptr) { 31 | addr, err = getorigdst(fd) 32 | }) 33 | 34 | return addr, err 35 | } 36 | 37 | // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c 38 | func getorigdst(fd uintptr) (socks5.Addr, error) { 39 | raw := syscall.RawSockaddrInet4{} 40 | siz := unsafe.Sizeof(raw) 41 | _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0) 42 | if err != 0 { 43 | return nil, err 44 | } 45 | 46 | addr := make([]byte, 1+net.IPv4len+2) 47 | addr[0] = socks5.AtypIPv4 48 | copy(addr[1:1+net.IPv4len], raw.Addr[:]) 49 | port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian 50 | addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] 51 | return addr, nil 52 | } 53 | -------------------------------------------------------------------------------- /proxy/redir/tcp_linux.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/Dreamacro/clash/component/socks5" 10 | ) 11 | 12 | const ( 13 | SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h 14 | IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h 15 | ) 16 | 17 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 18 | c, ok := conn.(*net.TCPConn) 19 | if !ok { 20 | return nil, errors.New("only work with TCP connection") 21 | } 22 | 23 | rc, err := c.SyscallConn() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | var addr socks5.Addr 29 | 30 | rc.Control(func(fd uintptr) { 31 | addr, err = getorigdst(fd) 32 | }) 33 | 34 | return addr, err 35 | } 36 | 37 | // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c 38 | func getorigdst(fd uintptr) (socks5.Addr, error) { 39 | raw := syscall.RawSockaddrInet4{} 40 | siz := unsafe.Sizeof(raw) 41 | if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { 42 | return nil, err 43 | } 44 | 45 | addr := make([]byte, 1+net.IPv4len+2) 46 | addr[0] = socks5.AtypIPv4 47 | copy(addr[1:1+net.IPv4len], raw.Addr[:]) 48 | port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian 49 | addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] 50 | return addr, nil 51 | } 52 | -------------------------------------------------------------------------------- /proxy/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 | -------------------------------------------------------------------------------- /proxy/redir/tcp_linux_other.go: -------------------------------------------------------------------------------- 1 | // +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 | -------------------------------------------------------------------------------- /proxy/redir/tcp_windows.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/component/socks5" 8 | ) 9 | 10 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 11 | return nil, errors.New("Windows not support yet") 12 | } 13 | -------------------------------------------------------------------------------- /proxy/redir/udp.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | adapters "github.com/Dreamacro/clash/adapters/inbound" 7 | "github.com/Dreamacro/clash/common/pool" 8 | "github.com/Dreamacro/clash/component/socks5" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/tunnel" 11 | ) 12 | 13 | type RedirUDPListener struct { 14 | net.PacketConn 15 | address string 16 | closed bool 17 | } 18 | 19 | func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { 20 | l, err := net.ListenPacket("udp", addr) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | rl := &RedirUDPListener{l, addr, false} 26 | 27 | c := l.(*net.UDPConn) 28 | 29 | err = setsockopt(c, addr) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | go func() { 35 | oob := make([]byte, 1024) 36 | for { 37 | buf := pool.Get(pool.RelayBufferSize) 38 | n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) 39 | if err != nil { 40 | pool.Put(buf) 41 | if rl.closed { 42 | break 43 | } 44 | continue 45 | } 46 | 47 | rAddr, err := getOrigDst(oob, oobn) 48 | if err != nil { 49 | continue 50 | } 51 | handleRedirUDP(l, buf[:n], lAddr, rAddr) 52 | } 53 | }() 54 | 55 | return rl, nil 56 | } 57 | 58 | func (l *RedirUDPListener) Close() error { 59 | l.closed = true 60 | return l.PacketConn.Close() 61 | } 62 | 63 | func (l *RedirUDPListener) Address() string { 64 | return l.address 65 | } 66 | 67 | func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { 68 | target := socks5.ParseAddrToSocksAddr(rAddr) 69 | pkt := &packet{ 70 | lAddr: lAddr, 71 | buf: buf, 72 | } 73 | tunnel.AddPacket(adapters.NewPacket(target, pkt, C.REDIR)) 74 | } 75 | -------------------------------------------------------------------------------- /proxy/redir/udp_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package redir 4 | 5 | import ( 6 | "encoding/binary" 7 | "errors" 8 | "net" 9 | "syscall" 10 | ) 11 | 12 | const ( 13 | IPV6_TRANSPARENT = 0x4b 14 | IPV6_RECVORIGDSTADDR = 0x4a 15 | ) 16 | 17 | func setsockopt(c *net.UDPConn, addr string) error { 18 | isIPv6 := true 19 | host, _, err := net.SplitHostPort(addr) 20 | if err != nil { 21 | return err 22 | } 23 | ip := net.ParseIP(host) 24 | if ip != nil && ip.To4() != nil { 25 | isIPv6 = false 26 | } 27 | 28 | rc, err := c.SyscallConn() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | rc.Control(func(fd uintptr) { 34 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 35 | 36 | if err == nil { 37 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) 38 | } 39 | if err == nil && isIPv6 { 40 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) 41 | } 42 | 43 | if err == nil { 44 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) 45 | } 46 | if err == nil && isIPv6 { 47 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) 48 | } 49 | }) 50 | 51 | return err 52 | } 53 | 54 | func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { 55 | msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | for _, msg := range msgs { 61 | if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { 62 | ip := net.IP(msg.Data[4:8]) 63 | port := binary.BigEndian.Uint16(msg.Data[2:4]) 64 | return &net.UDPAddr{IP: ip, Port: int(port)}, nil 65 | } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR { 66 | ip := net.IP(msg.Data[8:24]) 67 | port := binary.BigEndian.Uint16(msg.Data[2:4]) 68 | return &net.UDPAddr{IP: ip, Port: int(port)}, nil 69 | } 70 | } 71 | 72 | return nil, errors.New("cannot find origDst") 73 | } 74 | -------------------------------------------------------------------------------- /proxy/redir/udp_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func setsockopt(c *net.UDPConn, addr string) error { 11 | return errors.New("UDP redir not supported on current platform") 12 | } 13 | 14 | func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { 15 | return nil, errors.New("UDP redir not supported on current platform") 16 | } 17 | -------------------------------------------------------------------------------- /proxy/redir/utils.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | type packet struct { 10 | lAddr *net.UDPAddr 11 | buf []byte 12 | } 13 | 14 | func (c *packet) Data() []byte { 15 | return c.buf 16 | } 17 | 18 | // WriteBack opens a new socket binding `addr` to wirte UDP packet back 19 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 20 | tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) 21 | if err != nil { 22 | n = 0 23 | return 24 | } 25 | n, err = tc.Write(b) 26 | tc.Close() 27 | return 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *packet) LocalAddr() net.Addr { 32 | return c.lAddr 33 | } 34 | 35 | func (c *packet) Drop() { 36 | pool.Put(c.buf) 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /proxy/redir/utils_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package redir 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | "os" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | // dialUDP acts like net.DialUDP for transparent proxy. 14 | // It binds to a non-local address(`lAddr`). 15 | func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { 16 | rSockAddr, err := udpAddrToSockAddr(rAddr) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | lSockAddr, err := udpAddrToSockAddr(lAddr) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { 32 | syscall.Close(fd) 33 | return nil, err 34 | } 35 | 36 | if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { 37 | syscall.Close(fd) 38 | return nil, err 39 | } 40 | 41 | if err = syscall.Bind(fd, lSockAddr); err != nil { 42 | syscall.Close(fd) 43 | return nil, err 44 | } 45 | 46 | if err = syscall.Connect(fd, rSockAddr); err != nil { 47 | syscall.Close(fd) 48 | return nil, err 49 | } 50 | 51 | fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String())) 52 | defer fdFile.Close() 53 | 54 | c, err := net.FileConn(fdFile) 55 | if err != nil { 56 | syscall.Close(fd) 57 | return nil, err 58 | } 59 | 60 | return c.(*net.UDPConn), nil 61 | } 62 | 63 | func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { 64 | switch { 65 | case addr.IP.To4() != nil: 66 | ip := [4]byte{} 67 | copy(ip[:], addr.IP.To4()) 68 | 69 | return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil 70 | 71 | default: 72 | ip := [16]byte{} 73 | copy(ip[:], addr.IP.To16()) 74 | 75 | zoneID, err := strconv.ParseUint(addr.Zone, 10, 32) 76 | if err != nil { 77 | zoneID = 0 78 | } 79 | 80 | return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil 81 | } 82 | } 83 | 84 | func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { 85 | switch net[len(net)-1] { 86 | case '4': 87 | return syscall.AF_INET 88 | case '6': 89 | return syscall.AF_INET6 90 | } 91 | 92 | if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { 93 | return syscall.AF_INET 94 | } 95 | return syscall.AF_INET6 96 | } 97 | -------------------------------------------------------------------------------- /proxy/redir/utils_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { 11 | return nil, errors.New("UDP redir not supported on current platform") 12 | } 13 | -------------------------------------------------------------------------------- /proxy/socks/tcp.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "net" 7 | 8 | adapters "github.com/Dreamacro/clash/adapters/inbound" 9 | "github.com/Dreamacro/clash/component/socks5" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | authStore "github.com/Dreamacro/clash/proxy/auth" 13 | "github.com/Dreamacro/clash/tunnel" 14 | ) 15 | 16 | type SockListener struct { 17 | net.Listener 18 | address string 19 | closed bool 20 | } 21 | 22 | func NewSocksProxy(addr string) (*SockListener, error) { 23 | l, err := net.Listen("tcp", addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | sl := &SockListener{l, addr, false} 29 | go func() { 30 | log.Infoln("SOCKS proxy listening at: %s", addr) 31 | for { 32 | c, err := l.Accept() 33 | if err != nil { 34 | if sl.closed { 35 | break 36 | } 37 | continue 38 | } 39 | go HandleSocks(c) 40 | } 41 | }() 42 | 43 | return sl, nil 44 | } 45 | 46 | func (l *SockListener) Close() { 47 | l.closed = true 48 | l.Listener.Close() 49 | } 50 | 51 | func (l *SockListener) Address() string { 52 | return l.address 53 | } 54 | 55 | func HandleSocks(conn net.Conn) { 56 | target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) 57 | if err != nil { 58 | conn.Close() 59 | return 60 | } 61 | if c, ok := conn.(*net.TCPConn); ok { 62 | c.SetKeepAlive(true) 63 | } 64 | if command == socks5.CmdUDPAssociate { 65 | defer conn.Close() 66 | io.Copy(ioutil.Discard, conn) 67 | return 68 | } 69 | tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS)) 70 | } 71 | -------------------------------------------------------------------------------- /proxy/socks/udp.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | adapters "github.com/Dreamacro/clash/adapters/inbound" 7 | "github.com/Dreamacro/clash/common/pool" 8 | "github.com/Dreamacro/clash/common/sockopt" 9 | "github.com/Dreamacro/clash/component/socks5" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/tunnel" 12 | ) 13 | 14 | type SockUDPListener struct { 15 | net.PacketConn 16 | address string 17 | closed bool 18 | } 19 | 20 | func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { 21 | l, err := net.ListenPacket("udp", addr) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | err = sockopt.UDPReuseaddr(l.(*net.UDPConn)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | sl := &SockUDPListener{l, addr, false} 32 | go func() { 33 | for { 34 | buf := pool.Get(pool.RelayBufferSize) 35 | n, remoteAddr, err := l.ReadFrom(buf) 36 | if err != nil { 37 | pool.Put(buf) 38 | if sl.closed { 39 | break 40 | } 41 | continue 42 | } 43 | handleSocksUDP(l, buf[:n], remoteAddr) 44 | } 45 | }() 46 | 47 | return sl, nil 48 | } 49 | 50 | func (l *SockUDPListener) Close() error { 51 | l.closed = true 52 | return l.PacketConn.Close() 53 | } 54 | 55 | func (l *SockUDPListener) Address() string { 56 | return l.address 57 | } 58 | 59 | func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { 60 | target, payload, err := socks5.DecodeUDPPacket(buf) 61 | if err != nil { 62 | // Unresolved UDP packet, return buffer to the pool 63 | pool.Put(buf) 64 | return 65 | } 66 | packet := &packet{ 67 | pc: pc, 68 | rAddr: addr, 69 | payload: payload, 70 | bufRef: buf, 71 | } 72 | tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) 73 | } 74 | -------------------------------------------------------------------------------- /proxy/socks/utils.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | "github.com/Dreamacro/clash/component/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 wirtes 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 | return 38 | } 39 | -------------------------------------------------------------------------------- /rules/base.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | errPayload = errors.New("payload error") 9 | errParams = errors.New("params error") 10 | 11 | noResolve = "no-resolve" 12 | ) 13 | 14 | func HasNoResolve(params []string) bool { 15 | for _, p := range params { 16 | if p == noResolve { 17 | return true 18 | } 19 | } 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /rules/domain.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Domain struct { 10 | domain string 11 | adapter string 12 | } 13 | 14 | func (d *Domain) RuleType() C.RuleType { 15 | return C.Domain 16 | } 17 | 18 | func (d *Domain) Match(metadata *C.Metadata) bool { 19 | if metadata.AddrType != C.AtypDomainName { 20 | return false 21 | } 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) NoResolveIP() bool { 34 | return true 35 | } 36 | 37 | func NewDomain(domain string, adapter string) *Domain { 38 | return &Domain{ 39 | domain: strings.ToLower(domain), 40 | adapter: adapter, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rules/domain_keyword.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainKeyword struct { 10 | keyword string 11 | adapter string 12 | } 13 | 14 | func (dk *DomainKeyword) RuleType() C.RuleType { 15 | return C.DomainKeyword 16 | } 17 | 18 | func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { 19 | if metadata.AddrType != C.AtypDomainName { 20 | return false 21 | } 22 | domain := metadata.Host 23 | return strings.Contains(domain, dk.keyword) 24 | } 25 | 26 | func (dk *DomainKeyword) Adapter() string { 27 | return dk.adapter 28 | } 29 | 30 | func (dk *DomainKeyword) Payload() string { 31 | return dk.keyword 32 | } 33 | 34 | func (dk *DomainKeyword) NoResolveIP() bool { 35 | return true 36 | } 37 | 38 | func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { 39 | return &DomainKeyword{ 40 | keyword: strings.ToLower(keyword), 41 | adapter: adapter, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rules/domain_suffix.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainSuffix struct { 10 | suffix string 11 | adapter string 12 | } 13 | 14 | func (ds *DomainSuffix) RuleType() C.RuleType { 15 | return C.DomainSuffix 16 | } 17 | 18 | func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { 19 | if metadata.AddrType != C.AtypDomainName { 20 | return false 21 | } 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) NoResolveIP() bool { 35 | return true 36 | } 37 | 38 | func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { 39 | return &DomainSuffix{ 40 | suffix: strings.ToLower(suffix), 41 | adapter: adapter, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rules/final.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | type Match struct { 8 | adapter string 9 | } 10 | 11 | func (f *Match) RuleType() C.RuleType { 12 | return C.MATCH 13 | } 14 | 15 | func (f *Match) Match(metadata *C.Metadata) bool { 16 | return true 17 | } 18 | 19 | func (f *Match) Adapter() string { 20 | return f.adapter 21 | } 22 | 23 | func (f *Match) Payload() string { 24 | return "" 25 | } 26 | 27 | func (f *Match) NoResolveIP() bool { 28 | return true 29 | } 30 | 31 | func NewMatch(adapter string) *Match { 32 | return &Match{ 33 | adapter: adapter, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rules/geoip.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/mmdb" 5 | C "github.com/Dreamacro/clash/constant" 6 | ) 7 | 8 | type GEOIP struct { 9 | country string 10 | adapter string 11 | noResolveIP bool 12 | } 13 | 14 | func (g *GEOIP) RuleType() C.RuleType { 15 | return C.GEOIP 16 | } 17 | 18 | func (g *GEOIP) Match(metadata *C.Metadata) bool { 19 | ip := metadata.DstIP 20 | if ip == nil { 21 | return false 22 | } 23 | record, _ := mmdb.Instance().Country(ip) 24 | return record.Country.IsoCode == g.country 25 | } 26 | 27 | func (g *GEOIP) Adapter() string { 28 | return g.adapter 29 | } 30 | 31 | func (g *GEOIP) Payload() string { 32 | return g.country 33 | } 34 | 35 | func (g *GEOIP) NoResolveIP() bool { 36 | return g.noResolveIP 37 | } 38 | 39 | func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { 40 | geoip := &GEOIP{ 41 | country: country, 42 | adapter: adapter, 43 | noResolveIP: noResolveIP, 44 | } 45 | 46 | return geoip 47 | } 48 | -------------------------------------------------------------------------------- /rules/ipcidr.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type IPCIDROption func(*IPCIDR) 10 | 11 | func WithIPCIDRSourceIP(b bool) IPCIDROption { 12 | return func(i *IPCIDR) { 13 | i.isSourceIP = b 14 | } 15 | } 16 | 17 | func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { 18 | return func(i *IPCIDR) { 19 | i.noResolveIP = noResolve 20 | } 21 | } 22 | 23 | type IPCIDR struct { 24 | ipnet *net.IPNet 25 | adapter string 26 | isSourceIP bool 27 | noResolveIP bool 28 | } 29 | 30 | func (i *IPCIDR) RuleType() C.RuleType { 31 | if i.isSourceIP { 32 | return C.SrcIPCIDR 33 | } 34 | return C.IPCIDR 35 | } 36 | 37 | func (i *IPCIDR) Match(metadata *C.Metadata) bool { 38 | ip := metadata.DstIP 39 | if i.isSourceIP { 40 | ip = metadata.SrcIP 41 | } 42 | return ip != nil && i.ipnet.Contains(ip) 43 | } 44 | 45 | func (i *IPCIDR) Adapter() string { 46 | return i.adapter 47 | } 48 | 49 | func (i *IPCIDR) Payload() string { 50 | return i.ipnet.String() 51 | } 52 | 53 | func (i *IPCIDR) NoResolveIP() bool { 54 | return i.noResolveIP 55 | } 56 | 57 | func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { 58 | _, ipnet, err := net.ParseCIDR(s) 59 | if err != nil { 60 | return nil, errPayload 61 | } 62 | 63 | ipcidr := &IPCIDR{ 64 | ipnet: ipnet, 65 | adapter: adapter, 66 | } 67 | 68 | for _, o := range opts { 69 | o(ipcidr) 70 | } 71 | 72 | return ipcidr, nil 73 | } 74 | -------------------------------------------------------------------------------- /rules/parser.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { 10 | var ( 11 | parseErr error 12 | parsed C.Rule 13 | ) 14 | 15 | switch tp { 16 | case "DOMAIN": 17 | parsed = NewDomain(payload, target) 18 | case "DOMAIN-SUFFIX": 19 | parsed = NewDomainSuffix(payload, target) 20 | case "DOMAIN-KEYWORD": 21 | parsed = NewDomainKeyword(payload, target) 22 | case "GEOIP": 23 | noResolve := HasNoResolve(params) 24 | parsed = NewGEOIP(payload, target, noResolve) 25 | case "IP-CIDR", "IP-CIDR6": 26 | noResolve := HasNoResolve(params) 27 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) 28 | case "SRC-IP-CIDR": 29 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) 30 | case "SRC-PORT": 31 | parsed, parseErr = NewPort(payload, target, true) 32 | case "DST-PORT": 33 | parsed, parseErr = NewPort(payload, target, false) 34 | case "PROCESS-NAME": 35 | parsed, parseErr = NewProcess(payload, target) 36 | case "MATCH": 37 | parsed = NewMatch(target) 38 | default: 39 | parseErr = fmt.Errorf("unsupported rule type %s", tp) 40 | } 41 | 42 | return parsed, parseErr 43 | } 44 | -------------------------------------------------------------------------------- /rules/port.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strconv" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Port struct { 10 | adapter string 11 | port string 12 | isSource bool 13 | } 14 | 15 | func (p *Port) RuleType() C.RuleType { 16 | if p.isSource { 17 | return C.SrcPort 18 | } 19 | return C.DstPort 20 | } 21 | 22 | func (p *Port) Match(metadata *C.Metadata) bool { 23 | if p.isSource { 24 | return metadata.SrcPort == p.port 25 | } 26 | return metadata.DstPort == p.port 27 | } 28 | 29 | func (p *Port) Adapter() string { 30 | return p.adapter 31 | } 32 | 33 | func (p *Port) Payload() string { 34 | return p.port 35 | } 36 | 37 | func (p *Port) NoResolveIP() bool { 38 | return true 39 | } 40 | 41 | func NewPort(port string, adapter string, isSource bool) (*Port, error) { 42 | _, err := strconv.Atoi(port) 43 | if err != nil { 44 | return nil, errPayload 45 | } 46 | return &Port{ 47 | adapter: adapter, 48 | port: port, 49 | isSource: isSource, 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /rules/process.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/common/cache" 9 | "github.com/Dreamacro/clash/component/process" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | ) 13 | 14 | var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) 15 | 16 | type Process struct { 17 | adapter string 18 | process string 19 | } 20 | 21 | func (ps *Process) RuleType() C.RuleType { 22 | return C.Process 23 | } 24 | 25 | func (ps *Process) Match(metadata *C.Metadata) bool { 26 | key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) 27 | cached, hit := processCache.Get(key) 28 | if !hit { 29 | srcPort, err := strconv.Atoi(metadata.SrcPort) 30 | if err != nil { 31 | processCache.Set(key, "") 32 | return false 33 | } 34 | 35 | name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) 36 | if err != nil { 37 | log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error()) 38 | } 39 | 40 | processCache.Set(key, name) 41 | 42 | cached = name 43 | } 44 | 45 | return strings.EqualFold(cached.(string), ps.process) 46 | } 47 | 48 | func (p *Process) Adapter() string { 49 | return p.adapter 50 | } 51 | 52 | func (p *Process) Payload() string { 53 | return p.process 54 | } 55 | 56 | func (p *Process) NoResolveIP() bool { 57 | return true 58 | } 59 | 60 | func (p *Process) ShouldResolveIP() bool { 61 | return false 62 | } 63 | 64 | func NewProcess(process string, adapter string) (*Process, error) { 65 | return &Process{ 66 | adapter: adapter, 67 | process: process, 68 | }, nil 69 | } 70 | -------------------------------------------------------------------------------- /tunnel/manager.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | var DefaultManager *Manager 9 | 10 | func init() { 11 | DefaultManager = &Manager{ 12 | upload: make(chan int64), 13 | download: make(chan int64), 14 | } 15 | DefaultManager.handle() 16 | } 17 | 18 | type Manager struct { 19 | connections sync.Map 20 | upload chan int64 21 | download chan int64 22 | uploadTemp int64 23 | downloadTemp int64 24 | uploadBlip int64 25 | downloadBlip int64 26 | uploadTotal int64 27 | downloadTotal int64 28 | } 29 | 30 | func (m *Manager) Join(c tracker) { 31 | m.connections.Store(c.ID(), c) 32 | } 33 | 34 | func (m *Manager) Leave(c tracker) { 35 | m.connections.Delete(c.ID()) 36 | } 37 | 38 | func (m *Manager) Upload() chan<- int64 { 39 | return m.upload 40 | } 41 | 42 | func (m *Manager) Download() chan<- int64 { 43 | return m.download 44 | } 45 | 46 | func (m *Manager) Now() (up int64, down int64) { 47 | return m.uploadBlip, m.downloadBlip 48 | } 49 | 50 | func (m *Manager) Snapshot() *Snapshot { 51 | connections := []tracker{} 52 | m.connections.Range(func(key, value interface{}) bool { 53 | connections = append(connections, value.(tracker)) 54 | return true 55 | }) 56 | 57 | return &Snapshot{ 58 | UploadTotal: m.uploadTotal, 59 | DownloadTotal: m.downloadTotal, 60 | Connections: connections, 61 | } 62 | } 63 | 64 | func (m *Manager) ResetStatistic() { 65 | m.uploadTemp = 0 66 | m.uploadBlip = 0 67 | m.uploadTotal = 0 68 | m.downloadTemp = 0 69 | m.downloadBlip = 0 70 | m.downloadTotal = 0 71 | } 72 | 73 | func (m *Manager) UploadTotal() int64 { 74 | return m.uploadTotal 75 | } 76 | 77 | func (m *Manager) DownloadTotal() int64 { 78 | return m.downloadTotal 79 | } 80 | 81 | func (m *Manager) handle() { 82 | go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) 83 | go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) 84 | } 85 | 86 | func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) { 87 | ticker := time.NewTicker(time.Second) 88 | for { 89 | select { 90 | case n := <-ch: 91 | *temp += n 92 | *total += n 93 | case <-ticker.C: 94 | *blip = *temp 95 | *temp = 0 96 | } 97 | } 98 | } 99 | 100 | type Snapshot struct { 101 | DownloadTotal int64 `json:"downloadTotal"` 102 | UploadTotal int64 `json:"uploadTotal"` 103 | Connections []tracker `json:"connections"` 104 | } 105 | -------------------------------------------------------------------------------- /tunnel/mode.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | type TunnelMode int 10 | 11 | var ( 12 | // ModeMapping is a mapping for Mode enum 13 | ModeMapping = map[string]TunnelMode{ 14 | Global.String(): Global, 15 | Rule.String(): Rule, 16 | Direct.String(): Direct, 17 | } 18 | ) 19 | 20 | const ( 21 | Global TunnelMode = iota 22 | Rule 23 | Direct 24 | ) 25 | 26 | // UnmarshalJSON unserialize Mode 27 | func (m *TunnelMode) UnmarshalJSON(data []byte) error { 28 | var tp string 29 | json.Unmarshal(data, &tp) 30 | mode, exist := ModeMapping[strings.ToLower(tp)] 31 | if !exist { 32 | return errors.New("invalid mode") 33 | } 34 | *m = mode 35 | return nil 36 | } 37 | 38 | // UnmarshalYAML unserialize Mode with yaml 39 | func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error { 40 | var tp string 41 | unmarshal(&tp) 42 | mode, exist := ModeMapping[strings.ToLower(tp)] 43 | if !exist { 44 | return errors.New("invalid mode") 45 | } 46 | *m = mode 47 | return nil 48 | } 49 | 50 | // MarshalJSON serialize Mode 51 | func (m TunnelMode) MarshalJSON() ([]byte, error) { 52 | return json.Marshal(m.String()) 53 | } 54 | 55 | // MarshalYAML serialize TunnelMode with yaml 56 | func (m TunnelMode) MarshalYAML() (interface{}, error) { 57 | return m.String(), nil 58 | } 59 | 60 | func (m TunnelMode) String() string { 61 | switch m { 62 | case Global: 63 | return "global" 64 | case Rule: 65 | return "rule" 66 | case Direct: 67 | return "direct" 68 | default: 69 | return "Unknown" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tunnel/tracker.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/gofrs/uuid" 9 | ) 10 | 11 | type tracker interface { 12 | ID() string 13 | Close() error 14 | Chain() []string 15 | } 16 | 17 | type trackerInfo struct { 18 | UUID uuid.UUID `json:"id"` 19 | Metadata *C.Metadata `json:"metadata"` 20 | UploadTotal int64 `json:"upload"` 21 | DownloadTotal int64 `json:"download"` 22 | Start time.Time `json:"start"` 23 | Chain C.Chain `json:"chains"` 24 | Rule string `json:"rule"` 25 | RulePayload string `json:"rulePayload"` 26 | } 27 | 28 | type tcpTracker struct { 29 | C.Conn `json:"-"` 30 | *trackerInfo 31 | manager *Manager 32 | } 33 | 34 | func (tt *tcpTracker) Chain() []string { 35 | return tt.trackerInfo.Chain 36 | } 37 | 38 | func (tt *tcpTracker) ID() string { 39 | return tt.UUID.String() 40 | } 41 | 42 | func (tt *tcpTracker) Read(b []byte) (int, error) { 43 | n, err := tt.Conn.Read(b) 44 | download := int64(n) 45 | tt.manager.Download() <- download 46 | tt.DownloadTotal += download 47 | return n, err 48 | } 49 | 50 | func (tt *tcpTracker) Write(b []byte) (int, error) { 51 | n, err := tt.Conn.Write(b) 52 | upload := int64(n) 53 | tt.manager.Upload() <- upload 54 | tt.UploadTotal += upload 55 | return n, err 56 | } 57 | 58 | func (tt *tcpTracker) Close() error { 59 | tt.manager.Leave(tt) 60 | return tt.Conn.Close() 61 | } 62 | 63 | func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { 64 | uuid, _ := uuid.NewV4() 65 | 66 | t := &tcpTracker{ 67 | Conn: conn, 68 | manager: manager, 69 | trackerInfo: &trackerInfo{ 70 | UUID: uuid, 71 | Start: time.Now(), 72 | Metadata: metadata, 73 | Chain: conn.Chains(), 74 | Rule: "", 75 | }, 76 | } 77 | 78 | if rule != nil { 79 | t.trackerInfo.Rule = rule.RuleType().String() 80 | t.trackerInfo.RulePayload = rule.Payload() 81 | } 82 | 83 | manager.Join(t) 84 | return t 85 | } 86 | 87 | type udpTracker struct { 88 | C.PacketConn `json:"-"` 89 | *trackerInfo 90 | manager *Manager 91 | } 92 | 93 | func (ut *udpTracker) Chain() []string { 94 | return ut.trackerInfo.Chain 95 | } 96 | 97 | func (ut *udpTracker) ID() string { 98 | return ut.UUID.String() 99 | } 100 | 101 | func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { 102 | n, addr, err := ut.PacketConn.ReadFrom(b) 103 | download := int64(n) 104 | ut.manager.Download() <- download 105 | ut.DownloadTotal += download 106 | return n, addr, err 107 | } 108 | 109 | func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { 110 | n, err := ut.PacketConn.WriteTo(b, addr) 111 | upload := int64(n) 112 | ut.manager.Upload() <- upload 113 | ut.UploadTotal += upload 114 | return n, err 115 | } 116 | 117 | func (ut *udpTracker) Close() error { 118 | ut.manager.Leave(ut) 119 | return ut.PacketConn.Close() 120 | } 121 | 122 | func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { 123 | uuid, _ := uuid.NewV4() 124 | 125 | ut := &udpTracker{ 126 | PacketConn: conn, 127 | manager: manager, 128 | trackerInfo: &trackerInfo{ 129 | UUID: uuid, 130 | Start: time.Now(), 131 | Metadata: metadata, 132 | Chain: conn.Chains(), 133 | Rule: "", 134 | }, 135 | } 136 | 137 | if rule != nil { 138 | ut.trackerInfo.Rule = rule.RuleType().String() 139 | ut.trackerInfo.RulePayload = rule.Payload() 140 | } 141 | 142 | manager.Join(ut) 143 | return ut 144 | } 145 | --------------------------------------------------------------------------------