├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── go.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.arm32v7 ├── Dockerfile.arm64v8 ├── 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 │ ├── snell.go │ ├── socks5.go │ ├── util.go │ └── vmess.go ├── outboundgroup │ ├── common.go │ ├── fallback.go │ ├── loadbalance.go │ ├── parser.go │ ├── selector.go │ └── urltest.go └── provider │ ├── 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 │ └── pool.go ├── queue │ └── queue.go ├── singledo │ ├── singledo.go │ └── singledo_test.go └── structure │ ├── structure.go │ └── structure_test.go ├── component ├── auth │ └── auth.go ├── dialer │ ├── dialer.go │ └── hook.go ├── domain-trie │ ├── node.go │ ├── tire.go │ └── trie_test.go ├── fakeip │ ├── pool.go │ └── pool_test.go ├── mmdb │ └── mmdb.go ├── nat │ └── table.go ├── resolver │ └── resolver.go ├── simple-obfs │ ├── http.go │ └── tls.go ├── snell │ ├── cipher.go │ └── snell.go ├── socks5 │ └── socks5.go ├── v2ray-plugin │ ├── mux.go │ └── websocket.go └── vmess │ ├── aead.go │ ├── chunk.go │ ├── conn.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 ├── hooks └── pre_build ├── 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 ├── redir │ ├── tcp.go │ ├── tcp_darwin.go │ ├── tcp_freebsd.go │ ├── tcp_linux.go │ ├── tcp_linux_386.go │ ├── tcp_linux_other.go │ └── tcp_windows.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 └── port.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/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@v1 11 | with: 12 | go-version: 1.14.x 13 | 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v1 16 | 17 | - name: Cache go module 18 | uses: actions/cache@v1 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 | -------------------------------------------------------------------------------- /Dockerfile.arm32v7: -------------------------------------------------------------------------------- 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 | wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static && \ 6 | chmod +x /qemu-arm-static 7 | 8 | WORKDIR /clash-src 9 | COPY . /clash-src 10 | RUN go mod download && \ 11 | make linux-armv7 && \ 12 | mv ./bin/clash-linux-armv7 /clash 13 | 14 | FROM arm32v7/alpine:latest 15 | 16 | COPY --from=builder /qemu-arm-static /usr/bin/ 17 | COPY --from=builder /Country.mmdb /root/.config/clash/ 18 | COPY --from=builder /clash / 19 | RUN apk add --no-cache ca-certificates 20 | ENTRYPOINT ["/clash"] 21 | -------------------------------------------------------------------------------- /Dockerfile.arm64v8: -------------------------------------------------------------------------------- 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 | wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static && \ 6 | chmod +x /qemu-aarch64-static 7 | 8 | WORKDIR /clash-src 9 | COPY . /clash-src 10 | RUN go mod download && \ 11 | make linux-armv8 && \ 12 | mv ./bin/clash-linux-armv8 /clash 13 | 14 | FROM arm64v8/alpine:latest 15 | 16 | COPY --from=builder /qemu-aarch64-static /usr/bin/ 17 | COPY --from=builder /Country.mmdb /root/.config/clash/ 18 | COPY --from=builder /clash / 19 | RUN apk add --no-cache ca-certificates 20 | ENTRYPOINT ["/clash"] 21 | -------------------------------------------------------------------------------- /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 -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, netType C.NetWork) *SocketAdapter { 23 | metadata := parseSocksAddr(target) 24 | metadata.NetWork = netType 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 | 8 | "github.com/Dreamacro/clash/component/socks5" 9 | C "github.com/Dreamacro/clash/constant" 10 | ) 11 | 12 | func parseSocksAddr(target socks5.Addr) *C.Metadata { 13 | metadata := &C.Metadata{ 14 | AddrType: int(target[0]), 15 | } 16 | 17 | switch target[0] { 18 | case socks5.AtypDomainName: 19 | metadata.Host = string(target[2 : 2+target[1]]) 20 | metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) 21 | case socks5.AtypIPv4: 22 | ip := net.IP(target[1 : 1+net.IPv4len]) 23 | metadata.DstIP = ip 24 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) 25 | case socks5.AtypIPv6: 26 | ip := net.IP(target[1 : 1+net.IPv6len]) 27 | metadata.DstIP = ip 28 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) 29 | } 30 | 31 | return metadata 32 | } 33 | 34 | func parseHTTPAddr(request *http.Request) *C.Metadata { 35 | host := request.URL.Hostname() 36 | port := request.URL.Port() 37 | if port == "" { 38 | port = "80" 39 | } 40 | 41 | metadata := &C.Metadata{ 42 | NetWork: C.TCP, 43 | AddrType: C.AtypDomainName, 44 | Host: host, 45 | DstIP: nil, 46 | DstPort: port, 47 | } 48 | 49 | ip := net.ParseIP(host) 50 | if ip != nil { 51 | switch { 52 | case ip.To4() == nil: 53 | metadata.AddrType = C.AtypIPv6 54 | default: 55 | metadata.AddrType = C.AtypIPv4 56 | } 57 | metadata.DstIP = ip 58 | } 59 | 60 | return metadata 61 | } 62 | 63 | func parseAddr(addr string) (net.IP, string, error) { 64 | host, port, err := net.SplitHostPort(addr) 65 | if err != nil { 66 | return nil, "", err 67 | } 68 | 69 | ip := net.ParseIP(host) 70 | return ip, port, nil 71 | } 72 | -------------------------------------------------------------------------------- /adapters/outbound/direct.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/component/dialer" 8 | "github.com/Dreamacro/clash/component/resolver" 9 | C "github.com/Dreamacro/clash/constant" 10 | ) 11 | 12 | type Direct struct { 13 | *Base 14 | } 15 | 16 | func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 17 | address := net.JoinHostPort(metadata.Host, metadata.DstPort) 18 | if metadata.DstIP != nil { 19 | address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) 20 | } 21 | 22 | c, err := dialer.DialContext(ctx, "tcp", address) 23 | if err != nil { 24 | return nil, err 25 | } 26 | tcpKeepAlive(c) 27 | return newConn(c, d), nil 28 | } 29 | 30 | func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 31 | pc, err := dialer.ListenPacket("udp", "") 32 | if err != nil { 33 | return nil, err 34 | } 35 | return newPacketConn(&directPacketConn{pc}, d), nil 36 | } 37 | 38 | type directPacketConn struct { 39 | net.PacketConn 40 | } 41 | 42 | func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { 43 | if !metadata.Resolved() { 44 | ip, err := resolver.ResolveIP(metadata.Host) 45 | if err != nil { 46 | return 0, err 47 | } 48 | metadata.DstIP = ip 49 | } 50 | return dp.WriteTo(p, metadata.UDPAddr()) 51 | } 52 | 53 | func NewDirect() *Direct { 54 | return &Direct{ 55 | Base: &Base{ 56 | name: "DIRECT", 57 | tp: C.Direct, 58 | udp: true, 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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 | addr string 23 | user string 24 | pass string 25 | tlsConfig *tls.Config 26 | } 27 | 28 | type HttpOption struct { 29 | Name string `proxy:"name"` 30 | Server string `proxy:"server"` 31 | Port int `proxy:"port"` 32 | UserName string `proxy:"username,omitempty"` 33 | Password string `proxy:"password,omitempty"` 34 | TLS bool `proxy:"tls,omitempty"` 35 | SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 36 | } 37 | 38 | func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 39 | c, err := dialer.DialContext(ctx, "tcp", h.addr) 40 | if err == nil && h.tlsConfig != nil { 41 | cc := tls.Client(c, h.tlsConfig) 42 | err = cc.Handshake() 43 | c = cc 44 | } 45 | 46 | if err != nil { 47 | return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 48 | } 49 | tcpKeepAlive(c) 50 | if err := h.shakeHand(metadata, c); err != nil { 51 | return nil, err 52 | } 53 | 54 | return newConn(c, h), nil 55 | } 56 | 57 | func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 58 | addr := metadata.RemoteAddress() 59 | req := &http.Request{ 60 | Method: http.MethodConnect, 61 | URL: &url.URL{ 62 | Host: addr, 63 | }, 64 | Host: addr, 65 | Header: http.Header{ 66 | "Proxy-Connection": []string{"Keep-Alive"}, 67 | }, 68 | } 69 | 70 | if h.user != "" && h.pass != "" { 71 | auth := h.user + ":" + h.pass 72 | req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 73 | } 74 | 75 | if err := req.Write(rw); err != nil { 76 | return err 77 | } 78 | 79 | resp, err := http.ReadResponse(bufio.NewReader(rw), req) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | if resp.StatusCode == http.StatusOK { 85 | return nil 86 | } 87 | 88 | if resp.StatusCode == http.StatusProxyAuthRequired { 89 | return errors.New("HTTP need auth") 90 | } 91 | 92 | if resp.StatusCode == http.StatusMethodNotAllowed { 93 | return errors.New("CONNECT method not allowed by proxy") 94 | } 95 | 96 | if resp.StatusCode >= http.StatusInternalServerError { 97 | return errors.New(resp.Status) 98 | } 99 | 100 | return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 101 | } 102 | 103 | func NewHttp(option HttpOption) *Http { 104 | var tlsConfig *tls.Config 105 | if option.TLS { 106 | tlsConfig = &tls.Config{ 107 | InsecureSkipVerify: option.SkipCertVerify, 108 | ClientSessionCache: getClientSessionCache(), 109 | ServerName: option.Server, 110 | } 111 | } 112 | 113 | return &Http{ 114 | Base: &Base{ 115 | name: option.Name, 116 | tp: C.Http, 117 | }, 118 | addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 119 | user: option.UserName, 120 | pass: option.Password, 121 | tlsConfig: tlsConfig, 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /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 "socks5": 28 | socksOption := &Socks5Option{} 29 | err = decoder.Decode(mapping, socksOption) 30 | if err != nil { 31 | break 32 | } 33 | proxy = NewSocks5(*socksOption) 34 | case "http": 35 | httpOption := &HttpOption{} 36 | err = decoder.Decode(mapping, httpOption) 37 | if err != nil { 38 | break 39 | } 40 | proxy = NewHttp(*httpOption) 41 | case "vmess": 42 | vmessOption := &VmessOption{} 43 | err = decoder.Decode(mapping, vmessOption) 44 | if err != nil { 45 | break 46 | } 47 | proxy, err = NewVmess(*vmessOption) 48 | case "snell": 49 | snellOption := &SnellOption{} 50 | err = decoder.Decode(mapping, snellOption) 51 | if err != nil { 52 | break 53 | } 54 | proxy, err = NewSnell(*snellOption) 55 | default: 56 | return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) 57 | } 58 | 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return NewProxy(proxy), nil 64 | } 65 | -------------------------------------------------------------------------------- /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 | server string 19 | psk []byte 20 | obfsOption *simpleObfsOption 21 | } 22 | 23 | type SnellOption struct { 24 | Name string `proxy:"name"` 25 | Server string `proxy:"server"` 26 | Port int `proxy:"port"` 27 | Psk string `proxy:"psk"` 28 | ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` 29 | } 30 | 31 | func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 32 | c, err := dialer.DialContext(ctx, "tcp", s.server) 33 | if err != nil { 34 | return nil, fmt.Errorf("%s connect error: %w", s.server, err) 35 | } 36 | tcpKeepAlive(c) 37 | switch s.obfsOption.Mode { 38 | case "tls": 39 | c = obfs.NewTLSObfs(c, s.obfsOption.Host) 40 | case "http": 41 | _, port, _ := net.SplitHostPort(s.server) 42 | c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) 43 | } 44 | c = snell.StreamConn(c, s.psk) 45 | port, _ := strconv.Atoi(metadata.DstPort) 46 | err = snell.WriteHeader(c, metadata.String(), uint(port)) 47 | return newConn(c, s), err 48 | } 49 | 50 | func NewSnell(option SnellOption) (*Snell, error) { 51 | server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 52 | psk := []byte(option.Psk) 53 | 54 | decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) 55 | obfsOption := &simpleObfsOption{Host: "bing.com"} 56 | if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { 57 | return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err) 58 | } 59 | 60 | if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { 61 | return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode) 62 | } 63 | 64 | return &Snell{ 65 | Base: &Base{ 66 | name: option.Name, 67 | tp: C.Snell, 68 | }, 69 | server: server, 70 | psk: psk, 71 | obfsOption: obfsOption, 72 | }, nil 73 | } 74 | -------------------------------------------------------------------------------- /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 | if tcp, ok := c.(*net.TCPConn); ok { 57 | tcp.SetKeepAlive(true) 58 | tcp.SetKeepAlivePeriod(30 * time.Second) 59 | } 60 | } 61 | 62 | func getClientSessionCache() tls.ClientSessionCache { 63 | once.Do(func() { 64 | globalClientSessionCache = tls.NewLRUClientSessionCache(128) 65 | }) 66 | return globalClientSessionCache 67 | } 68 | 69 | func serializesSocksAddr(metadata *C.Metadata) []byte { 70 | var buf [][]byte 71 | aType := uint8(metadata.AddrType) 72 | p, _ := strconv.Atoi(metadata.DstPort) 73 | port := []byte{uint8(p >> 8), uint8(p & 0xff)} 74 | switch metadata.AddrType { 75 | case socks5.AtypDomainName: 76 | len := uint8(len(metadata.Host)) 77 | host := []byte(metadata.Host) 78 | buf = [][]byte{{aType, len}, host, port} 79 | case socks5.AtypIPv4: 80 | host := metadata.DstIP.To4() 81 | buf = [][]byte{{aType}, host, port} 82 | case socks5.AtypIPv6: 83 | host := metadata.DstIP.To16() 84 | buf = [][]byte{{aType}, host, port} 85 | } 86 | return bytes.Join(buf, nil) 87 | } 88 | 89 | func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { 90 | host, port, err := net.SplitHostPort(address) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | ip, err := resolver.ResolveIP(host) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 100 | } 101 | -------------------------------------------------------------------------------- /adapters/outbound/vmess.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/Dreamacro/clash/component/dialer" 12 | "github.com/Dreamacro/clash/component/resolver" 13 | "github.com/Dreamacro/clash/component/vmess" 14 | C "github.com/Dreamacro/clash/constant" 15 | ) 16 | 17 | type Vmess struct { 18 | *Base 19 | server string 20 | client *vmess.Client 21 | } 22 | 23 | type VmessOption struct { 24 | Name string `proxy:"name"` 25 | Server string `proxy:"server"` 26 | Port int `proxy:"port"` 27 | UUID string `proxy:"uuid"` 28 | AlterID int `proxy:"alterId"` 29 | Cipher string `proxy:"cipher"` 30 | TLS bool `proxy:"tls,omitempty"` 31 | UDP bool `proxy:"udp,omitempty"` 32 | Network string `proxy:"network,omitempty"` 33 | WSPath string `proxy:"ws-path,omitempty"` 34 | WSHeaders map[string]string `proxy:"ws-headers,omitempty"` 35 | SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 36 | } 37 | 38 | func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 39 | c, err := dialer.DialContext(ctx, "tcp", v.server) 40 | if err != nil { 41 | return nil, fmt.Errorf("%s connect error", v.server) 42 | } 43 | tcpKeepAlive(c) 44 | c, err = v.client.New(c, parseVmessAddr(metadata)) 45 | return newConn(c, v), err 46 | } 47 | 48 | func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 49 | // vmess use stream-oriented udp, so clash needs a net.UDPAddr 50 | if !metadata.Resolved() { 51 | ip, err := resolver.ResolveIP(metadata.Host) 52 | if err != nil { 53 | return nil, errors.New("can't resolve ip") 54 | } 55 | metadata.DstIP = ip 56 | } 57 | 58 | ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) 59 | defer cancel() 60 | c, err := dialer.DialContext(ctx, "tcp", v.server) 61 | if err != nil { 62 | return nil, fmt.Errorf("%s connect error", v.server) 63 | } 64 | tcpKeepAlive(c) 65 | c, err = v.client.New(c, parseVmessAddr(metadata)) 66 | if err != nil { 67 | return nil, fmt.Errorf("new vmess client error: %v", err) 68 | } 69 | return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil 70 | } 71 | 72 | func NewVmess(option VmessOption) (*Vmess, error) { 73 | security := strings.ToLower(option.Cipher) 74 | client, err := vmess.NewClient(vmess.Config{ 75 | UUID: option.UUID, 76 | AlterID: uint16(option.AlterID), 77 | Security: security, 78 | TLS: option.TLS, 79 | HostName: option.Server, 80 | Port: strconv.Itoa(option.Port), 81 | NetWork: option.Network, 82 | WebSocketPath: option.WSPath, 83 | WebSocketHeaders: option.WSHeaders, 84 | SkipCertVerify: option.SkipCertVerify, 85 | SessionCache: getClientSessionCache(), 86 | }) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return &Vmess{ 92 | Base: &Base{ 93 | name: option.Name, 94 | tp: C.Vmess, 95 | udp: true, 96 | }, 97 | server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 98 | client: client, 99 | }, nil 100 | } 101 | 102 | func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { 103 | var addrType byte 104 | var addr []byte 105 | switch metadata.AddrType { 106 | case C.AtypIPv4: 107 | addrType = byte(vmess.AtypIPv4) 108 | addr = make([]byte, net.IPv4len) 109 | copy(addr[:], metadata.DstIP.To4()) 110 | case C.AtypIPv6: 111 | addrType = byte(vmess.AtypIPv6) 112 | addr = make([]byte, net.IPv6len) 113 | copy(addr[:], metadata.DstIP.To16()) 114 | case C.AtypDomainName: 115 | addrType = byte(vmess.AtypDomainName) 116 | addr = make([]byte, len(metadata.Host)+1) 117 | addr[0] = byte(len(metadata.Host)) 118 | copy(addr[1:], []byte(metadata.Host)) 119 | } 120 | 121 | port, _ := strconv.Atoi(metadata.DstPort) 122 | return &vmess.DstAddr{ 123 | UDP: metadata.NetWork == C.UDP, 124 | AddrType: addrType, 125 | Addr: addr, 126 | Port: uint(port), 127 | } 128 | } 129 | 130 | type vmessPacketConn struct { 131 | net.Conn 132 | rAddr net.Addr 133 | } 134 | 135 | func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 136 | return uc.Conn.Write(b) 137 | } 138 | 139 | func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { 140 | return uc.Conn.Write(p) 141 | } 142 | 143 | func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 144 | n, err := uc.Conn.Read(b) 145 | return n, uc.rAddr, err 146 | } 147 | -------------------------------------------------------------------------------- /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 | func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy { 15 | proxies := []C.Proxy{} 16 | for _, provider := range providers { 17 | proxies = append(proxies, provider.Proxies()...) 18 | } 19 | return proxies 20 | } 21 | -------------------------------------------------------------------------------- /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) Now() string { 20 | proxy := f.findAliveProxy() 21 | return proxy.Name() 22 | } 23 | 24 | func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 25 | proxy := f.findAliveProxy() 26 | c, err := proxy.DialContext(ctx, metadata) 27 | if err == nil { 28 | c.AppendToChains(f) 29 | } 30 | return c, err 31 | } 32 | 33 | func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 34 | proxy := f.findAliveProxy() 35 | pc, err := proxy.DialUDP(metadata) 36 | if err == nil { 37 | pc.AppendToChains(f) 38 | } 39 | return pc, err 40 | } 41 | 42 | func (f *Fallback) SupportUDP() bool { 43 | proxy := f.findAliveProxy() 44 | return proxy.SupportUDP() 45 | } 46 | 47 | func (f *Fallback) MarshalJSON() ([]byte, error) { 48 | var all []string 49 | for _, proxy := range f.proxies() { 50 | all = append(all, proxy.Name()) 51 | } 52 | return json.Marshal(map[string]interface{}{ 53 | "type": f.Type().String(), 54 | "now": f.Now(), 55 | "all": all, 56 | }) 57 | } 58 | 59 | func (f *Fallback) proxies() []C.Proxy { 60 | elm, _, _ := f.single.Do(func() (interface{}, error) { 61 | return getProvidersProxies(f.providers), nil 62 | }) 63 | 64 | return elm.([]C.Proxy) 65 | } 66 | 67 | func (f *Fallback) findAliveProxy() C.Proxy { 68 | proxies := f.proxies() 69 | for _, proxy := range proxies { 70 | if proxy.Alive() { 71 | return proxy 72 | } 73 | } 74 | 75 | return f.proxies()[0] 76 | } 77 | 78 | func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { 79 | return &Fallback{ 80 | Base: outbound.NewBase(name, C.Fallback, false), 81 | single: singledo.NewSingle(defaultGetProxiesDuration), 82 | providers: providers, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /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 getKey(metadata *C.Metadata) string { 25 | if metadata.Host != "" { 26 | // ip host 27 | if ip := net.ParseIP(metadata.Host); ip != nil { 28 | return metadata.Host 29 | } 30 | 31 | if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil { 32 | return etld 33 | } 34 | } 35 | 36 | if metadata.DstIP == nil { 37 | return "" 38 | } 39 | 40 | return metadata.DstIP.String() 41 | } 42 | 43 | func jumpHash(key uint64, buckets int32) int32 { 44 | var b, j int64 45 | 46 | for j < int64(buckets) { 47 | b = j 48 | key = key*2862933555777941757 + 1 49 | j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) 50 | } 51 | 52 | return int32(b) 53 | } 54 | 55 | func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { 56 | defer func() { 57 | if err == nil { 58 | c.AppendToChains(lb) 59 | } 60 | }() 61 | 62 | key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) 63 | proxies := lb.proxies() 64 | buckets := int32(len(proxies)) 65 | for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { 66 | idx := jumpHash(key, buckets) 67 | proxy := proxies[idx] 68 | if proxy.Alive() { 69 | c, err = proxy.DialContext(ctx, metadata) 70 | return 71 | } 72 | } 73 | c, err = proxies[0].DialContext(ctx, metadata) 74 | return 75 | } 76 | 77 | func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { 78 | defer func() { 79 | if err == nil { 80 | pc.AppendToChains(lb) 81 | } 82 | }() 83 | 84 | key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) 85 | proxies := lb.proxies() 86 | buckets := int32(len(proxies)) 87 | for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { 88 | idx := jumpHash(key, buckets) 89 | proxy := proxies[idx] 90 | if proxy.Alive() { 91 | return proxy.DialUDP(metadata) 92 | } 93 | } 94 | 95 | return proxies[0].DialUDP(metadata) 96 | } 97 | 98 | func (lb *LoadBalance) SupportUDP() bool { 99 | return true 100 | } 101 | 102 | func (lb *LoadBalance) proxies() []C.Proxy { 103 | elm, _, _ := lb.single.Do(func() (interface{}, error) { 104 | return getProvidersProxies(lb.providers), nil 105 | }) 106 | 107 | return elm.([]C.Proxy) 108 | } 109 | 110 | func (lb *LoadBalance) MarshalJSON() ([]byte, error) { 111 | var all []string 112 | for _, proxy := range lb.proxies() { 113 | all = append(all, proxy.Name()) 114 | } 115 | return json.Marshal(map[string]interface{}{ 116 | "type": lb.Type().String(), 117 | "all": all, 118 | }) 119 | } 120 | 121 | func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { 122 | return &LoadBalance{ 123 | Base: outbound.NewBase(name, C.LoadBalance, false), 124 | single: singledo.NewSingle(defaultGetProxiesDuration), 125 | maxRetry: 3, 126 | providers: providers, 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /adapters/outboundgroup/parser.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/Dreamacro/clash/adapters/provider" 8 | "github.com/Dreamacro/clash/common/structure" 9 | C "github.com/Dreamacro/clash/constant" 10 | ) 11 | 12 | var ( 13 | errFormat = errors.New("format error") 14 | errType = errors.New("unsupport type") 15 | errMissUse = errors.New("`use` field should not be empty") 16 | errMissProxy = errors.New("`use` or `proxies` missing") 17 | errMissHealthCheck = errors.New("`url` or `interval` missing") 18 | errDuplicateProvider = errors.New("`duplicate provider name") 19 | ) 20 | 21 | type GroupCommonOption struct { 22 | Name string `group:"name"` 23 | Type string `group:"type"` 24 | Proxies []string `group:"proxies,omitempty"` 25 | Use []string `group:"use,omitempty"` 26 | URL string `group:"url,omitempty"` 27 | Interval int `group:"interval,omitempty"` 28 | } 29 | 30 | func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) { 31 | decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) 32 | 33 | groupOption := &GroupCommonOption{} 34 | if err := decoder.Decode(config, groupOption); err != nil { 35 | return nil, errFormat 36 | } 37 | 38 | if groupOption.Type == "" || groupOption.Name == "" { 39 | return nil, errFormat 40 | } 41 | 42 | groupName := groupOption.Name 43 | 44 | providers := []provider.ProxyProvider{} 45 | 46 | if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { 47 | return nil, errMissProxy 48 | } 49 | 50 | if len(groupOption.Proxies) != 0 { 51 | ps, err := getProxies(proxyMap, groupOption.Proxies) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | // if Use not empty, drop health check options 57 | if len(groupOption.Use) != 0 { 58 | hc := provider.NewHealthCheck(ps, "", 0) 59 | pd, err := provider.NewCompatibleProvider(groupName, ps, hc) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | providers = append(providers, pd) 65 | } else { 66 | // select don't need health check 67 | if groupOption.Type == "select" { 68 | hc := provider.NewHealthCheck(ps, "", 0) 69 | pd, err := provider.NewCompatibleProvider(groupName, ps, hc) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | providers = append(providers, pd) 75 | providersMap[groupName] = pd 76 | } else { 77 | if groupOption.URL == "" || groupOption.Interval == 0 { 78 | return nil, errMissHealthCheck 79 | } 80 | 81 | hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval)) 82 | pd, err := provider.NewCompatibleProvider(groupName, ps, hc) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | providers = append(providers, pd) 88 | providersMap[groupName] = pd 89 | } 90 | } 91 | } 92 | 93 | if len(groupOption.Use) != 0 { 94 | list, err := getProviders(providersMap, groupOption.Use) 95 | if err != nil { 96 | return nil, err 97 | } 98 | providers = append(providers, list...) 99 | } 100 | 101 | var group C.ProxyAdapter 102 | switch groupOption.Type { 103 | case "url-test": 104 | group = NewURLTest(groupName, providers) 105 | case "select": 106 | group = NewSelector(groupName, providers) 107 | case "fallback": 108 | group = NewFallback(groupName, providers) 109 | case "load-balance": 110 | group = NewLoadBalance(groupName, providers) 111 | default: 112 | return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) 113 | } 114 | 115 | return group, nil 116 | } 117 | 118 | func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { 119 | var ps []C.Proxy 120 | for _, name := range list { 121 | p, ok := mapping[name] 122 | if !ok { 123 | return nil, fmt.Errorf("'%s' not found", name) 124 | } 125 | ps = append(ps, p) 126 | } 127 | return ps, nil 128 | } 129 | 130 | func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) { 131 | var ps []provider.ProxyProvider 132 | for _, name := range list { 133 | p, ok := mapping[name] 134 | if !ok { 135 | return nil, fmt.Errorf("'%s' not found", name) 136 | } 137 | 138 | if p.VehicleType() == provider.Compatible { 139 | return nil, fmt.Errorf("proxy group %s can't contains in `use`", name) 140 | } 141 | ps = append(ps, p) 142 | } 143 | return ps, nil 144 | } 145 | -------------------------------------------------------------------------------- /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 C.Proxy 18 | providers []provider.ProxyProvider 19 | } 20 | 21 | func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { 22 | c, err := s.selected.DialContext(ctx, metadata) 23 | if err == nil { 24 | c.AppendToChains(s) 25 | } 26 | return c, err 27 | } 28 | 29 | func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 30 | pc, err := s.selected.DialUDP(metadata) 31 | if err == nil { 32 | pc.AppendToChains(s) 33 | } 34 | return pc, err 35 | } 36 | 37 | func (s *Selector) SupportUDP() bool { 38 | return s.selected.SupportUDP() 39 | } 40 | 41 | func (s *Selector) MarshalJSON() ([]byte, error) { 42 | var all []string 43 | for _, proxy := range s.proxies() { 44 | all = append(all, proxy.Name()) 45 | } 46 | 47 | return json.Marshal(map[string]interface{}{ 48 | "type": s.Type().String(), 49 | "now": s.Now(), 50 | "all": all, 51 | }) 52 | } 53 | 54 | func (s *Selector) Now() string { 55 | return s.selected.Name() 56 | } 57 | 58 | func (s *Selector) Set(name string) error { 59 | for _, proxy := range s.proxies() { 60 | if proxy.Name() == name { 61 | s.selected = proxy 62 | return nil 63 | } 64 | } 65 | 66 | return errors.New("Proxy does not exist") 67 | } 68 | 69 | func (s *Selector) proxies() []C.Proxy { 70 | elm, _, _ := s.single.Do(func() (interface{}, error) { 71 | return getProvidersProxies(s.providers), nil 72 | }) 73 | 74 | return elm.([]C.Proxy) 75 | } 76 | 77 | func NewSelector(name string, providers []provider.ProxyProvider) *Selector { 78 | selected := providers[0].Proxies()[0] 79 | return &Selector{ 80 | Base: outbound.NewBase(name, C.Selector, false), 81 | single: singledo.NewSingle(defaultGetProxiesDuration), 82 | providers: providers, 83 | selected: selected, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /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 URLTest struct { 15 | *outbound.Base 16 | single *singledo.Single 17 | fastSingle *singledo.Single 18 | providers []provider.ProxyProvider 19 | } 20 | 21 | func (u *URLTest) Now() string { 22 | return u.fast().Name() 23 | } 24 | 25 | func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { 26 | c, err = u.fast().DialContext(ctx, metadata) 27 | if err == nil { 28 | c.AppendToChains(u) 29 | } 30 | return c, err 31 | } 32 | 33 | func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { 34 | pc, err := u.fast().DialUDP(metadata) 35 | if err == nil { 36 | pc.AppendToChains(u) 37 | } 38 | return pc, err 39 | } 40 | 41 | func (u *URLTest) proxies() []C.Proxy { 42 | elm, _, _ := u.single.Do(func() (interface{}, error) { 43 | return getProvidersProxies(u.providers), nil 44 | }) 45 | 46 | return elm.([]C.Proxy) 47 | } 48 | 49 | func (u *URLTest) fast() C.Proxy { 50 | elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { 51 | proxies := u.proxies() 52 | fast := proxies[0] 53 | min := fast.LastDelay() 54 | for _, proxy := range proxies[1:] { 55 | if !proxy.Alive() { 56 | continue 57 | } 58 | 59 | delay := proxy.LastDelay() 60 | if delay < min { 61 | fast = proxy 62 | min = delay 63 | } 64 | } 65 | return fast, nil 66 | }) 67 | 68 | return elm.(C.Proxy) 69 | } 70 | 71 | func (u *URLTest) SupportUDP() bool { 72 | return u.fast().SupportUDP() 73 | } 74 | 75 | func (u *URLTest) MarshalJSON() ([]byte, error) { 76 | var all []string 77 | for _, proxy := range u.proxies() { 78 | all = append(all, proxy.Name()) 79 | } 80 | return json.Marshal(map[string]interface{}{ 81 | "type": u.Type().String(), 82 | "now": u.Now(), 83 | "all": all, 84 | }) 85 | } 86 | 87 | func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { 88 | return &URLTest{ 89 | Base: outbound.NewBase(name, C.URLTest, false), 90 | single: singledo.NewSingle(defaultGetProxiesDuration), 91 | fastSingle: singledo.NewSingle(time.Second * 10), 92 | providers: providers, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /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 * 5 12 | ) 13 | 14 | type HealthCheckOption struct { 15 | URL string 16 | Interval uint 17 | } 18 | 19 | type HealthCheck struct { 20 | url string 21 | proxies []C.Proxy 22 | interval uint 23 | done chan struct{} 24 | } 25 | 26 | func (hc *HealthCheck) process() { 27 | ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) 28 | 29 | go hc.check() 30 | for { 31 | select { 32 | case <-ticker.C: 33 | hc.check() 34 | case <-hc.done: 35 | ticker.Stop() 36 | return 37 | } 38 | } 39 | } 40 | 41 | func (hc *HealthCheck) setProxy(proxies []C.Proxy) { 42 | hc.proxies = proxies 43 | } 44 | 45 | func (hc *HealthCheck) auto() bool { 46 | return hc.interval != 0 47 | } 48 | 49 | func (hc *HealthCheck) check() { 50 | ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) 51 | for _, proxy := range hc.proxies { 52 | go proxy.URLTest(ctx, hc.url) 53 | } 54 | 55 | <-ctx.Done() 56 | cancel() 57 | } 58 | 59 | func (hc *HealthCheck) close() { 60 | hc.done <- struct{}{} 61 | } 62 | 63 | func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck { 64 | return &HealthCheck{ 65 | proxies: proxies, 66 | url: url, 67 | interval: interval, 68 | done: make(chan struct{}, 1), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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{}) (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 := C.Path.Resolve(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 | "time" 8 | 9 | "github.com/Dreamacro/clash/component/dialer" 10 | ) 11 | 12 | // Vehicle Type 13 | const ( 14 | File VehicleType = iota 15 | HTTP 16 | Compatible 17 | ) 18 | 19 | // VehicleType defined 20 | type VehicleType int 21 | 22 | func (v VehicleType) String() string { 23 | switch v { 24 | case File: 25 | return "File" 26 | case HTTP: 27 | return "HTTP" 28 | case Compatible: 29 | return "Compatible" 30 | default: 31 | return "Unknown" 32 | } 33 | } 34 | 35 | type Vehicle interface { 36 | Read() ([]byte, error) 37 | Path() string 38 | Type() VehicleType 39 | } 40 | 41 | type FileVehicle struct { 42 | path string 43 | } 44 | 45 | func (f *FileVehicle) Type() VehicleType { 46 | return File 47 | } 48 | 49 | func (f *FileVehicle) Path() string { 50 | return f.path 51 | } 52 | 53 | func (f *FileVehicle) Read() ([]byte, error) { 54 | return ioutil.ReadFile(f.path) 55 | } 56 | 57 | func NewFileVehicle(path string) *FileVehicle { 58 | return &FileVehicle{path: path} 59 | } 60 | 61 | type HTTPVehicle struct { 62 | url string 63 | path string 64 | } 65 | 66 | func (h *HTTPVehicle) Type() VehicleType { 67 | return HTTP 68 | } 69 | 70 | func (h *HTTPVehicle) Path() string { 71 | return h.path 72 | } 73 | 74 | func (h *HTTPVehicle) Read() ([]byte, error) { 75 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 76 | defer cancel() 77 | 78 | req, err := http.NewRequest(http.MethodGet, h.url, nil) 79 | if err != nil { 80 | return nil, err 81 | } 82 | req = req.WithContext(ctx) 83 | 84 | transport := &http.Transport{ 85 | // from http.DefaultTransport 86 | MaxIdleConns: 100, 87 | IdleConnTimeout: 90 * time.Second, 88 | TLSHandshakeTimeout: 10 * time.Second, 89 | ExpectContinueTimeout: 1 * time.Second, 90 | DialContext: dialer.DialContext, 91 | } 92 | 93 | client := http.Client{Transport: transport} 94 | resp, err := client.Do(req) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | buf, err := ioutil.ReadAll(resp.Body) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return buf, nil 105 | } 106 | 107 | func NewHTTPVehicle(url string, path string) *HTTPVehicle { 108 | return &HTTPVehicle{url, path} 109 | } 110 | -------------------------------------------------------------------------------- /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/cache/lrucache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // Modified by https://github.com/die-net/lrucache 4 | 5 | import ( 6 | "container/list" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // Option is part of Functional Options Pattern 12 | type Option func(*LruCache) 13 | 14 | // EvictCallback is used to get a callback when a cache entry is evicted 15 | type EvictCallback = func(key interface{}, value interface{}) 16 | 17 | // WithEvict set the evict callback 18 | func WithEvict(cb EvictCallback) Option { 19 | return func(l *LruCache) { 20 | l.onEvict = cb 21 | } 22 | } 23 | 24 | // WithUpdateAgeOnGet update expires when Get element 25 | func WithUpdateAgeOnGet() Option { 26 | return func(l *LruCache) { 27 | l.updateAgeOnGet = true 28 | } 29 | } 30 | 31 | // WithAge defined element max age (second) 32 | func WithAge(maxAge int64) Option { 33 | return func(l *LruCache) { 34 | l.maxAge = maxAge 35 | } 36 | } 37 | 38 | // WithSize defined max length of LruCache 39 | func WithSize(maxSize int) Option { 40 | return func(l *LruCache) { 41 | l.maxSize = maxSize 42 | } 43 | } 44 | 45 | // LruCache is a thread-safe, in-memory lru-cache that evicts the 46 | // least recently used entries from memory when (if set) the entries are 47 | // older than maxAge (in seconds). Use the New constructor to create one. 48 | type LruCache struct { 49 | maxAge int64 50 | maxSize int 51 | mu sync.Mutex 52 | cache map[interface{}]*list.Element 53 | lru *list.List // Front is least-recent 54 | updateAgeOnGet bool 55 | onEvict EvictCallback 56 | } 57 | 58 | // NewLRUCache creates an LruCache 59 | func NewLRUCache(options ...Option) *LruCache { 60 | lc := &LruCache{ 61 | lru: list.New(), 62 | cache: make(map[interface{}]*list.Element), 63 | } 64 | 65 | for _, option := range options { 66 | option(lc) 67 | } 68 | 69 | return lc 70 | } 71 | 72 | // Get returns the interface{} representation of a cached response and a bool 73 | // set to true if the key was found. 74 | func (c *LruCache) Get(key interface{}) (interface{}, bool) { 75 | c.mu.Lock() 76 | defer c.mu.Unlock() 77 | 78 | le, ok := c.cache[key] 79 | if !ok { 80 | return nil, false 81 | } 82 | 83 | if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() { 84 | c.deleteElement(le) 85 | c.maybeDeleteOldest() 86 | 87 | return nil, false 88 | } 89 | 90 | c.lru.MoveToBack(le) 91 | entry := le.Value.(*entry) 92 | if c.maxAge > 0 && c.updateAgeOnGet { 93 | entry.expires = time.Now().Unix() + c.maxAge 94 | } 95 | value := entry.value 96 | 97 | return value, true 98 | } 99 | 100 | // Exist returns if key exist in cache but not put item to the head of linked list 101 | func (c *LruCache) Exist(key interface{}) bool { 102 | c.mu.Lock() 103 | defer c.mu.Unlock() 104 | 105 | _, ok := c.cache[key] 106 | return ok 107 | } 108 | 109 | // Set stores the interface{} representation of a response for a given key. 110 | func (c *LruCache) Set(key interface{}, value interface{}) { 111 | c.mu.Lock() 112 | defer c.mu.Unlock() 113 | 114 | expires := int64(0) 115 | if c.maxAge > 0 { 116 | expires = time.Now().Unix() + c.maxAge 117 | } 118 | 119 | if le, ok := c.cache[key]; ok { 120 | c.lru.MoveToBack(le) 121 | e := le.Value.(*entry) 122 | e.value = value 123 | e.expires = expires 124 | } else { 125 | e := &entry{key: key, value: value, expires: expires} 126 | c.cache[key] = c.lru.PushBack(e) 127 | 128 | if c.maxSize > 0 { 129 | if len := c.lru.Len(); len > c.maxSize { 130 | c.deleteElement(c.lru.Front()) 131 | } 132 | } 133 | } 134 | 135 | c.maybeDeleteOldest() 136 | } 137 | 138 | // Delete removes the value associated with a key. 139 | func (c *LruCache) Delete(key string) { 140 | c.mu.Lock() 141 | 142 | if le, ok := c.cache[key]; ok { 143 | c.deleteElement(le) 144 | } 145 | 146 | c.mu.Unlock() 147 | } 148 | 149 | func (c *LruCache) maybeDeleteOldest() { 150 | if c.maxAge > 0 { 151 | now := time.Now().Unix() 152 | for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() { 153 | c.deleteElement(le) 154 | } 155 | } 156 | } 157 | 158 | func (c *LruCache) deleteElement(le *list.Element) { 159 | c.lru.Remove(le) 160 | e := le.Value.(*entry) 161 | delete(c.cache, e.key) 162 | if c.onEvict != nil { 163 | c.onEvict(e.key, e.value) 164 | } 165 | } 166 | 167 | type entry struct { 168 | key interface{} 169 | value interface{} 170 | expires int64 171 | } 172 | -------------------------------------------------------------------------------- /common/cache/lrucache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var entries = []struct { 11 | key string 12 | value string 13 | }{ 14 | {"1", "one"}, 15 | {"2", "two"}, 16 | {"3", "three"}, 17 | {"4", "four"}, 18 | {"5", "five"}, 19 | } 20 | 21 | func TestLRUCache(t *testing.T) { 22 | c := NewLRUCache() 23 | 24 | for _, e := range entries { 25 | c.Set(e.key, e.value) 26 | } 27 | 28 | c.Delete("missing") 29 | _, ok := c.Get("missing") 30 | assert.False(t, ok) 31 | 32 | for _, e := range entries { 33 | value, ok := c.Get(e.key) 34 | if assert.True(t, ok) { 35 | assert.Equal(t, e.value, value.(string)) 36 | } 37 | } 38 | 39 | for _, e := range entries { 40 | c.Delete(e.key) 41 | 42 | _, ok := c.Get(e.key) 43 | assert.False(t, ok) 44 | } 45 | } 46 | 47 | func TestLRUMaxAge(t *testing.T) { 48 | c := NewLRUCache(WithAge(86400)) 49 | 50 | now := time.Now().Unix() 51 | expected := now + 86400 52 | 53 | // Add one expired entry 54 | c.Set("foo", "bar") 55 | c.lru.Back().Value.(*entry).expires = now 56 | 57 | // Reset 58 | c.Set("foo", "bar") 59 | e := c.lru.Back().Value.(*entry) 60 | assert.True(t, e.expires >= now) 61 | c.lru.Back().Value.(*entry).expires = now 62 | 63 | // Set a few and verify expiration times 64 | for _, s := range entries { 65 | c.Set(s.key, s.value) 66 | e := c.lru.Back().Value.(*entry) 67 | assert.True(t, e.expires >= expected && e.expires <= expected+10) 68 | } 69 | 70 | // Make sure we can get them all 71 | for _, s := range entries { 72 | _, ok := c.Get(s.key) 73 | assert.True(t, ok) 74 | } 75 | 76 | // Expire all entries 77 | for _, s := range entries { 78 | le, ok := c.cache[s.key] 79 | if assert.True(t, ok) { 80 | le.Value.(*entry).expires = now 81 | } 82 | } 83 | 84 | // Get one expired entry, which should clear all expired entries 85 | _, ok := c.Get("3") 86 | assert.False(t, ok) 87 | assert.Equal(t, c.lru.Len(), 0) 88 | } 89 | 90 | func TestLRUpdateOnGet(t *testing.T) { 91 | c := NewLRUCache(WithAge(86400), WithUpdateAgeOnGet()) 92 | 93 | now := time.Now().Unix() 94 | expires := now + 86400/2 95 | 96 | // Add one expired entry 97 | c.Set("foo", "bar") 98 | c.lru.Back().Value.(*entry).expires = expires 99 | 100 | _, ok := c.Get("foo") 101 | assert.True(t, ok) 102 | assert.True(t, c.lru.Back().Value.(*entry).expires > expires) 103 | } 104 | 105 | func TestMaxSize(t *testing.T) { 106 | c := NewLRUCache(WithSize(2)) 107 | // Add one expired entry 108 | c.Set("foo", "bar") 109 | _, ok := c.Get("foo") 110 | assert.True(t, ok) 111 | 112 | c.Set("bar", "foo") 113 | c.Set("baz", "foo") 114 | 115 | _, ok = c.Get("foo") 116 | assert.False(t, ok) 117 | } 118 | 119 | func TestExist(t *testing.T) { 120 | c := NewLRUCache(WithSize(1)) 121 | c.Set(1, 2) 122 | assert.True(t, c.Exist(1)) 123 | c.Set(2, 3) 124 | assert.False(t, c.Exist(1)) 125 | } 126 | 127 | func TestEvict(t *testing.T) { 128 | temp := 0 129 | evict := func(key interface{}, value interface{}) { 130 | temp = key.(int) + value.(int) 131 | } 132 | 133 | c := NewLRUCache(WithEvict(evict), WithSize(1)) 134 | c.Set(1, 2) 135 | c.Set(2, 3) 136 | 137 | assert.Equal(t, temp, 3) 138 | } 139 | -------------------------------------------------------------------------------- /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 | result interface{} 20 | } 21 | 22 | func newPicker(ctx context.Context, cancel func()) *Picker { 23 | return &Picker{ 24 | ctx: ctx, 25 | cancel: cancel, 26 | } 27 | } 28 | 29 | // WithContext returns a new Picker and an associated Context derived from ctx. 30 | // and cancel when first element return. 31 | func WithContext(ctx context.Context) (*Picker, context.Context) { 32 | ctx, cancel := context.WithCancel(ctx) 33 | return newPicker(ctx, cancel), ctx 34 | } 35 | 36 | // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. 37 | func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { 38 | ctx, cancel := context.WithTimeout(ctx, timeout) 39 | return newPicker(ctx, cancel), ctx 40 | } 41 | 42 | // Wait blocks until all function calls from the Go method have returned, 43 | // then returns the first nil error result (if any) from them. 44 | func (p *Picker) Wait() interface{} { 45 | p.wg.Wait() 46 | if p.cancel != nil { 47 | p.cancel() 48 | } 49 | return p.result 50 | } 51 | 52 | // Go calls the given function in a new goroutine. 53 | // The first call to return a nil error cancels the group; its result will be returned by Wait. 54 | func (p *Picker) Go(f func() (interface{}, error)) { 55 | p.wg.Add(1) 56 | 57 | go func() { 58 | defer p.wg.Done() 59 | 60 | if ret, err := f(); err == nil { 61 | p.once.Do(func() { 62 | p.result = ret 63 | if p.cancel != nil { 64 | p.cancel() 65 | } 66 | }) 67 | } 68 | }() 69 | } 70 | -------------------------------------------------------------------------------- /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 | } 40 | -------------------------------------------------------------------------------- /common/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | const ( 8 | // io.Copy default buffer size is 32 KiB 9 | // but the maximum packet size of vmess/shadowsocks is about 16 KiB 10 | // so define a buffer of 20 KiB to reduce the memory of each TCP relay 11 | bufferSize = 20 * 1024 12 | ) 13 | 14 | // BufPool provide buffer for relay 15 | var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} 16 | -------------------------------------------------------------------------------- /common/queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Queue is a simple concurrent safe queue 8 | type Queue struct { 9 | items []interface{} 10 | lock sync.RWMutex 11 | } 12 | 13 | // Put add the item to the queue. 14 | func (q *Queue) Put(items ...interface{}) { 15 | if len(items) == 0 { 16 | return 17 | } 18 | 19 | q.lock.Lock() 20 | q.items = append(q.items, items...) 21 | q.lock.Unlock() 22 | } 23 | 24 | // Pop returns the head of items. 25 | func (q *Queue) Pop() interface{} { 26 | if len(q.items) == 0 { 27 | return nil 28 | } 29 | 30 | q.lock.Lock() 31 | head := q.items[0] 32 | q.items = q.items[1:] 33 | q.lock.Unlock() 34 | return head 35 | } 36 | 37 | // Last returns the last of item. 38 | func (q *Queue) Last() interface{} { 39 | if len(q.items) == 0 { 40 | return nil 41 | } 42 | 43 | q.lock.RLock() 44 | last := q.items[len(q.items)-1] 45 | q.lock.RUnlock() 46 | return last 47 | } 48 | 49 | // Copy get the copy of queue. 50 | func (q *Queue) Copy() []interface{} { 51 | items := []interface{}{} 52 | q.lock.RLock() 53 | items = append(items, q.items...) 54 | q.lock.RUnlock() 55 | return items 56 | } 57 | 58 | // Len returns the number of items in this queue. 59 | func (q *Queue) Len() int64 { 60 | q.lock.Lock() 61 | defer q.lock.Unlock() 62 | 63 | return int64(len(q.items)) 64 | } 65 | 66 | // New is a constructor for a new concurrent safe queue. 67 | func New(hint int64) *Queue { 68 | return &Queue{ 69 | items: make([]interface{}, 0, hint), 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/singledo/singledo.go: -------------------------------------------------------------------------------- 1 | package singledo 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type call struct { 9 | wg sync.WaitGroup 10 | val interface{} 11 | err error 12 | } 13 | 14 | type Single struct { 15 | mux sync.Mutex 16 | last time.Time 17 | wait time.Duration 18 | call *call 19 | result *Result 20 | } 21 | 22 | type Result struct { 23 | Val interface{} 24 | Err error 25 | } 26 | 27 | func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { 28 | s.mux.Lock() 29 | now := time.Now() 30 | if now.Before(s.last.Add(s.wait)) { 31 | s.mux.Unlock() 32 | return s.result.Val, s.result.Err, true 33 | } 34 | 35 | if call := s.call; call != nil { 36 | s.mux.Unlock() 37 | call.wg.Wait() 38 | return call.val, call.err, true 39 | } 40 | 41 | call := &call{} 42 | call.wg.Add(1) 43 | s.call = call 44 | s.mux.Unlock() 45 | call.val, call.err = fn() 46 | call.wg.Done() 47 | s.call = nil 48 | s.result = &Result{call.val, call.err} 49 | s.last = now 50 | return call.val, call.err, false 51 | } 52 | 53 | func NewSingle(wait time.Duration) *Single { 54 | return &Single{wait: wait} 55 | } 56 | -------------------------------------------------------------------------------- /common/singledo/singledo_test.go: -------------------------------------------------------------------------------- 1 | package singledo 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestBasic(t *testing.T) { 12 | single := NewSingle(time.Millisecond * 30) 13 | foo := 0 14 | shardCount := 0 15 | call := func() (interface{}, error) { 16 | foo++ 17 | time.Sleep(time.Millisecond * 5) 18 | return nil, nil 19 | } 20 | 21 | var wg sync.WaitGroup 22 | const n = 10 23 | wg.Add(n) 24 | for i := 0; i < n; i++ { 25 | go func() { 26 | _, _, shard := single.Do(call) 27 | if shard { 28 | shardCount++ 29 | } 30 | wg.Done() 31 | }() 32 | } 33 | 34 | wg.Wait() 35 | assert.Equal(t, 1, foo) 36 | assert.Equal(t, 9, shardCount) 37 | } 38 | 39 | func TestTimer(t *testing.T) { 40 | single := NewSingle(time.Millisecond * 30) 41 | foo := 0 42 | call := func() (interface{}, error) { 43 | foo++ 44 | return nil, nil 45 | } 46 | 47 | single.Do(call) 48 | time.Sleep(10 * time.Millisecond) 49 | _, _, shard := single.Do(call) 50 | 51 | assert.Equal(t, 1, foo) 52 | assert.True(t, shard) 53 | } 54 | -------------------------------------------------------------------------------- /common/structure/structure_test.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | var decoder = NewDecoder(Option{TagName: "test"}) 9 | var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) 10 | 11 | type Baz struct { 12 | Foo int `test:"foo"` 13 | Bar string `test:"bar"` 14 | } 15 | 16 | type BazSlice struct { 17 | Foo int `test:"foo"` 18 | Bar []string `test:"bar"` 19 | } 20 | 21 | type BazOptional struct { 22 | Foo int `test:"foo,omitempty"` 23 | Bar string `test:"bar,omitempty"` 24 | } 25 | 26 | func TestStructure_Basic(t *testing.T) { 27 | rawMap := map[string]interface{}{ 28 | "foo": 1, 29 | "bar": "test", 30 | "extra": false, 31 | } 32 | 33 | goal := &Baz{ 34 | Foo: 1, 35 | Bar: "test", 36 | } 37 | 38 | s := &Baz{} 39 | err := decoder.Decode(rawMap, s) 40 | if err != nil { 41 | t.Fatal(err.Error()) 42 | } 43 | if !reflect.DeepEqual(s, goal) { 44 | t.Fatalf("bad: %#v", s) 45 | } 46 | } 47 | 48 | func TestStructure_Slice(t *testing.T) { 49 | rawMap := map[string]interface{}{ 50 | "foo": 1, 51 | "bar": []string{"one", "two"}, 52 | } 53 | 54 | goal := &BazSlice{ 55 | Foo: 1, 56 | Bar: []string{"one", "two"}, 57 | } 58 | 59 | s := &BazSlice{} 60 | err := decoder.Decode(rawMap, s) 61 | if err != nil { 62 | t.Fatal(err.Error()) 63 | } 64 | if !reflect.DeepEqual(s, goal) { 65 | t.Fatalf("bad: %#v", s) 66 | } 67 | } 68 | 69 | func TestStructure_Optional(t *testing.T) { 70 | rawMap := map[string]interface{}{ 71 | "foo": 1, 72 | } 73 | 74 | goal := &BazOptional{ 75 | Foo: 1, 76 | } 77 | 78 | s := &BazOptional{} 79 | err := decoder.Decode(rawMap, s) 80 | if err != nil { 81 | t.Fatal(err.Error()) 82 | } 83 | if !reflect.DeepEqual(s, goal) { 84 | t.Fatalf("bad: %#v", s) 85 | } 86 | } 87 | 88 | func TestStructure_MissingKey(t *testing.T) { 89 | rawMap := map[string]interface{}{ 90 | "foo": 1, 91 | } 92 | 93 | s := &Baz{} 94 | err := decoder.Decode(rawMap, s) 95 | if err == nil { 96 | t.Fatalf("should throw error: %#v", s) 97 | } 98 | } 99 | 100 | func TestStructure_ParamError(t *testing.T) { 101 | rawMap := map[string]interface{}{} 102 | s := Baz{} 103 | err := decoder.Decode(rawMap, s) 104 | if err == nil { 105 | t.Fatalf("should throw error: %#v", s) 106 | } 107 | } 108 | 109 | func TestStructure_SliceTypeError(t *testing.T) { 110 | rawMap := map[string]interface{}{ 111 | "foo": 1, 112 | "bar": []int{1, 2}, 113 | } 114 | 115 | s := &BazSlice{} 116 | err := decoder.Decode(rawMap, s) 117 | if err == nil { 118 | t.Fatalf("should throw error: %#v", s) 119 | } 120 | } 121 | 122 | func TestStructure_WeakType(t *testing.T) { 123 | rawMap := map[string]interface{}{ 124 | "foo": "1", 125 | "bar": []int{1}, 126 | } 127 | 128 | goal := &BazSlice{ 129 | Foo: 1, 130 | Bar: []string{"1"}, 131 | } 132 | 133 | s := &BazSlice{} 134 | err := weakTypeDecoder.Decode(rawMap, s) 135 | if err != nil { 136 | t.Fatal(err.Error()) 137 | } 138 | if !reflect.DeepEqual(s, goal) { 139 | t.Fatalf("bad: %#v", s) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /component/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Authenticator interface { 8 | Verify(user string, pass string) bool 9 | Users() []string 10 | } 11 | 12 | type AuthUser struct { 13 | User string 14 | Pass string 15 | } 16 | 17 | type inMemoryAuthenticator struct { 18 | storage *sync.Map 19 | usernames []string 20 | } 21 | 22 | func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { 23 | realPass, ok := au.storage.Load(user) 24 | return ok && realPass == pass 25 | } 26 | 27 | func (au *inMemoryAuthenticator) Users() []string { return au.usernames } 28 | 29 | func NewAuthenticator(users []AuthUser) Authenticator { 30 | if len(users) == 0 { 31 | return nil 32 | } 33 | 34 | au := &inMemoryAuthenticator{storage: &sync.Map{}} 35 | for _, user := range users { 36 | au.storage.Store(user.User, user.Pass) 37 | } 38 | usernames := make([]string, 0, len(users)) 39 | au.storage.Range(func(key, value interface{}) bool { 40 | usernames = append(usernames, key.(string)) 41 | return true 42 | }) 43 | au.usernames = usernames 44 | 45 | return au 46 | } 47 | -------------------------------------------------------------------------------- /component/dialer/dialer.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | 8 | "github.com/Dreamacro/clash/component/resolver" 9 | ) 10 | 11 | func Dialer() *net.Dialer { 12 | dialer := &net.Dialer{} 13 | if DialerHook != nil { 14 | DialerHook(dialer) 15 | } 16 | 17 | return dialer 18 | } 19 | 20 | func ListenConfig() *net.ListenConfig { 21 | cfg := &net.ListenConfig{} 22 | if ListenConfigHook != nil { 23 | ListenConfigHook(cfg) 24 | } 25 | 26 | return cfg 27 | } 28 | 29 | func Dial(network, address string) (net.Conn, error) { 30 | return DialContext(context.Background(), network, address) 31 | } 32 | 33 | func DialContext(ctx context.Context, network, address string) (net.Conn, error) { 34 | switch network { 35 | case "tcp4", "tcp6", "udp4", "udp6": 36 | host, port, err := net.SplitHostPort(address) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | dialer := Dialer() 42 | 43 | var ip net.IP 44 | switch network { 45 | case "tcp4", "udp4": 46 | ip, err = resolver.ResolveIPv4(host) 47 | default: 48 | ip, err = resolver.ResolveIPv6(host) 49 | } 50 | 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | if DialHook != nil { 56 | DialHook(dialer, network, ip) 57 | } 58 | return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) 59 | case "tcp", "udp": 60 | return dualStackDailContext(ctx, network, address) 61 | default: 62 | return nil, errors.New("network invalid") 63 | } 64 | } 65 | 66 | func ListenPacket(network, address string) (net.PacketConn, error) { 67 | lc := ListenConfig() 68 | 69 | if ListenPacketHook != nil && address == "" { 70 | ip := ListenPacketHook() 71 | if ip != nil { 72 | address = net.JoinHostPort(ip.String(), "0") 73 | } 74 | } 75 | return lc.ListenPacket(context.Background(), network, address) 76 | } 77 | 78 | func dualStackDailContext(ctx context.Context, network, address string) (net.Conn, error) { 79 | host, port, err := net.SplitHostPort(address) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | returned := make(chan struct{}) 85 | defer close(returned) 86 | 87 | type dialResult struct { 88 | net.Conn 89 | error 90 | resolved bool 91 | ipv6 bool 92 | done bool 93 | } 94 | results := make(chan dialResult) 95 | var primary, fallback dialResult 96 | 97 | startRacer := func(ctx context.Context, network, host string, ipv6 bool) { 98 | dialer := Dialer() 99 | result := dialResult{ipv6: ipv6, done: true} 100 | defer func() { 101 | select { 102 | case results <- result: 103 | case <-returned: 104 | if result.Conn != nil { 105 | result.Conn.Close() 106 | } 107 | } 108 | }() 109 | 110 | var ip net.IP 111 | if ipv6 { 112 | ip, result.error = resolver.ResolveIPv6(host) 113 | } else { 114 | ip, result.error = resolver.ResolveIPv4(host) 115 | } 116 | if result.error != nil { 117 | return 118 | } 119 | result.resolved = true 120 | 121 | if DialHook != nil { 122 | DialHook(dialer, network, ip) 123 | } 124 | result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) 125 | } 126 | 127 | go startRacer(ctx, network+"4", host, false) 128 | go startRacer(ctx, network+"6", host, true) 129 | 130 | for { 131 | select { 132 | case res := <-results: 133 | if res.error == nil { 134 | return res.Conn, nil 135 | } 136 | 137 | if !res.ipv6 { 138 | primary = res 139 | } else { 140 | fallback = res 141 | } 142 | 143 | if primary.done && fallback.done { 144 | if primary.resolved { 145 | return nil, primary.error 146 | } else if fallback.resolved { 147 | return nil, fallback.error 148 | } else { 149 | return nil, primary.error 150 | } 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /component/dialer/hook.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/common/singledo" 9 | ) 10 | 11 | type DialerHookFunc = func(dialer *net.Dialer) 12 | type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) 13 | type ListenConfigHookFunc = func(*net.ListenConfig) 14 | type ListenPacketHookFunc = func() net.IP 15 | 16 | var ( 17 | DialerHook DialerHookFunc 18 | DialHook DialHookFunc 19 | ListenConfigHook ListenConfigHookFunc 20 | ListenPacketHook ListenPacketHookFunc 21 | ) 22 | 23 | var ( 24 | ErrAddrNotFound = errors.New("addr not found") 25 | ErrNetworkNotSupport = errors.New("network not support") 26 | ) 27 | 28 | func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) { 29 | ipv4 := ip.To4() != nil 30 | 31 | for _, elm := range addrs { 32 | addr, ok := elm.(*net.IPNet) 33 | if !ok { 34 | continue 35 | } 36 | 37 | addrV4 := addr.IP.To4() != nil 38 | 39 | if addrV4 && ipv4 { 40 | return &net.TCPAddr{IP: addr.IP, Port: 0}, nil 41 | } else if !addrV4 && !ipv4 { 42 | return &net.TCPAddr{IP: addr.IP, Port: 0}, nil 43 | } 44 | } 45 | 46 | return nil, ErrAddrNotFound 47 | } 48 | 49 | func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { 50 | ipv4 := ip.To4() != nil 51 | 52 | for _, elm := range addrs { 53 | addr, ok := elm.(*net.IPNet) 54 | if !ok { 55 | continue 56 | } 57 | 58 | addrV4 := addr.IP.To4() != nil 59 | 60 | if addrV4 && ipv4 { 61 | return &net.UDPAddr{IP: addr.IP, Port: 0}, nil 62 | } else if !addrV4 && !ipv4 { 63 | return &net.UDPAddr{IP: addr.IP, Port: 0}, nil 64 | } 65 | } 66 | 67 | return nil, ErrAddrNotFound 68 | } 69 | 70 | func ListenPacketWithInterface(name string) ListenPacketHookFunc { 71 | single := singledo.NewSingle(5 * time.Second) 72 | 73 | return func() net.IP { 74 | elm, err, _ := single.Do(func() (interface{}, error) { 75 | iface, err := net.InterfaceByName(name) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | addrs, err := iface.Addrs() 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return addrs, nil 86 | }) 87 | 88 | if err != nil { 89 | return nil 90 | } 91 | 92 | addrs := elm.([]net.Addr) 93 | 94 | for _, elm := range addrs { 95 | addr, ok := elm.(*net.IPNet) 96 | if !ok || addr.IP.To4() == nil { 97 | continue 98 | } 99 | 100 | return addr.IP 101 | } 102 | 103 | return nil 104 | } 105 | } 106 | 107 | func DialerWithInterface(name string) DialHookFunc { 108 | single := singledo.NewSingle(5 * time.Second) 109 | 110 | return func(dialer *net.Dialer, network string, ip net.IP) { 111 | elm, err, _ := single.Do(func() (interface{}, error) { 112 | iface, err := net.InterfaceByName(name) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | addrs, err := iface.Addrs() 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return addrs, nil 123 | }) 124 | 125 | if err != nil { 126 | return 127 | } 128 | 129 | addrs := elm.([]net.Addr) 130 | 131 | switch network { 132 | case "tcp", "tcp4", "tcp6": 133 | if addr, err := lookupTCPAddr(ip, addrs); err == nil { 134 | dialer.LocalAddr = addr 135 | } 136 | case "udp", "udp4", "udp6": 137 | if addr, err := lookupUDPAddr(ip, addrs); err == nil { 138 | dialer.LocalAddr = addr 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /component/domain-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/domain-trie/tire.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | wildcard = "*" 10 | domainStep = "." 11 | ) 12 | 13 | var ( 14 | // ErrInvalidDomain means insert domain is invalid 15 | ErrInvalidDomain = errors.New("invalid domain") 16 | ) 17 | 18 | // Trie contains the main logic for adding and searching nodes for domain segments. 19 | // support wildcard domain (e.g *.google.com) 20 | type Trie struct { 21 | root *Node 22 | } 23 | 24 | func isValidDomain(domain string) bool { 25 | return domain != "" && domain[0] != '.' && domain[len(domain)-1] != '.' 26 | } 27 | 28 | // Insert adds a node to the trie. 29 | // Support 30 | // 1. www.example.com 31 | // 2. *.example.com 32 | // 3. subdomain.*.example.com 33 | func (t *Trie) Insert(domain string, data interface{}) error { 34 | if !isValidDomain(domain) { 35 | return ErrInvalidDomain 36 | } 37 | 38 | parts := strings.Split(domain, domainStep) 39 | node := t.root 40 | // reverse storage domain part to save space 41 | for i := len(parts) - 1; i >= 0; i-- { 42 | part := parts[i] 43 | if !node.hasChild(part) { 44 | node.addChild(part, newNode(nil)) 45 | } 46 | 47 | node = node.getChild(part) 48 | } 49 | 50 | node.Data = data 51 | return nil 52 | } 53 | 54 | // Search is the most important part of the Trie. 55 | // Priority as: 56 | // 1. static part 57 | // 2. wildcard domain 58 | func (t *Trie) Search(domain string) *Node { 59 | if !isValidDomain(domain) { 60 | return nil 61 | } 62 | parts := strings.Split(domain, domainStep) 63 | 64 | n := t.root 65 | for i := len(parts) - 1; i >= 0; i-- { 66 | part := parts[i] 67 | 68 | var child *Node 69 | if !n.hasChild(part) { 70 | if !n.hasChild(wildcard) { 71 | return nil 72 | } 73 | 74 | child = n.getChild(wildcard) 75 | } else { 76 | child = n.getChild(part) 77 | } 78 | 79 | n = child 80 | } 81 | 82 | if n.Data == nil { 83 | return nil 84 | } 85 | 86 | return n 87 | } 88 | 89 | // New returns a new, empty Trie. 90 | func New() *Trie { 91 | return &Trie{root: newNode(nil)} 92 | } 93 | -------------------------------------------------------------------------------- /component/domain-trie/trie_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | var localIP = net.IP{127, 0, 0, 1} 9 | 10 | func TestTrie_Basic(t *testing.T) { 11 | tree := New() 12 | domains := []string{ 13 | "example.com", 14 | "google.com", 15 | } 16 | 17 | for _, domain := range domains { 18 | tree.Insert(domain, localIP) 19 | } 20 | 21 | node := tree.Search("example.com") 22 | if node == nil { 23 | t.Error("should not recv nil") 24 | } 25 | 26 | if !node.Data.(net.IP).Equal(localIP) { 27 | t.Error("should equal 127.0.0.1") 28 | } 29 | 30 | if tree.Insert("", localIP) == nil { 31 | t.Error("should return error") 32 | } 33 | } 34 | 35 | func TestTrie_Wildcard(t *testing.T) { 36 | tree := New() 37 | domains := []string{ 38 | "*.example.com", 39 | "sub.*.example.com", 40 | "*.dev", 41 | } 42 | 43 | for _, domain := range domains { 44 | tree.Insert(domain, localIP) 45 | } 46 | 47 | if tree.Search("sub.example.com") == nil { 48 | t.Error("should not recv nil") 49 | } 50 | 51 | if tree.Search("sub.foo.example.com") == nil { 52 | t.Error("should not recv nil") 53 | } 54 | 55 | if tree.Search("foo.sub.example.com") != nil { 56 | t.Error("should recv nil") 57 | } 58 | 59 | if tree.Search("foo.example.dev") != nil { 60 | t.Error("should recv nil") 61 | } 62 | 63 | if tree.Search("example.com") != nil { 64 | t.Error("should recv nil") 65 | } 66 | } 67 | 68 | func TestTrie_Boundary(t *testing.T) { 69 | tree := New() 70 | tree.Insert("*.dev", localIP) 71 | 72 | if err := tree.Insert(".", localIP); err == nil { 73 | t.Error("should recv err") 74 | } 75 | 76 | if err := tree.Insert(".com", localIP); err == nil { 77 | t.Error("should recv err") 78 | } 79 | 80 | if tree.Search("dev") != nil { 81 | t.Error("should recv nil") 82 | } 83 | 84 | if tree.Search(".dev") != nil { 85 | t.Error("should recv nil") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /component/fakeip/pool.go: -------------------------------------------------------------------------------- 1 | package fakeip 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | 8 | "github.com/Dreamacro/clash/common/cache" 9 | trie "github.com/Dreamacro/clash/component/domain-trie" 10 | ) 11 | 12 | // Pool is a implementation about fake ip generator without storage 13 | type Pool struct { 14 | max uint32 15 | min uint32 16 | gateway uint32 17 | offset uint32 18 | mux sync.Mutex 19 | host *trie.Trie 20 | cache *cache.LruCache 21 | } 22 | 23 | // Lookup return a fake ip with host 24 | func (p *Pool) Lookup(host string) net.IP { 25 | p.mux.Lock() 26 | defer p.mux.Unlock() 27 | if elm, exist := p.cache.Get(host); exist { 28 | ip := elm.(net.IP) 29 | 30 | // ensure ip --> 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 | func (p *Pool) get(host string) net.IP { 93 | current := p.offset 94 | for { 95 | p.offset = (p.offset + 1) % (p.max - p.min) 96 | // Avoid infinite loops 97 | if p.offset == current { 98 | break 99 | } 100 | 101 | if !p.cache.Exist(p.offset) { 102 | break 103 | } 104 | } 105 | ip := uintToIP(p.min + p.offset - 1) 106 | p.cache.Set(p.offset, host) 107 | return ip 108 | } 109 | 110 | func ipToUint(ip net.IP) uint32 { 111 | v := uint32(ip[0]) << 24 112 | v += uint32(ip[1]) << 16 113 | v += uint32(ip[2]) << 8 114 | v += uint32(ip[3]) 115 | return v 116 | } 117 | 118 | func uintToIP(v uint32) net.IP { 119 | return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 120 | } 121 | 122 | // New return Pool instance 123 | func New(ipnet *net.IPNet, size int, host *trie.Trie) (*Pool, error) { 124 | min := ipToUint(ipnet.IP) + 2 125 | 126 | ones, bits := ipnet.Mask.Size() 127 | total := 1< n { 52 | ho.buf = buf[:idx+4+length] 53 | ho.offset = idx + 4 + n 54 | } else { 55 | pool.BufPool.Put(buf[:cap(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/v2ray-plugin/mux.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "net" 9 | ) 10 | 11 | type SessionStatus = byte 12 | 13 | const ( 14 | SessionStatusNew SessionStatus = 0x01 15 | SessionStatusKeep SessionStatus = 0x02 16 | SessionStatusEnd SessionStatus = 0x03 17 | SessionStatusKeepAlive SessionStatus = 0x04 18 | ) 19 | 20 | const ( 21 | OptionNone = byte(0x00) 22 | OptionData = byte(0x01) 23 | OptionError = byte(0x02) 24 | ) 25 | 26 | type MuxOption struct { 27 | ID [2]byte 28 | Port uint16 29 | Host string 30 | Type string 31 | } 32 | 33 | // Mux is an mux-compatible client for v2ray-plugin, not a complete implementation 34 | type Mux struct { 35 | net.Conn 36 | buf bytes.Buffer 37 | id [2]byte 38 | length [2]byte 39 | status [2]byte 40 | otb []byte 41 | remain int 42 | } 43 | 44 | func (m *Mux) Read(b []byte) (int, error) { 45 | if m.remain != 0 { 46 | length := m.remain 47 | if len(b) < m.remain { 48 | length = len(b) 49 | } 50 | 51 | n, err := m.Conn.Read(b[:length]) 52 | if err != nil { 53 | return 0, err 54 | } 55 | m.remain -= n 56 | return n, nil 57 | } 58 | 59 | for { 60 | _, err := io.ReadFull(m.Conn, m.length[:]) 61 | if err != nil { 62 | return 0, err 63 | } 64 | length := binary.BigEndian.Uint16(m.length[:]) 65 | if length > 512 { 66 | return 0, errors.New("invalid metalen") 67 | } 68 | 69 | _, err = io.ReadFull(m.Conn, m.id[:]) 70 | if err != nil { 71 | return 0, err 72 | } 73 | 74 | _, err = m.Conn.Read(m.status[:]) 75 | if err != nil { 76 | return 0, err 77 | } 78 | 79 | opcode := m.status[0] 80 | if opcode == SessionStatusKeepAlive { 81 | continue 82 | } 83 | 84 | opts := m.status[1] 85 | 86 | if opts != OptionData { 87 | continue 88 | } 89 | 90 | _, err = io.ReadFull(m.Conn, m.length[:]) 91 | if err != nil { 92 | return 0, err 93 | } 94 | dataLen := int(binary.BigEndian.Uint16(m.length[:])) 95 | m.remain = dataLen 96 | if dataLen > len(b) { 97 | dataLen = len(b) 98 | } 99 | 100 | n, err := m.Conn.Read(b[:dataLen]) 101 | m.remain -= n 102 | return n, err 103 | } 104 | } 105 | 106 | func (m *Mux) Write(b []byte) (int, error) { 107 | if m.otb != nil { 108 | // create a sub connection 109 | if _, err := m.Conn.Write(m.otb); err != nil { 110 | return 0, err 111 | } 112 | m.otb = nil 113 | } 114 | m.buf.Reset() 115 | binary.Write(&m.buf, binary.BigEndian, uint16(4)) 116 | m.buf.Write(m.id[:]) 117 | m.buf.WriteByte(SessionStatusKeep) 118 | m.buf.WriteByte(OptionData) 119 | binary.Write(&m.buf, binary.BigEndian, uint16(len(b))) 120 | m.buf.Write(b) 121 | 122 | return m.Conn.Write(m.buf.Bytes()) 123 | } 124 | 125 | func (m *Mux) Close() error { 126 | _, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone}) 127 | if err != nil { 128 | return err 129 | } 130 | return m.Conn.Close() 131 | } 132 | 133 | func NewMux(conn net.Conn, option MuxOption) *Mux { 134 | buf := &bytes.Buffer{} 135 | 136 | // fill empty length 137 | buf.Write([]byte{0x0, 0x0}) 138 | buf.Write(option.ID[:]) 139 | buf.WriteByte(SessionStatusNew) 140 | buf.WriteByte(OptionNone) 141 | 142 | // tcp 143 | netType := byte(0x1) 144 | if option.Type == "udp" { 145 | netType = byte(0x2) 146 | } 147 | buf.WriteByte(netType) 148 | 149 | // port 150 | binary.Write(buf, binary.BigEndian, option.Port) 151 | 152 | // address 153 | ip := net.ParseIP(option.Host) 154 | if ip == nil { 155 | buf.WriteByte(0x2) 156 | buf.WriteString(option.Host) 157 | } else if ipv4 := ip.To4(); ipv4 != nil { 158 | buf.WriteByte(0x1) 159 | buf.Write(ipv4) 160 | } else { 161 | buf.WriteByte(0x3) 162 | buf.Write(ip.To16()) 163 | } 164 | 165 | metadata := buf.Bytes() 166 | binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2)) 167 | 168 | return &Mux{ 169 | Conn: conn, 170 | id: option.ID, 171 | otb: metadata, 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /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 | Path string 15 | Headers map[string]string 16 | TLSConfig *tls.Config 17 | Mux bool 18 | } 19 | 20 | // NewV2rayObfs return a HTTPObfs 21 | func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { 22 | header := http.Header{} 23 | for k, v := range option.Headers { 24 | header.Add(k, v) 25 | } 26 | 27 | config := &vmess.WebsocketConfig{ 28 | Host: option.Host, 29 | Path: option.Path, 30 | TLS: option.TLSConfig != nil, 31 | Headers: header, 32 | TLSConfig: option.TLSConfig, 33 | } 34 | 35 | var err error 36 | conn, err = vmess.NewWebsocketConn(conn, config) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | if option.Mux { 42 | conn = NewMux(conn, MuxOption{ 43 | ID: [2]byte{0, 0}, 44 | Host: "127.0.0.1", 45 | Port: 0, 46 | }) 47 | } 48 | return conn, nil 49 | } 50 | -------------------------------------------------------------------------------- /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.BufPool.Get().([]byte) 26 | defer pool.BufPool.Put(buf[:cap(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.BufPool.Put(r.buf[:cap(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.BufPool.Get().([]byte) 93 | _, err = io.ReadFull(r.Reader, buf[:size]) 94 | if err != nil { 95 | pool.BufPool.Put(buf[:cap(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.BufPool.Put(buf[:cap(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.BufPool.Put(cr.buf[:cap(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.BufPool.Get().([]byte) 63 | _, err = io.ReadFull(cr.Reader, buf[:size]) 64 | if err != nil { 65 | pool.BufPool.Put(buf[:cap(buf)]) 66 | return 0, err 67 | } 68 | n := copy(b, cr.buf[:]) 69 | cr.offset = n 70 | cr.buf = buf[:size] 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.BufPool.Get().([]byte) 80 | defer pool.BufPool.Put(buf[:cap(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/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 | "net/http" 9 | "runtime" 10 | "sync" 11 | 12 | "github.com/gofrs/uuid" 13 | ) 14 | 15 | // Version of vmess 16 | const Version byte = 1 17 | 18 | // Request Options 19 | const ( 20 | OptionChunkStream byte = 1 21 | OptionChunkMasking byte = 4 22 | ) 23 | 24 | // Security type vmess 25 | type Security = byte 26 | 27 | // Cipher types 28 | const ( 29 | SecurityAES128GCM Security = 3 30 | SecurityCHACHA20POLY1305 Security = 4 31 | SecurityNone Security = 5 32 | ) 33 | 34 | // CipherMapping return 35 | var CipherMapping = map[string]byte{ 36 | "none": SecurityNone, 37 | "aes-128-gcm": SecurityAES128GCM, 38 | "chacha20-poly1305": SecurityCHACHA20POLY1305, 39 | } 40 | 41 | var ( 42 | clientSessionCache tls.ClientSessionCache 43 | once sync.Once 44 | ) 45 | 46 | // Command types 47 | const ( 48 | CommandTCP byte = 1 49 | CommandUDP byte = 2 50 | ) 51 | 52 | // Addr types 53 | const ( 54 | AtypIPv4 byte = 1 55 | AtypDomainName byte = 2 56 | AtypIPv6 byte = 3 57 | ) 58 | 59 | // DstAddr store destination address 60 | type DstAddr struct { 61 | UDP bool 62 | AddrType byte 63 | Addr []byte 64 | Port uint 65 | } 66 | 67 | // Client is vmess connection generator 68 | type Client struct { 69 | user []*ID 70 | uuid *uuid.UUID 71 | security Security 72 | tls bool 73 | host string 74 | wsConfig *WebsocketConfig 75 | tlsConfig *tls.Config 76 | } 77 | 78 | // Config of vmess 79 | type Config struct { 80 | UUID string 81 | AlterID uint16 82 | Security string 83 | TLS bool 84 | HostName string 85 | Port string 86 | NetWork string 87 | WebSocketPath string 88 | WebSocketHeaders map[string]string 89 | SkipCertVerify bool 90 | SessionCache tls.ClientSessionCache 91 | } 92 | 93 | // New return a Conn with net.Conn and DstAddr 94 | func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { 95 | var err error 96 | r := rand.Intn(len(c.user)) 97 | if c.wsConfig != nil { 98 | conn, err = NewWebsocketConn(conn, c.wsConfig) 99 | if err != nil { 100 | return nil, err 101 | } 102 | } else if c.tls { 103 | conn = tls.Client(conn, c.tlsConfig) 104 | } 105 | return newConn(conn, c.user[r], dst, c.security) 106 | } 107 | 108 | // NewClient return Client instance 109 | func NewClient(config Config) (*Client, error) { 110 | uid, err := uuid.FromString(config.UUID) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | var security Security 116 | switch config.Security { 117 | case "aes-128-gcm": 118 | security = SecurityAES128GCM 119 | case "chacha20-poly1305": 120 | security = SecurityCHACHA20POLY1305 121 | case "none": 122 | security = SecurityNone 123 | case "auto": 124 | security = SecurityCHACHA20POLY1305 125 | if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { 126 | security = SecurityAES128GCM 127 | } 128 | default: 129 | return nil, fmt.Errorf("Unknown security type: %s", config.Security) 130 | } 131 | 132 | if config.NetWork != "" && config.NetWork != "ws" { 133 | return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) 134 | } 135 | 136 | header := http.Header{} 137 | for k, v := range config.WebSocketHeaders { 138 | header.Add(k, v) 139 | } 140 | 141 | host := net.JoinHostPort(config.HostName, config.Port) 142 | 143 | var tlsConfig *tls.Config 144 | if config.TLS { 145 | tlsConfig = &tls.Config{ 146 | ServerName: config.HostName, 147 | InsecureSkipVerify: config.SkipCertVerify, 148 | ClientSessionCache: config.SessionCache, 149 | } 150 | if tlsConfig.ClientSessionCache == nil { 151 | tlsConfig.ClientSessionCache = getClientSessionCache() 152 | } 153 | if host := header.Get("Host"); host != "" { 154 | tlsConfig.ServerName = host 155 | } 156 | } 157 | 158 | var wsConfig *WebsocketConfig 159 | if config.NetWork == "ws" { 160 | wsConfig = &WebsocketConfig{ 161 | Host: host, 162 | Path: config.WebSocketPath, 163 | Headers: header, 164 | TLS: config.TLS, 165 | TLSConfig: tlsConfig, 166 | } 167 | } 168 | 169 | return &Client{ 170 | user: newAlterIDs(newID(&uid), config.AlterID), 171 | uuid: &uid, 172 | security: security, 173 | tls: config.TLS, 174 | host: host, 175 | wsConfig: wsConfig, 176 | tlsConfig: tlsConfig, 177 | }, nil 178 | } 179 | 180 | func getClientSessionCache() tls.ClientSessionCache { 181 | once.Do(func() { 182 | clientSessionCache = tls.NewLRUClientSessionCache(128) 183 | }) 184 | return clientSessionCache 185 | } 186 | -------------------------------------------------------------------------------- /component/vmess/websocket.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/gorilla/websocket" 15 | ) 16 | 17 | type websocketConn struct { 18 | conn *websocket.Conn 19 | reader io.Reader 20 | remoteAddr net.Addr 21 | 22 | // https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency 23 | rMux sync.Mutex 24 | wMux sync.Mutex 25 | } 26 | 27 | type WebsocketConfig struct { 28 | Host string 29 | Path string 30 | Headers http.Header 31 | TLS bool 32 | TLSConfig *tls.Config 33 | } 34 | 35 | // Read implements net.Conn.Read() 36 | func (wsc *websocketConn) Read(b []byte) (int, error) { 37 | wsc.rMux.Lock() 38 | defer wsc.rMux.Unlock() 39 | for { 40 | reader, err := wsc.getReader() 41 | if err != nil { 42 | return 0, err 43 | } 44 | 45 | nBytes, err := reader.Read(b) 46 | if err == io.EOF { 47 | wsc.reader = nil 48 | continue 49 | } 50 | return nBytes, err 51 | } 52 | } 53 | 54 | // Write implements io.Writer. 55 | func (wsc *websocketConn) Write(b []byte) (int, error) { 56 | wsc.wMux.Lock() 57 | defer wsc.wMux.Unlock() 58 | if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { 59 | return 0, err 60 | } 61 | return len(b), nil 62 | } 63 | 64 | func (wsc *websocketConn) Close() error { 65 | var errors []string 66 | if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { 67 | errors = append(errors, err.Error()) 68 | } 69 | if err := wsc.conn.Close(); err != nil { 70 | errors = append(errors, err.Error()) 71 | } 72 | if len(errors) > 0 { 73 | return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ",")) 74 | } 75 | return nil 76 | } 77 | 78 | func (wsc *websocketConn) getReader() (io.Reader, error) { 79 | if wsc.reader != nil { 80 | return wsc.reader, nil 81 | } 82 | 83 | _, reader, err := wsc.conn.NextReader() 84 | if err != nil { 85 | return nil, err 86 | } 87 | wsc.reader = reader 88 | return reader, nil 89 | } 90 | 91 | func (wsc *websocketConn) LocalAddr() net.Addr { 92 | return wsc.conn.LocalAddr() 93 | } 94 | 95 | func (wsc *websocketConn) RemoteAddr() net.Addr { 96 | return wsc.remoteAddr 97 | } 98 | 99 | func (wsc *websocketConn) SetDeadline(t time.Time) error { 100 | if err := wsc.SetReadDeadline(t); err != nil { 101 | return err 102 | } 103 | return wsc.SetWriteDeadline(t) 104 | } 105 | 106 | func (wsc *websocketConn) SetReadDeadline(t time.Time) error { 107 | return wsc.conn.SetReadDeadline(t) 108 | } 109 | 110 | func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { 111 | return wsc.conn.SetWriteDeadline(t) 112 | } 113 | 114 | func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { 115 | dialer := &websocket.Dialer{ 116 | NetDial: func(network, addr string) (net.Conn, error) { 117 | return conn, nil 118 | }, 119 | ReadBufferSize: 4 * 1024, 120 | WriteBufferSize: 4 * 1024, 121 | HandshakeTimeout: time.Second * 8, 122 | } 123 | 124 | scheme := "ws" 125 | if c.TLS { 126 | scheme = "wss" 127 | dialer.TLSClientConfig = c.TLSConfig 128 | } 129 | 130 | host, port, _ := net.SplitHostPort(c.Host) 131 | if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { 132 | host = c.Host 133 | } 134 | 135 | uri := url.URL{ 136 | Scheme: scheme, 137 | Host: host, 138 | Path: c.Path, 139 | } 140 | 141 | headers := http.Header{} 142 | if c.Headers != nil { 143 | for k := range c.Headers { 144 | headers.Add(k, c.Headers.Get(k)) 145 | } 146 | } 147 | 148 | wsConn, resp, err := dialer.Dial(uri.String(), headers) 149 | if err != nil { 150 | reason := err.Error() 151 | if resp != nil { 152 | reason = resp.Status 153 | } 154 | return nil, fmt.Errorf("Dial %s error: %s", host, reason) 155 | } 156 | 157 | return &websocketConn{ 158 | conn: wsConn, 159 | remoteAddr: conn.RemoteAddr(), 160 | }, nil 161 | } 162 | -------------------------------------------------------------------------------- /config/initial.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/log" 11 | ) 12 | 13 | func downloadMMDB(path string) (err error) { 14 | resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb") 15 | if err != nil { 16 | return 17 | } 18 | defer resp.Body.Close() 19 | 20 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) 21 | if err != nil { 22 | return err 23 | } 24 | defer f.Close() 25 | _, err = io.Copy(f, resp.Body) 26 | 27 | return err 28 | } 29 | 30 | // Init prepare necessary files 31 | func Init(dir string) error { 32 | // initial homedir 33 | if _, err := os.Stat(dir); os.IsNotExist(err) { 34 | if err := os.MkdirAll(dir, 0777); err != nil { 35 | return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error()) 36 | } 37 | } 38 | 39 | // initial config.yaml 40 | if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { 41 | log.Infoln("Can't find config, create a initial config file") 42 | f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) 43 | if err != nil { 44 | return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error()) 45 | } 46 | f.Write([]byte(`port: 7890`)) 47 | f.Close() 48 | } 49 | 50 | // initial mmdb 51 | if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { 52 | log.Infoln("Can't find MMDB, start download") 53 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 54 | return fmt.Errorf("Can't download MMDB: %s", err.Error()) 55 | } 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /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 | Fallback 14 | Reject 15 | Selector 16 | Shadowsocks 17 | Snell 18 | Socks5 19 | Http 20 | URLTest 21 | Vmess 22 | LoadBalance 23 | ) 24 | 25 | type ServerAdapter interface { 26 | net.Conn 27 | Metadata() *Metadata 28 | } 29 | 30 | type Connection interface { 31 | Chains() Chain 32 | AppendToChains(adapter ProxyAdapter) 33 | } 34 | 35 | type Chain []string 36 | 37 | func (c Chain) String() string { 38 | switch len(c) { 39 | case 0: 40 | return "" 41 | case 1: 42 | return c[0] 43 | default: 44 | return fmt.Sprintf("%s[%s]", c[len(c)-1], c[0]) 45 | } 46 | } 47 | 48 | type Conn interface { 49 | net.Conn 50 | Connection 51 | } 52 | 53 | type PacketConn interface { 54 | net.PacketConn 55 | Connection 56 | WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error) 57 | } 58 | 59 | type ProxyAdapter interface { 60 | Name() string 61 | Type() AdapterType 62 | DialContext(ctx context.Context, metadata *Metadata) (Conn, error) 63 | DialUDP(metadata *Metadata) (PacketConn, error) 64 | SupportUDP() bool 65 | MarshalJSON() ([]byte, error) 66 | } 67 | 68 | type DelayHistory struct { 69 | Time time.Time `json:"time"` 70 | Delay uint16 `json:"delay"` 71 | } 72 | 73 | type Proxy interface { 74 | ProxyAdapter 75 | Alive() bool 76 | DelayHistory() []DelayHistory 77 | Dial(metadata *Metadata) (Conn, error) 78 | LastDelay() uint16 79 | URLTest(ctx context.Context, url string) (uint16, error) 80 | } 81 | 82 | // AdapterType is enum of adapter type 83 | type AdapterType int 84 | 85 | func (at AdapterType) String() string { 86 | switch at { 87 | case Direct: 88 | return "Direct" 89 | case Fallback: 90 | return "Fallback" 91 | case Reject: 92 | return "Reject" 93 | case Selector: 94 | return "Selector" 95 | case Shadowsocks: 96 | return "Shadowsocks" 97 | case Snell: 98 | return "Snell" 99 | case Socks5: 100 | return "Socks5" 101 | case Http: 102 | return "Http" 103 | case URLTest: 104 | return "URLTest" 105 | case Vmess: 106 | return "Vmess" 107 | case LoadBalance: 108 | return "LoadBalance" 109 | default: 110 | return "Unknown" 111 | } 112 | } 113 | 114 | // UDPPacket contains the data of UDP packet, and offers control/info of UDP packet's source 115 | type UDPPacket interface { 116 | // Data get the payload of UDP Packet 117 | Data() []byte 118 | 119 | // WriteBack writes the payload with source IP/Port equals addr 120 | // - variable source IP/Port is important to STUN 121 | // - if addr is not provided, WriteBack will wirte out UDP packet with SourceIP/Prot equals to origional Target, 122 | // this is important when using Fake-IP. 123 | WriteBack(b []byte, addr net.Addr) (n int, err error) 124 | 125 | // Close closes the underlaying connection. 126 | Close() error 127 | 128 | // LocalAddr returns the source IP/Port of packet 129 | LocalAddr() net.Addr 130 | } 131 | -------------------------------------------------------------------------------- /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 | MATCH 14 | ) 15 | 16 | type RuleType int 17 | 18 | func (rt RuleType) String() string { 19 | switch rt { 20 | case Domain: 21 | return "Domain" 22 | case DomainSuffix: 23 | return "DomainSuffix" 24 | case DomainKeyword: 25 | return "DomainKeyword" 26 | case GEOIP: 27 | return "GeoIP" 28 | case IPCIDR: 29 | return "IPCIDR" 30 | case SrcIPCIDR: 31 | return "SrcIPCIDR" 32 | case SrcPort: 33 | return "SrcPort" 34 | case DstPort: 35 | return "DstPort" 36 | case MATCH: 37 | return "Match" 38 | default: 39 | return "Unknown" 40 | } 41 | } 42 | 43 | type Rule interface { 44 | RuleType() RuleType 45 | Match(metadata *Metadata) bool 46 | Adapter() string 47 | Payload() string 48 | NoResolveIP() bool 49 | } 50 | -------------------------------------------------------------------------------- /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 := dialer.Dialer() 38 | if dialer.DialHook != nil { 39 | network := "udp" 40 | if strings.HasPrefix(c.Client.Net, "tcp") { 41 | network = "tcp" 42 | } 43 | dialer.DialHook(d, network, ip) 44 | } 45 | 46 | c.Client.Dialer = d 47 | 48 | // miekg/dns ExchangeContext doesn't respond to context cancel. 49 | // this is a workaround 50 | type result struct { 51 | msg *D.Msg 52 | err error 53 | } 54 | ch := make(chan result, 1) 55 | go func() { 56 | msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) 57 | ch <- result{msg, err} 58 | }() 59 | 60 | select { 61 | case <-ctx.Done(): 62 | return nil, ctx.Err() 63 | case ret := <-ch: 64 | return ret.msg, ret.err 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /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+"?bla=foo:443", 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 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 80 | host, port, err := net.SplitHostPort(addr) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | ip, err := r.ResolveIPv4(host) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) 91 | }, 92 | }, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /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 | D.HandleFailed(w, r) 22 | return 23 | } else if q.Qtype != D.TypeA { 24 | next(w, r) 25 | return 26 | } 27 | 28 | host := strings.TrimRight(q.Name, ".") 29 | if fakePool.LookupHost(host) { 30 | next(w, r) 31 | return 32 | } 33 | 34 | rr := &D.A{} 35 | rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} 36 | ip := fakePool.Lookup(host) 37 | rr.A = ip 38 | msg := r.Copy() 39 | msg.Answer = []D.RR{rr} 40 | 41 | setMsgTTL(msg, 1) 42 | msg.SetRcode(r, msg.Rcode) 43 | msg.Authoritative = true 44 | w.WriteMsg(msg) 45 | return 46 | } 47 | } 48 | } 49 | 50 | func withResolver(resolver *Resolver) handler { 51 | return func(w D.ResponseWriter, r *D.Msg) { 52 | msg, err := resolver.Exchange(r) 53 | if err != nil { 54 | q := r.Question[0] 55 | log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err) 56 | D.HandleFailed(w, r) 57 | return 58 | } 59 | msg.SetRcode(r, msg.Rcode) 60 | msg.Authoritative = true 61 | w.WriteMsg(msg) 62 | return 63 | } 64 | } 65 | 66 | func compose(middlewares []middleware, endpoint handler) handler { 67 | length := len(middlewares) 68 | h := endpoint 69 | for i := length - 1; i >= 0; i-- { 70 | middleware := middlewares[i] 71 | h = middleware(h) 72 | } 73 | 74 | return h 75 | } 76 | 77 | func newHandler(resolver *Resolver) handler { 78 | middlewares := []middleware{} 79 | 80 | if resolver.FakeIPEnabled() { 81 | middlewares = append(middlewares, withFakeIP(resolver.pool)) 82 | } 83 | 84 | return compose(middlewares, withResolver(resolver)) 85 | } 86 | -------------------------------------------------------------------------------- /dns/server.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | D "github.com/miekg/dns" 7 | ) 8 | 9 | var ( 10 | address string 11 | server = &Server{} 12 | 13 | dnsDefaultTTL uint32 = 600 14 | ) 15 | 16 | type Server struct { 17 | *D.Server 18 | handler handler 19 | } 20 | 21 | func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { 22 | if len(r.Question) == 0 { 23 | D.HandleFailed(w, r) 24 | return 25 | } 26 | 27 | s.handler(w, r) 28 | } 29 | 30 | func (s *Server) setHandler(handler handler) { 31 | s.handler = handler 32 | } 33 | 34 | func ReCreateServer(addr string, resolver *Resolver) error { 35 | if addr == address && resolver != nil { 36 | handler := newHandler(resolver) 37 | server.setHandler(handler) 38 | return nil 39 | } 40 | 41 | if server.Server != nil { 42 | server.Shutdown() 43 | address = "" 44 | } 45 | 46 | _, port, err := net.SplitHostPort(addr) 47 | if port == "0" || port == "" || err != nil { 48 | return nil 49 | } 50 | 51 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | p, err := net.ListenUDP("udp", udpAddr) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | address = addr 62 | handler := newHandler(resolver) 63 | server = &Server{handler: handler} 64 | server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} 65 | 66 | go func() { 67 | server.ActivateAndServe() 68 | }() 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /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 | yaml "gopkg.in/yaml.v2" 15 | ) 16 | 17 | var ( 18 | // EnhancedModeMapping is a mapping for EnhancedMode enum 19 | EnhancedModeMapping = map[string]EnhancedMode{ 20 | NORMAL.String(): NORMAL, 21 | FAKEIP.String(): FAKEIP, 22 | MAPPING.String(): MAPPING, 23 | } 24 | ) 25 | 26 | const ( 27 | NORMAL EnhancedMode = iota 28 | FAKEIP 29 | MAPPING 30 | ) 31 | 32 | type EnhancedMode int 33 | 34 | // UnmarshalYAML unserialize EnhancedMode with yaml 35 | func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error { 36 | var tp string 37 | if err := unmarshal(&tp); err != nil { 38 | return err 39 | } 40 | mode, exist := EnhancedModeMapping[tp] 41 | if !exist { 42 | return errors.New("invalid mode") 43 | } 44 | *e = mode 45 | return nil 46 | } 47 | 48 | // MarshalYAML serialize EnhancedMode with yaml 49 | func (e EnhancedMode) MarshalYAML() ([]byte, error) { 50 | return yaml.Marshal(e.String()) 51 | } 52 | 53 | // UnmarshalJSON unserialize EnhancedMode with json 54 | func (e *EnhancedMode) UnmarshalJSON(data []byte) error { 55 | var tp string 56 | json.Unmarshal(data, &tp) 57 | mode, exist := EnhancedModeMapping[tp] 58 | if !exist { 59 | return errors.New("invalid mode") 60 | } 61 | *e = mode 62 | return nil 63 | } 64 | 65 | // MarshalJSON serialize EnhancedMode with json 66 | func (e EnhancedMode) MarshalJSON() ([]byte, error) { 67 | return json.Marshal(e.String()) 68 | } 69 | 70 | func (e EnhancedMode) String() string { 71 | switch e { 72 | case NORMAL: 73 | return "normal" 74 | case FAKEIP: 75 | return "fake-ip" 76 | case MAPPING: 77 | return "redir-host" 78 | default: 79 | return "unknown" 80 | } 81 | } 82 | 83 | func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { 84 | var ttl time.Duration 85 | switch { 86 | case len(msg.Answer) != 0: 87 | ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second 88 | case len(msg.Ns) != 0: 89 | ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second 90 | case len(msg.Extra) != 0: 91 | ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second 92 | default: 93 | log.Debugln("[DNS] response msg error: %#v", msg) 94 | return 95 | } 96 | 97 | c.Put(key, msg.Copy(), ttl) 98 | } 99 | 100 | func setMsgTTL(msg *D.Msg, ttl uint32) { 101 | for _, answer := range msg.Answer { 102 | answer.Header().Ttl = ttl 103 | } 104 | 105 | for _, ns := range msg.Ns { 106 | ns.Header().Ttl = ttl 107 | } 108 | 109 | for _, extra := range msg.Extra { 110 | extra.Header().Ttl = ttl 111 | } 112 | } 113 | 114 | func isIPRequest(q D.Question) bool { 115 | if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) { 116 | return true 117 | } 118 | return false 119 | } 120 | 121 | func transform(servers []NameServer, resolver *Resolver) []dnsClient { 122 | ret := []dnsClient{} 123 | for _, s := range servers { 124 | if s.Net == "https" { 125 | ret = append(ret, newDoHClient(s.Addr, resolver)) 126 | continue 127 | } 128 | 129 | host, port, _ := net.SplitHostPort(s.Addr) 130 | ret = append(ret, &client{ 131 | Client: &D.Client{ 132 | Net: s.Net, 133 | TLSConfig: &tls.Config{ 134 | ClientSessionCache: globalSessionCache, 135 | // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 136 | NextProtos: []string{"dns"}, 137 | }, 138 | UDPSize: 4096, 139 | Timeout: 5 * time.Second, 140 | }, 141 | port: port, 142 | host: host, 143 | r: resolver, 144 | }) 145 | } 146 | return ret 147 | } 148 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WhoJave/clash/612421a935bcf37b7c6bae19ce2d42aceb0a3d91/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.0.3+incompatible 9 | github.com/go-chi/cors v1.0.0 10 | github.com/go-chi/render v1.0.1 11 | github.com/gofrs/uuid v3.2.0+incompatible 12 | github.com/gorilla/websocket v1.4.1 13 | github.com/miekg/dns v1.1.27 14 | github.com/oschwald/geoip2-golang v1.4.0 15 | github.com/sirupsen/logrus v1.4.2 16 | github.com/stretchr/testify v1.5.1 17 | golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d 18 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b 19 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e 20 | gopkg.in/eapache/channels.v1 v1.1.0 21 | gopkg.in/yaml.v2 v2.2.8 22 | ) 23 | -------------------------------------------------------------------------------- /hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Register qemu-*-static for all supported processors except the 3 | # current one, but also remove all registered binfmt_misc before 4 | docker run --rm --privileged multiarch/qemu-user-static:register --reset --credential yes 5 | -------------------------------------------------------------------------------- /hub/hub.go: -------------------------------------------------------------------------------- 1 | package hub 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/hub/executor" 5 | "github.com/Dreamacro/clash/hub/route" 6 | ) 7 | 8 | // Parse call at the beginning of clash 9 | func Parse() error { 10 | cfg, err := executor.Parse() 11 | if err != nil { 12 | return err 13 | } 14 | 15 | if cfg.General.ExternalUI != "" { 16 | route.SetUIPath(cfg.General.ExternalUI) 17 | } 18 | 19 | if cfg.General.ExternalController != "" { 20 | go route.Start(cfg.General.ExternalController, cfg.General.Secret) 21 | } 22 | 23 | executor.ApplyConfig(cfg, true) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /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 | AllowLan *bool `json:"allow-lan"` 30 | BindAddress *string `json:"bind-address"` 31 | Mode *tunnel.TunnelMode `json:"mode"` 32 | LogLevel *log.LogLevel `json:"log-level"` 33 | } 34 | 35 | func getConfigs(w http.ResponseWriter, r *http.Request) { 36 | general := executor.GetGeneral() 37 | render.JSON(w, r, general) 38 | } 39 | 40 | func pointerOrDefault(p *int, def int) int { 41 | if p != nil { 42 | return *p 43 | } 44 | 45 | return def 46 | } 47 | 48 | func patchConfigs(w http.ResponseWriter, r *http.Request) { 49 | general := &configSchema{} 50 | if err := render.DecodeJSON(r.Body, general); err != nil { 51 | render.Status(r, http.StatusBadRequest) 52 | render.JSON(w, r, ErrBadRequest) 53 | return 54 | } 55 | 56 | if general.AllowLan != nil { 57 | P.SetAllowLan(*general.AllowLan) 58 | } 59 | 60 | if general.BindAddress != nil { 61 | P.SetBindAddress(*general.BindAddress) 62 | } 63 | 64 | ports := P.GetPorts() 65 | P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) 66 | P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) 67 | P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) 68 | 69 | if general.Mode != nil { 70 | tunnel.SetMode(*general.Mode) 71 | } 72 | 73 | if general.LogLevel != nil { 74 | log.SetLevel(*general.LogLevel) 75 | } 76 | 77 | render.NoContent(w, r) 78 | } 79 | 80 | type updateConfigRequest struct { 81 | Path string `json:"path"` 82 | Payload string `json:"payload"` 83 | } 84 | 85 | func updateConfigs(w http.ResponseWriter, r *http.Request) { 86 | req := updateConfigRequest{} 87 | if err := render.DecodeJSON(r.Body, &req); err != nil { 88 | render.Status(r, http.StatusBadRequest) 89 | render.JSON(w, r, ErrBadRequest) 90 | return 91 | } 92 | 93 | force := r.URL.Query().Get("force") == "true" 94 | var cfg *config.Config 95 | var err error 96 | 97 | if req.Payload != "" { 98 | cfg, err = executor.ParseWithBytes([]byte(req.Payload)) 99 | if err != nil { 100 | render.Status(r, http.StatusBadRequest) 101 | render.JSON(w, r, newError(err.Error())) 102 | return 103 | } 104 | } else { 105 | if !filepath.IsAbs(req.Path) { 106 | render.Status(r, http.StatusBadRequest) 107 | render.JSON(w, r, newError("path is not a absoluted path")) 108 | return 109 | } 110 | 111 | cfg, err = executor.ParseWithPath(req.Path) 112 | if err != nil { 113 | render.Status(r, http.StatusBadRequest) 114 | render.JSON(w, r, newError(err.Error())) 115 | return 116 | } 117 | } 118 | 119 | executor.ApplyConfig(cfg, force) 120 | render.NoContent(w, r) 121 | } 122 | -------------------------------------------------------------------------------- /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/proxies.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/Dreamacro/clash/adapters/outbound" 11 | "github.com/Dreamacro/clash/adapters/outboundgroup" 12 | C "github.com/Dreamacro/clash/constant" 13 | "github.com/Dreamacro/clash/tunnel" 14 | 15 | "github.com/go-chi/chi" 16 | "github.com/go-chi/render" 17 | ) 18 | 19 | func proxyRouter() http.Handler { 20 | r := chi.NewRouter() 21 | r.Get("/", getProxies) 22 | 23 | r.Route("/{name}", func(r chi.Router) { 24 | r.Use(parseProxyName, findProxyByName) 25 | r.Get("/", getProxy) 26 | r.Get("/delay", getProxyDelay) 27 | r.Put("/", updateProxy) 28 | }) 29 | return r 30 | } 31 | 32 | func parseProxyName(next http.Handler) http.Handler { 33 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 | name := getEscapeParam(r, "name") 35 | ctx := context.WithValue(r.Context(), CtxKeyProxyName, name) 36 | next.ServeHTTP(w, r.WithContext(ctx)) 37 | }) 38 | } 39 | 40 | func findProxyByName(next http.Handler) http.Handler { 41 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 | name := r.Context().Value(CtxKeyProxyName).(string) 43 | proxies := tunnel.Proxies() 44 | proxy, exist := proxies[name] 45 | if !exist { 46 | render.Status(r, http.StatusNotFound) 47 | render.JSON(w, r, ErrNotFound) 48 | return 49 | } 50 | 51 | ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy) 52 | next.ServeHTTP(w, r.WithContext(ctx)) 53 | }) 54 | } 55 | 56 | func getProxies(w http.ResponseWriter, r *http.Request) { 57 | proxies := tunnel.Proxies() 58 | render.JSON(w, r, render.M{ 59 | "proxies": proxies, 60 | }) 61 | } 62 | 63 | func getProxy(w http.ResponseWriter, r *http.Request) { 64 | proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) 65 | render.JSON(w, r, proxy) 66 | } 67 | 68 | type UpdateProxyRequest struct { 69 | Name string `json:"name"` 70 | } 71 | 72 | func updateProxy(w http.ResponseWriter, r *http.Request) { 73 | req := UpdateProxyRequest{} 74 | if err := render.DecodeJSON(r.Body, &req); err != nil { 75 | render.Status(r, http.StatusBadRequest) 76 | render.JSON(w, r, ErrBadRequest) 77 | return 78 | } 79 | 80 | proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy) 81 | selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector) 82 | if !ok { 83 | render.Status(r, http.StatusBadRequest) 84 | render.JSON(w, r, newError("Must be a Selector")) 85 | return 86 | } 87 | 88 | if err := selector.Set(req.Name); err != nil { 89 | render.Status(r, http.StatusBadRequest) 90 | render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error()))) 91 | return 92 | } 93 | 94 | render.NoContent(w, r) 95 | } 96 | 97 | func getProxyDelay(w http.ResponseWriter, r *http.Request) { 98 | query := r.URL.Query() 99 | url := query.Get("url") 100 | timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) 101 | if err != nil { 102 | render.Status(r, http.StatusBadRequest) 103 | render.JSON(w, r, ErrBadRequest) 104 | return 105 | } 106 | 107 | proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) 108 | 109 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) 110 | defer cancel() 111 | 112 | delay, err := proxy.URLTest(ctx, url) 113 | if ctx.Err() != nil { 114 | render.Status(r, http.StatusGatewayTimeout) 115 | render.JSON(w, r, ErrRequestTimeout) 116 | return 117 | } 118 | 119 | if err != nil || delay == 0 { 120 | render.Status(r, http.StatusServiceUnavailable) 121 | render.JSON(w, r, newError("An error occurred in the delay test")) 122 | return 123 | } 124 | 125 | render.JSON(w, r, render.M{ 126 | "delay": delay, 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /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 | func (l LogLevel) String() string { 59 | switch l { 60 | case INFO: 61 | return "info" 62 | case WARNING: 63 | return "warning" 64 | case ERROR: 65 | return "error" 66 | case DEBUG: 67 | return "debug" 68 | case SILENT: 69 | return "silent" 70 | default: 71 | return "unknown" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /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 | version bool 22 | testConfig bool 23 | homeDir string 24 | configFile string 25 | ) 26 | 27 | func init() { 28 | flag.StringVar(&homeDir, "d", "", "set configuration directory") 29 | flag.StringVar(&configFile, "f", "", "specify configuration file") 30 | flag.BoolVar(&version, "v", false, "show current version of clash") 31 | flag.BoolVar(&testConfig, "t", false, "test configuration and exit") 32 | flag.Parse() 33 | } 34 | 35 | func main() { 36 | if version { 37 | fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime) 38 | return 39 | } 40 | 41 | if homeDir != "" { 42 | if !filepath.IsAbs(homeDir) { 43 | currentDir, _ := os.Getwd() 44 | homeDir = filepath.Join(currentDir, homeDir) 45 | } 46 | C.SetHomeDir(homeDir) 47 | } 48 | 49 | if configFile != "" { 50 | if !filepath.IsAbs(configFile) { 51 | currentDir, _ := os.Getwd() 52 | configFile = filepath.Join(currentDir, configFile) 53 | } 54 | C.SetConfig(configFile) 55 | } else { 56 | configFile := filepath.Join(C.Path.HomeDir(), C.Path.Config()) 57 | C.SetConfig(configFile) 58 | } 59 | 60 | if err := config.Init(C.Path.HomeDir()); err != nil { 61 | log.Fatalln("Initial configuration directory error: %s", err.Error()) 62 | } 63 | 64 | if testConfig { 65 | if _, err := executor.Parse(); err != nil { 66 | log.Errorln(err.Error()) 67 | fmt.Printf("configuration file %s test failed\n", constant.Path.Config()) 68 | os.Exit(1) 69 | } 70 | fmt.Printf("configuration file %s test is successful\n", constant.Path.Config()) 71 | return 72 | } 73 | 74 | if err := hub.Parse(); err != nil { 75 | log.Fatalln("Parse config error: %s", err.Error()) 76 | } 77 | 78 | sigCh := make(chan os.Signal, 1) 79 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 80 | <-sigCh 81 | } 82 | -------------------------------------------------------------------------------- /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/listener.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | 8 | "github.com/Dreamacro/clash/proxy/http" 9 | "github.com/Dreamacro/clash/proxy/redir" 10 | "github.com/Dreamacro/clash/proxy/socks" 11 | ) 12 | 13 | var ( 14 | allowLan = false 15 | bindAddress = "*" 16 | 17 | socksListener *socks.SockListener 18 | socksUDPListener *socks.SockUDPListener 19 | httpListener *http.HttpListener 20 | redirListener *redir.RedirListener 21 | ) 22 | 23 | type listener interface { 24 | Close() 25 | Address() string 26 | } 27 | 28 | type Ports struct { 29 | Port int `json:"port"` 30 | SocksPort int `json:"socks-port"` 31 | RedirPort int `json:"redir-port"` 32 | } 33 | 34 | func AllowLan() bool { 35 | return allowLan 36 | } 37 | 38 | func BindAddress() string { 39 | return bindAddress 40 | } 41 | 42 | func SetAllowLan(al bool) { 43 | allowLan = al 44 | } 45 | 46 | func SetBindAddress(host string) { 47 | bindAddress = host 48 | } 49 | 50 | func ReCreateHTTP(port int) error { 51 | addr := genAddr(bindAddress, port, allowLan) 52 | 53 | if httpListener != nil { 54 | if httpListener.Address() == addr { 55 | return nil 56 | } 57 | httpListener.Close() 58 | httpListener = nil 59 | } 60 | 61 | if portIsZero(addr) { 62 | return nil 63 | } 64 | 65 | var err error 66 | httpListener, err = http.NewHttpProxy(addr) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func ReCreateSocks(port int) error { 75 | addr := genAddr(bindAddress, port, allowLan) 76 | 77 | shouldTCPIgnore := false 78 | shouldUDPIgnore := false 79 | 80 | if socksListener != nil { 81 | if socksListener.Address() != addr { 82 | socksListener.Close() 83 | socksListener = nil 84 | } else { 85 | shouldTCPIgnore = true 86 | } 87 | } 88 | 89 | if socksUDPListener != nil { 90 | if socksUDPListener.Address() != addr { 91 | socksUDPListener.Close() 92 | socksUDPListener = nil 93 | } else { 94 | shouldUDPIgnore = true 95 | } 96 | } 97 | 98 | if shouldTCPIgnore && shouldUDPIgnore { 99 | return nil 100 | } 101 | 102 | if portIsZero(addr) { 103 | return nil 104 | } 105 | 106 | tcpListener, err := socks.NewSocksProxy(addr) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | udpListener, err := socks.NewSocksUDPProxy(addr) 112 | if err != nil { 113 | tcpListener.Close() 114 | return err 115 | } 116 | 117 | socksListener = tcpListener 118 | socksUDPListener = udpListener 119 | 120 | return nil 121 | } 122 | 123 | func ReCreateRedir(port int) error { 124 | addr := genAddr(bindAddress, port, allowLan) 125 | 126 | if redirListener != nil { 127 | if redirListener.Address() == addr { 128 | return nil 129 | } 130 | redirListener.Close() 131 | redirListener = nil 132 | } 133 | 134 | if portIsZero(addr) { 135 | return nil 136 | } 137 | 138 | var err error 139 | redirListener, err = redir.NewRedirProxy(addr) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | return nil 145 | } 146 | 147 | // GetPorts return the ports of proxy servers 148 | func GetPorts() *Ports { 149 | ports := &Ports{} 150 | 151 | if httpListener != nil { 152 | _, portStr, _ := net.SplitHostPort(httpListener.Address()) 153 | port, _ := strconv.Atoi(portStr) 154 | ports.Port = port 155 | } 156 | 157 | if socksListener != nil { 158 | _, portStr, _ := net.SplitHostPort(socksListener.Address()) 159 | port, _ := strconv.Atoi(portStr) 160 | ports.SocksPort = port 161 | } 162 | 163 | if redirListener != nil { 164 | _, portStr, _ := net.SplitHostPort(redirListener.Address()) 165 | port, _ := strconv.Atoi(portStr) 166 | ports.RedirPort = port 167 | } 168 | 169 | return ports 170 | } 171 | 172 | func portIsZero(addr string) bool { 173 | _, port, err := net.SplitHostPort(addr) 174 | if port == "0" || port == "" || err != nil { 175 | return true 176 | } 177 | return false 178 | } 179 | 180 | func genAddr(host string, port int, allowLan bool) string { 181 | if allowLan { 182 | if host == "*" { 183 | return fmt.Sprintf(":%d", port) 184 | } else { 185 | return fmt.Sprintf("%s:%d", host, port) 186 | } 187 | } 188 | 189 | return fmt.Sprintf("127.0.0.1:%d", port) 190 | } 191 | -------------------------------------------------------------------------------- /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, C.TCP)) 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/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 | conn.(*net.TCPConn).SetKeepAlive(true) 62 | if command == socks5.CmdUDPAssociate { 63 | defer conn.Close() 64 | io.Copy(ioutil.Discard, conn) 65 | return 66 | } 67 | tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) 68 | } 69 | -------------------------------------------------------------------------------- /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/component/socks5" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/tunnel" 11 | ) 12 | 13 | type SockUDPListener struct { 14 | net.PacketConn 15 | address string 16 | closed bool 17 | } 18 | 19 | func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { 20 | l, err := net.ListenPacket("udp", addr) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | sl := &SockUDPListener{l, addr, false} 26 | go func() { 27 | for { 28 | buf := pool.BufPool.Get().([]byte) 29 | n, remoteAddr, err := l.ReadFrom(buf) 30 | if err != nil { 31 | pool.BufPool.Put(buf[:cap(buf)]) 32 | if sl.closed { 33 | break 34 | } 35 | continue 36 | } 37 | handleSocksUDP(l, buf[:n], remoteAddr) 38 | } 39 | }() 40 | 41 | return sl, nil 42 | } 43 | 44 | func (l *SockUDPListener) Close() error { 45 | l.closed = true 46 | return l.PacketConn.Close() 47 | } 48 | 49 | func (l *SockUDPListener) Address() string { 50 | return l.address 51 | } 52 | 53 | func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { 54 | target, payload, err := socks5.DecodeUDPPacket(buf) 55 | if err != nil { 56 | // Unresolved UDP packet, return buffer to the pool 57 | pool.BufPool.Put(buf[:cap(buf)]) 58 | return 59 | } 60 | packet := &fakeConn{ 61 | PacketConn: pc, 62 | rAddr: addr, 63 | payload: payload, 64 | bufRef: buf, 65 | } 66 | tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) 67 | } 68 | -------------------------------------------------------------------------------- /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 fakeConn struct { 11 | net.PacketConn 12 | rAddr net.Addr 13 | payload []byte 14 | bufRef []byte 15 | } 16 | 17 | func (c *fakeConn) Data() []byte { 18 | return c.payload 19 | } 20 | 21 | // WriteBack wirtes UDP packet with source(ip, port) = `addr` 22 | func (c *fakeConn) 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.PacketConn.WriteTo(packet, c.rAddr) 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *fakeConn) LocalAddr() net.Addr { 32 | return c.rAddr 33 | } 34 | 35 | func (c *fakeConn) Close() error { 36 | err := c.PacketConn.Close() 37 | pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) 38 | return err 39 | } 40 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /tunnel/connection.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | adapters "github.com/Dreamacro/clash/adapters/inbound" 12 | C "github.com/Dreamacro/clash/constant" 13 | 14 | "github.com/Dreamacro/clash/common/pool" 15 | ) 16 | 17 | func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { 18 | req := request.R 19 | host := req.Host 20 | 21 | inboundReeder := bufio.NewReader(request) 22 | outboundReeder := bufio.NewReader(outbound) 23 | 24 | for { 25 | keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" 26 | 27 | req.Header.Set("Connection", "close") 28 | req.RequestURI = "" 29 | adapters.RemoveHopByHopHeaders(req.Header) 30 | err := req.Write(outbound) 31 | if err != nil { 32 | break 33 | } 34 | 35 | handleResponse: 36 | resp, err := http.ReadResponse(outboundReeder, req) 37 | if err != nil { 38 | break 39 | } 40 | adapters.RemoveHopByHopHeaders(resp.Header) 41 | 42 | if resp.StatusCode == http.StatusContinue { 43 | err = resp.Write(request) 44 | if err != nil { 45 | break 46 | } 47 | goto handleResponse 48 | } 49 | 50 | if keepAlive || resp.ContentLength >= 0 { 51 | resp.Header.Set("Proxy-Connection", "keep-alive") 52 | resp.Header.Set("Connection", "keep-alive") 53 | resp.Header.Set("Keep-Alive", "timeout=4") 54 | resp.Close = false 55 | } else { 56 | resp.Close = true 57 | } 58 | err = resp.Write(request) 59 | if err != nil || resp.Close { 60 | break 61 | } 62 | 63 | // even if resp.Write write body to the connection, but some http request have to Copy to close it 64 | buf := pool.BufPool.Get().([]byte) 65 | _, err = io.CopyBuffer(request, resp.Body, buf) 66 | pool.BufPool.Put(buf[:cap(buf)]) 67 | if err != nil && err != io.EOF { 68 | break 69 | } 70 | 71 | req, err = http.ReadRequest(inboundReeder) 72 | if err != nil { 73 | break 74 | } 75 | 76 | // Sometimes firefox just open a socket to process multiple domains in HTTP 77 | // The temporary solution is close connection when encountering different HOST 78 | if req.Host != host { 79 | break 80 | } 81 | } 82 | } 83 | 84 | func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) { 85 | if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil { 86 | return 87 | } 88 | DefaultManager.Upload() <- int64(len(packet.Data())) 89 | } 90 | 91 | func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { 92 | buf := pool.BufPool.Get().([]byte) 93 | defer pool.BufPool.Put(buf[:cap(buf)]) 94 | defer natTable.Delete(key) 95 | defer pc.Close() 96 | 97 | for { 98 | pc.SetReadDeadline(time.Now().Add(udpTimeout)) 99 | n, from, err := pc.ReadFrom(buf) 100 | if err != nil { 101 | return 102 | } 103 | 104 | n, err = packet.WriteBack(buf[:n], from) 105 | if err != nil { 106 | return 107 | } 108 | DefaultManager.Download() <- int64(n) 109 | } 110 | } 111 | 112 | func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { 113 | relay(request, outbound) 114 | } 115 | 116 | // relay copies between left and right bidirectionally. 117 | func relay(leftConn, rightConn net.Conn) { 118 | ch := make(chan error) 119 | 120 | go func() { 121 | buf := pool.BufPool.Get().([]byte) 122 | _, err := io.CopyBuffer(leftConn, rightConn, buf) 123 | pool.BufPool.Put(buf[:cap(buf)]) 124 | leftConn.SetReadDeadline(time.Now()) 125 | ch <- err 126 | }() 127 | 128 | buf := pool.BufPool.Get().([]byte) 129 | io.CopyBuffer(rightConn, leftConn, buf) 130 | pool.BufPool.Put(buf[:cap(buf)]) 131 | rightConn.SetReadDeadline(time.Now()) 132 | <-ch 133 | } 134 | -------------------------------------------------------------------------------- /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) handle() { 74 | go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) 75 | go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) 76 | } 77 | 78 | func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) { 79 | ticker := time.NewTicker(time.Second) 80 | for { 81 | select { 82 | case n := <-ch: 83 | *temp += n 84 | *total += n 85 | case <-ticker.C: 86 | *blip = *temp 87 | *temp = 0 88 | } 89 | } 90 | } 91 | 92 | type Snapshot struct { 93 | DownloadTotal int64 `json:"downloadTotal"` 94 | UploadTotal int64 `json:"uploadTotal"` 95 | Connections []tracker `json:"connections"` 96 | } 97 | -------------------------------------------------------------------------------- /tunnel/mode.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | type TunnelMode int 9 | 10 | var ( 11 | // ModeMapping is a mapping for Mode enum 12 | ModeMapping = map[string]TunnelMode{ 13 | Global.String(): Global, 14 | Rule.String(): Rule, 15 | Direct.String(): Direct, 16 | } 17 | ) 18 | 19 | const ( 20 | Global TunnelMode = iota 21 | Rule 22 | Direct 23 | ) 24 | 25 | // UnmarshalJSON unserialize Mode 26 | func (m *TunnelMode) UnmarshalJSON(data []byte) error { 27 | var tp string 28 | json.Unmarshal(data, &tp) 29 | mode, exist := ModeMapping[tp] 30 | if !exist { 31 | return errors.New("invalid mode") 32 | } 33 | *m = mode 34 | return nil 35 | } 36 | 37 | // UnmarshalYAML unserialize Mode with yaml 38 | func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error { 39 | var tp string 40 | unmarshal(&tp) 41 | mode, exist := ModeMapping[tp] 42 | if !exist { 43 | return errors.New("invalid mode") 44 | } 45 | *m = mode 46 | return nil 47 | } 48 | 49 | // MarshalJSON serialize Mode 50 | func (m TunnelMode) MarshalJSON() ([]byte, error) { 51 | return json.Marshal(m.String()) 52 | } 53 | 54 | func (m TunnelMode) String() string { 55 | switch m { 56 | case Global: 57 | return "Global" 58 | case Rule: 59 | return "Rule" 60 | case Direct: 61 | return "Direct" 62 | default: 63 | return "Unknown" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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 | } 15 | 16 | type trackerInfo struct { 17 | UUID uuid.UUID `json:"id"` 18 | Metadata *C.Metadata `json:"metadata"` 19 | UploadTotal int64 `json:"upload"` 20 | DownloadTotal int64 `json:"download"` 21 | Start time.Time `json:"start"` 22 | Chain C.Chain `json:"chains"` 23 | Rule string `json:"rule"` 24 | } 25 | 26 | type tcpTracker struct { 27 | C.Conn `json:"-"` 28 | *trackerInfo 29 | manager *Manager 30 | } 31 | 32 | func (tt *tcpTracker) ID() string { 33 | return tt.UUID.String() 34 | } 35 | 36 | func (tt *tcpTracker) Read(b []byte) (int, error) { 37 | n, err := tt.Conn.Read(b) 38 | download := int64(n) 39 | tt.manager.Download() <- download 40 | tt.DownloadTotal += download 41 | return n, err 42 | } 43 | 44 | func (tt *tcpTracker) Write(b []byte) (int, error) { 45 | n, err := tt.Conn.Write(b) 46 | upload := int64(n) 47 | tt.manager.Upload() <- upload 48 | tt.UploadTotal += upload 49 | return n, err 50 | } 51 | 52 | func (tt *tcpTracker) Close() error { 53 | tt.manager.Leave(tt) 54 | return tt.Conn.Close() 55 | } 56 | 57 | func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { 58 | uuid, _ := uuid.NewV4() 59 | ruleType := "" 60 | if rule != nil { 61 | ruleType = rule.RuleType().String() 62 | } 63 | 64 | t := &tcpTracker{ 65 | Conn: conn, 66 | manager: manager, 67 | trackerInfo: &trackerInfo{ 68 | UUID: uuid, 69 | Start: time.Now(), 70 | Metadata: metadata, 71 | Chain: conn.Chains(), 72 | Rule: ruleType, 73 | }, 74 | } 75 | 76 | manager.Join(t) 77 | return t 78 | } 79 | 80 | type udpTracker struct { 81 | C.PacketConn `json:"-"` 82 | *trackerInfo 83 | manager *Manager 84 | } 85 | 86 | func (ut *udpTracker) ID() string { 87 | return ut.UUID.String() 88 | } 89 | 90 | func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { 91 | n, addr, err := ut.PacketConn.ReadFrom(b) 92 | download := int64(n) 93 | ut.manager.Download() <- download 94 | ut.DownloadTotal += download 95 | return n, addr, err 96 | } 97 | 98 | func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { 99 | n, err := ut.PacketConn.WriteTo(b, addr) 100 | upload := int64(n) 101 | ut.manager.Upload() <- upload 102 | ut.UploadTotal += upload 103 | return n, err 104 | } 105 | 106 | func (ut *udpTracker) Close() error { 107 | ut.manager.Leave(ut) 108 | return ut.PacketConn.Close() 109 | } 110 | 111 | func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { 112 | uuid, _ := uuid.NewV4() 113 | ruleType := "" 114 | if rule != nil { 115 | ruleType = rule.RuleType().String() 116 | } 117 | 118 | ut := &udpTracker{ 119 | PacketConn: conn, 120 | manager: manager, 121 | trackerInfo: &trackerInfo{ 122 | UUID: uuid, 123 | Start: time.Now(), 124 | Metadata: metadata, 125 | Chain: conn.Chains(), 126 | Rule: ruleType, 127 | }, 128 | } 129 | 130 | manager.Join(ut) 131 | return ut 132 | } 133 | --------------------------------------------------------------------------------