├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ └── release.yml ├── .gitignore ├── .golangci.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── adapter ├── adapter.go ├── inbound │ ├── http.go │ ├── https.go │ ├── packet.go │ ├── socket.go │ └── util.go ├── outbound │ ├── base.go │ ├── direct.go │ ├── http.go │ ├── reject.go │ ├── shadowsocks.go │ ├── shadowsocksr.go │ ├── snell.go │ ├── socks5.go │ ├── trojan.go │ ├── util.go │ └── vmess.go ├── outboundgroup │ ├── common.go │ ├── fallback.go │ ├── loadbalance.go │ ├── parser.go │ ├── relay.go │ ├── selector.go │ ├── urltest.go │ └── util.go ├── parser.go └── provider │ ├── fetcher.go │ ├── healthcheck.go │ ├── parser.go │ ├── provider.go │ └── vehicle.go ├── common ├── batch │ ├── batch.go │ └── batch_test.go ├── cache │ ├── cache.go │ ├── cache_test.go │ ├── lrucache.go │ └── lrucache_test.go ├── murmur3 │ ├── murmur.go │ └── murmur32.go ├── net │ ├── bufconn.go │ ├── io.go │ └── relay.go ├── observable │ ├── iterable.go │ ├── observable.go │ ├── observable_test.go │ └── subscriber.go ├── picker │ ├── picker.go │ └── picker_test.go ├── pool │ ├── alloc.go │ ├── alloc_test.go │ ├── buffer.go │ └── pool.go ├── queue │ └── queue.go ├── singledo │ ├── singledo.go │ └── singledo_test.go ├── sockopt │ ├── reuseaddr_linux.go │ └── reuseaddr_other.go └── structure │ ├── structure.go │ └── structure_test.go ├── component ├── auth │ └── auth.go ├── dhcp │ ├── conn.go │ └── dhcp.go ├── dialer │ ├── bind_darwin.go │ ├── bind_linux.go │ ├── bind_others.go │ ├── dialer.go │ ├── mark_linux.go │ ├── mark_nonlinux.go │ ├── options.go │ ├── reuse_others.go │ ├── reuse_unix.go │ └── reuse_windows.go ├── fakeip │ ├── cachefile.go │ ├── memory.go │ ├── pool.go │ └── pool_test.go ├── iface │ └── iface.go ├── mmdb │ └── mmdb.go ├── nat │ └── table.go ├── pool │ ├── pool.go │ └── pool_test.go ├── process │ ├── process.go │ ├── process_darwin.go │ ├── process_freebsd_amd64.go │ ├── process_linux.go │ ├── process_other.go │ └── process_windows.go ├── profile │ ├── cachefile │ │ └── cache.go │ └── profile.go ├── resolver │ ├── defaults.go │ ├── enhancer.go │ └── resolver.go └── trie │ ├── domain.go │ ├── domain_test.go │ └── node.go ├── config ├── config.go ├── initial.go └── utils.go ├── constant ├── adapters.go ├── context.go ├── dns.go ├── listener.go ├── metadata.go ├── mime │ └── mime.go ├── path.go ├── provider │ └── interface.go ├── rule.go └── version.go ├── context ├── conn.go ├── dns.go └── packetconn.go ├── dns ├── client.go ├── dhcp.go ├── doh.go ├── enhancer.go ├── filters.go ├── middleware.go ├── resolver.go ├── server.go └── util.go ├── docs └── logo.png ├── go.mod ├── go.sum ├── hub ├── executor │ └── executor.go ├── hub.go └── route │ ├── common.go │ ├── configs.go │ ├── connections.go │ ├── ctxkeys.go │ ├── errors.go │ ├── provider.go │ ├── proxies.go │ ├── rules.go │ └── server.go ├── listener ├── auth │ └── auth.go ├── http │ ├── client.go │ ├── hack.go │ ├── proxy.go │ ├── server.go │ ├── upgrade.go │ └── utils.go ├── listener.go ├── mixed │ └── mixed.go ├── redir │ ├── tcp.go │ ├── tcp_darwin.go │ ├── tcp_freebsd.go │ ├── tcp_linux.go │ ├── tcp_linux_386.go │ ├── tcp_linux_other.go │ └── tcp_other.go ├── socks │ ├── tcp.go │ ├── udp.go │ └── utils.go └── tproxy │ ├── packet.go │ ├── setsockopt_linux.go │ ├── setsockopt_other.go │ ├── tproxy.go │ ├── udp.go │ ├── udp_linux.go │ └── udp_other.go ├── log ├── level.go └── log.go ├── main.go ├── rule ├── base.go ├── domain.go ├── domain_keyword.go ├── domain_suffix.go ├── final.go ├── geoip.go ├── ipcidr.go ├── parser.go ├── port.go └── process.go ├── test ├── .golangci.yaml ├── Makefile ├── README.md ├── clash_test.go ├── config │ ├── example.org-key.pem │ ├── example.org.pem │ ├── snell-http.conf │ ├── snell-tls.conf │ ├── snell.conf │ ├── trojan-grpc.json │ ├── trojan-ws.json │ ├── trojan.json │ ├── vmess-grpc.json │ ├── vmess-http.json │ ├── vmess-http2.json │ ├── vmess-tls.json │ ├── vmess-ws-0rtt.json │ ├── vmess-ws-tls.json │ ├── vmess-ws.json │ └── vmess.json ├── dns_test.go ├── docker_test.go ├── go.mod ├── go.sum ├── snell_test.go ├── ss_test.go ├── trojan_test.go ├── util.go ├── util_darwin_test.go ├── util_other_test.go └── vmess_test.go ├── transport ├── gun │ └── gun.go ├── shadowsocks │ ├── README.md │ ├── core │ │ └── cipher.go │ ├── shadowaead │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go │ └── shadowstream │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go ├── simple-obfs │ ├── http.go │ └── tls.go ├── snell │ ├── cipher.go │ ├── pool.go │ └── snell.go ├── socks4 │ └── socks4.go ├── socks5 │ └── socks5.go ├── ssr │ ├── obfs │ │ ├── base.go │ │ ├── http_post.go │ │ ├── http_simple.go │ │ ├── obfs.go │ │ ├── plain.go │ │ ├── random_head.go │ │ └── tls1.2_ticket_auth.go │ ├── protocol │ │ ├── auth_aes128_md5.go │ │ ├── auth_aes128_sha1.go │ │ ├── auth_chain_a.go │ │ ├── auth_chain_b.go │ │ ├── auth_sha1_v4.go │ │ ├── base.go │ │ ├── origin.go │ │ ├── packet.go │ │ ├── protocol.go │ │ └── stream.go │ └── tools │ │ ├── bufPool.go │ │ ├── crypto.go │ │ └── random.go ├── trojan │ └── trojan.go ├── v2ray-plugin │ ├── mux.go │ └── websocket.go └── vmess │ ├── aead.go │ ├── chunk.go │ ├── conn.go │ ├── h2.go │ ├── header.go │ ├── http.go │ ├── tls.go │ ├── user.go │ ├── vmess.go │ └── websocket.go └── tunnel ├── connection.go ├── mode.go ├── statistic ├── manager.go └── tracker.go └── tunnel.go /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | title: "[Bug] " 4 | body: 5 | - type: checkboxes 6 | id: ensure 7 | attributes: 8 | label: Verify steps 9 | description: " 10 | 在提交之前,请确认 11 | Please verify that you've followed these steps 12 | " 13 | options: 14 | - label: " 15 | 如果你可以自己 debug 并解决的话,提交 PR 吧 16 | Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome. 17 | " 18 | required: true 19 | - label: " 20 | 我已经在 [Issue Tracker](……/) 中找过我要提出的问题 21 | I have searched on the [issue tracker](……/) for a related issue. 22 | " 23 | required: true 24 | - label: " 25 | 我已经使用 dev 分支版本测试过,问题依旧存在 26 | I have tested using the dev branch, and the issue still exists. 27 | " 28 | required: true 29 | - label: " 30 | 我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题 31 | I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue. 32 | " 33 | required: true 34 | - label: " 35 | 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题 36 | This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash. 37 | " 38 | required: true 39 | - type: input 40 | attributes: 41 | label: Clash version 42 | validations: 43 | required: true 44 | - type: dropdown 45 | id: os 46 | attributes: 47 | label: What OS are you seeing the problem on? 48 | multiple: true 49 | options: 50 | - macOS 51 | - Windows 52 | - Linux 53 | - OpenBSD/FreeBSD 54 | - type: textarea 55 | attributes: 56 | render: yaml 57 | label: "Clash config" 58 | description: " 59 | 在下方附上 Clash core 脱敏后配置文件的内容 60 | Paste the Clash core configuration below. 61 | " 62 | validations: 63 | required: true 64 | - type: textarea 65 | attributes: 66 | render: shell 67 | label: Clash log 68 | description: " 69 | 在下方附上 Clash Core 的日志,log level 使用 DEBUG 70 | Paste the Clash core log below with the log level set to `DEBUG`. 71 | " 72 | - type: textarea 73 | attributes: 74 | label: Description 75 | validations: 76 | required: true 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Get help in GitHub Discussions 5 | url: https://github.com/Dreamacro/clash/discussions 6 | about: Have a question? Not sure if your issue affects everyone reproducibly? The quickest way to get help is on Clash's GitHub Discussions! 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[Feature] " 4 | body: 5 | - type: checkboxes 6 | id: ensure 7 | attributes: 8 | label: Verify steps 9 | description: " 10 | 在提交之前,请确认 11 | Please verify that you've followed these steps 12 | " 13 | options: 14 | - label: " 15 | 我已经在 [Issue Tracker](……/) 中找过我要提出的请求 16 | I have searched on the [issue tracker](……/) for a related feature request. 17 | " 18 | required: true 19 | - label: " 20 | 我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题 21 | I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue. 22 | " 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Description 27 | description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽? 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Possible Solution 33 | description: " 34 | 此项非必须,但是如果你有想法的话欢迎提出。 35 | Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change 36 | " 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Get latest go version 8 | id: version 9 | run: | 10 | echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') 11 | 12 | - name: Setup Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: ${{ steps.version.outputs.go_version }} 16 | 17 | - name: Check out code into the Go module directory 18 | uses: actions/checkout@v3 19 | 20 | - name: Cache go module 21 | uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/go/pkg/mod 25 | ~/.cache/go-build 26 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 27 | restore-keys: | 28 | ${{ runner.os }}-go- 29 | 30 | - name: Get dependencies, run test 31 | run: | 32 | go test ./... 33 | 34 | - name: Build 35 | if: startsWith(github.ref, 'refs/tags/') 36 | env: 37 | NAME: clash 38 | BINDIR: bin 39 | run: make -j releases 40 | 41 | - name: Upload Release 42 | uses: softprops/action-gh-release@v1 43 | if: startsWith(github.ref, 'refs/tags/') 44 | with: 45 | files: bin/* 46 | draft: true 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # go mod vendor 16 | vendor 17 | 18 | # GoLand 19 | .idea/* 20 | 21 | # macOS file 22 | .DS_Store 23 | 24 | # test suite 25 | test/config/cache* 26 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - staticcheck 6 | - govet 7 | - gci 8 | 9 | linters-settings: 10 | gci: 11 | sections: 12 | - standard 13 | - prefix(github.com/Dreamacro/clash) 14 | - default 15 | staticcheck: 16 | go: '1.18' 17 | -------------------------------------------------------------------------------- /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 --from=tonistiigi/xx:golang / / 7 | COPY . /clash-src 8 | RUN go mod download && \ 9 | make docker && \ 10 | mv ./bin/clash-docker /clash 11 | 12 | FROM alpine:latest 13 | LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash" 14 | 15 | RUN apk add --no-cache ca-certificates tzdata 16 | COPY --from=builder /Country.mmdb /root/.config/clash/ 17 | COPY --from=builder /clash / 18 | ENTRYPOINT ["/clash"] 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clash for SagerNet 2 | 3 | ### Changes 4 | 5 | * Add support for Shadowsocks 2022 ciphers 6 | 7 | ```yaml 8 | proxies: 9 | - name: "shadowsocks" 10 | type: ss 11 | server: server 12 | port: 443 13 | cipher: 2022-blake3-aes-128-gcm 14 | password: "" 15 | ``` -------------------------------------------------------------------------------- /adapter/inbound/http.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/context" 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | // NewHTTP receive normal http request and return HTTPContext 12 | func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext { 13 | metadata := parseSocksAddr(target) 14 | metadata.NetWork = C.TCP 15 | metadata.Type = C.HTTP 16 | if ip, port, err := parseAddr(source.String()); err == nil { 17 | metadata.SrcIP = ip 18 | metadata.SrcPort = port 19 | } 20 | return context.NewConnContext(conn, metadata) 21 | } 22 | -------------------------------------------------------------------------------- /adapter/inbound/https.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/context" 9 | ) 10 | 11 | // NewHTTPS receive CONNECT request and return ConnContext 12 | func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext { 13 | metadata := parseHTTPAddr(request) 14 | metadata.Type = C.HTTPCONNECT 15 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 16 | metadata.SrcIP = ip 17 | metadata.SrcPort = port 18 | } 19 | return context.NewConnContext(conn, metadata) 20 | } 21 | -------------------------------------------------------------------------------- /adapter/inbound/packet.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | "github.com/Dreamacro/clash/transport/socks5" 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 | -------------------------------------------------------------------------------- /adapter/inbound/socket.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/context" 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | // NewSocket receive TCP inbound and return ConnContext 12 | func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext { 13 | metadata := parseSocksAddr(target) 14 | metadata.NetWork = C.TCP 15 | metadata.Type = source 16 | if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { 17 | metadata.SrcIP = ip 18 | metadata.SrcPort = port 19 | } 20 | 21 | return context.NewConnContext(conn, metadata) 22 | } 23 | -------------------------------------------------------------------------------- /adapter/inbound/util.go: -------------------------------------------------------------------------------- 1 | package inbound 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/transport/socks5" 11 | ) 12 | 13 | func parseSocksAddr(target socks5.Addr) *C.Metadata { 14 | metadata := &C.Metadata{ 15 | AddrType: int(target[0]), 16 | } 17 | 18 | switch target[0] { 19 | case socks5.AtypDomainName: 20 | // trim for FQDN 21 | metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") 22 | metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) 23 | case socks5.AtypIPv4: 24 | ip := net.IP(target[1 : 1+net.IPv4len]) 25 | metadata.DstIP = ip 26 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) 27 | case socks5.AtypIPv6: 28 | ip := net.IP(target[1 : 1+net.IPv6len]) 29 | metadata.DstIP = ip 30 | metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) 31 | } 32 | 33 | return metadata 34 | } 35 | 36 | func parseHTTPAddr(request *http.Request) *C.Metadata { 37 | host := request.URL.Hostname() 38 | port := request.URL.Port() 39 | if port == "" { 40 | port = "80" 41 | } 42 | 43 | // trim FQDN (#737) 44 | host = strings.TrimRight(host, ".") 45 | 46 | metadata := &C.Metadata{ 47 | NetWork: C.TCP, 48 | AddrType: C.AtypDomainName, 49 | Host: host, 50 | DstIP: nil, 51 | DstPort: port, 52 | } 53 | 54 | ip := net.ParseIP(host) 55 | if ip != nil { 56 | switch { 57 | case ip.To4() == nil: 58 | metadata.AddrType = C.AtypIPv6 59 | default: 60 | metadata.AddrType = C.AtypIPv4 61 | } 62 | metadata.DstIP = ip 63 | } 64 | 65 | return metadata 66 | } 67 | 68 | func parseAddr(addr string) (net.IP, string, error) { 69 | host, port, err := net.SplitHostPort(addr) 70 | if err != nil { 71 | return nil, "", err 72 | } 73 | 74 | ip := net.ParseIP(host) 75 | return ip, port, nil 76 | } 77 | -------------------------------------------------------------------------------- /adapter/outbound/direct.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/component/dialer" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type Direct struct { 12 | *Base 13 | } 14 | 15 | // DialContext implements C.ProxyAdapter 16 | func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 17 | c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) 18 | if err != nil { 19 | return nil, err 20 | } 21 | tcpKeepAlive(c) 22 | return NewConn(c, d), nil 23 | } 24 | 25 | // ListenPacketContext implements C.ProxyAdapter 26 | func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 27 | pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return newPacketConn(&directPacketConn{pc}, d), nil 32 | } 33 | 34 | type directPacketConn struct { 35 | net.PacketConn 36 | } 37 | 38 | func NewDirect() *Direct { 39 | return &Direct{ 40 | Base: &Base{ 41 | name: "DIRECT", 42 | tp: C.Direct, 43 | udp: true, 44 | }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /adapter/outbound/reject.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "time" 8 | 9 | "github.com/Dreamacro/clash/component/dialer" 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | type Reject struct { 14 | *Base 15 | } 16 | 17 | // DialContext implements C.ProxyAdapter 18 | func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 19 | return NewConn(&nopConn{}, r), nil 20 | } 21 | 22 | // ListenPacketContext implements C.ProxyAdapter 23 | func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 24 | return newPacketConn(&nopPacketConn{}, r), nil 25 | } 26 | 27 | func NewReject() *Reject { 28 | return &Reject{ 29 | Base: &Base{ 30 | name: "REJECT", 31 | tp: C.Reject, 32 | udp: true, 33 | }, 34 | } 35 | } 36 | 37 | type nopConn struct{} 38 | 39 | func (rw *nopConn) Read(b []byte) (int, error) { 40 | return 0, io.EOF 41 | } 42 | 43 | func (rw *nopConn) Write(b []byte) (int, error) { 44 | return 0, io.EOF 45 | } 46 | 47 | func (rw *nopConn) Close() error { return nil } 48 | func (rw *nopConn) LocalAddr() net.Addr { return nil } 49 | func (rw *nopConn) RemoteAddr() net.Addr { return nil } 50 | func (rw *nopConn) SetDeadline(time.Time) error { return nil } 51 | func (rw *nopConn) SetReadDeadline(time.Time) error { return nil } 52 | func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil } 53 | 54 | type nopPacketConn struct{} 55 | 56 | func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil } 57 | func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF } 58 | func (npc *nopPacketConn) Close() error { return nil } 59 | func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} } 60 | func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil } 61 | func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil } 62 | func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil } 63 | -------------------------------------------------------------------------------- /adapter/outbound/util.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/Dreamacro/clash/component/resolver" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | ) 13 | 14 | func tcpKeepAlive(c net.Conn) { 15 | if tcp, ok := c.(*net.TCPConn); ok { 16 | tcp.SetKeepAlive(true) 17 | tcp.SetKeepAlivePeriod(30 * time.Second) 18 | } 19 | } 20 | 21 | func serializesSocksAddr(metadata *C.Metadata) []byte { 22 | var buf [][]byte 23 | aType := uint8(metadata.AddrType) 24 | p, _ := strconv.ParseUint(metadata.DstPort, 10, 16) 25 | port := []byte{uint8(p >> 8), uint8(p & 0xff)} 26 | switch metadata.AddrType { 27 | case socks5.AtypDomainName: 28 | len := uint8(len(metadata.Host)) 29 | host := []byte(metadata.Host) 30 | buf = [][]byte{{aType, len}, host, port} 31 | case socks5.AtypIPv4: 32 | host := metadata.DstIP.To4() 33 | buf = [][]byte{{aType}, host, port} 34 | case socks5.AtypIPv6: 35 | host := metadata.DstIP.To16() 36 | buf = [][]byte{{aType}, host, port} 37 | } 38 | return bytes.Join(buf, nil) 39 | } 40 | 41 | func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { 42 | host, port, err := net.SplitHostPort(address) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | ip, err := resolver.ResolveIP(host) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 52 | } 53 | 54 | func safeConnClose(c net.Conn, err error) { 55 | if err != nil { 56 | c.Close() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /adapter/outboundgroup/common.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "time" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/constant/provider" 8 | ) 9 | 10 | const ( 11 | defaultGetProxiesDuration = time.Second * 5 12 | ) 13 | 14 | func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { 15 | proxies := []C.Proxy{} 16 | for _, provider := range providers { 17 | if touch { 18 | proxies = append(proxies, provider.ProxiesWithTouch()...) 19 | } else { 20 | proxies = append(proxies, provider.Proxies()...) 21 | } 22 | } 23 | return proxies 24 | } 25 | -------------------------------------------------------------------------------- /adapter/outboundgroup/util.go: -------------------------------------------------------------------------------- 1 | package outboundgroup 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { 12 | host, port, err := net.SplitHostPort(rawAddress) 13 | if err != nil { 14 | err = fmt.Errorf("addrToMetadata failed: %w", err) 15 | return 16 | } 17 | 18 | ip := net.ParseIP(host) 19 | if ip == nil { 20 | addr = &C.Metadata{ 21 | AddrType: C.AtypDomainName, 22 | Host: host, 23 | DstIP: nil, 24 | DstPort: port, 25 | } 26 | return 27 | } else if ip4 := ip.To4(); ip4 != nil { 28 | addr = &C.Metadata{ 29 | AddrType: C.AtypIPv4, 30 | Host: "", 31 | DstIP: ip4, 32 | DstPort: port, 33 | } 34 | return 35 | } 36 | 37 | addr = &C.Metadata{ 38 | AddrType: C.AtypIPv6, 39 | Host: "", 40 | DstIP: ip, 41 | DstPort: port, 42 | } 43 | return 44 | } 45 | 46 | func tcpKeepAlive(c net.Conn) { 47 | if tcp, ok := c.(*net.TCPConn); ok { 48 | tcp.SetKeepAlive(true) 49 | tcp.SetKeepAlivePeriod(30 * time.Second) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /adapter/parser.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Dreamacro/clash/adapter/outbound" 7 | "github.com/Dreamacro/clash/common/structure" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | func ParseProxy(mapping map[string]any) (C.Proxy, error) { 12 | decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) 13 | proxyType, existType := mapping["type"].(string) 14 | if !existType { 15 | return nil, fmt.Errorf("missing type") 16 | } 17 | 18 | var ( 19 | proxy C.ProxyAdapter 20 | err error 21 | ) 22 | switch proxyType { 23 | case "ss": 24 | ssOption := &outbound.ShadowSocksOption{} 25 | err = decoder.Decode(mapping, ssOption) 26 | if err != nil { 27 | break 28 | } 29 | proxy, err = outbound.NewShadowSocks(*ssOption) 30 | case "ssr": 31 | ssrOption := &outbound.ShadowSocksROption{} 32 | err = decoder.Decode(mapping, ssrOption) 33 | if err != nil { 34 | break 35 | } 36 | proxy, err = outbound.NewShadowSocksR(*ssrOption) 37 | case "socks5": 38 | socksOption := &outbound.Socks5Option{} 39 | err = decoder.Decode(mapping, socksOption) 40 | if err != nil { 41 | break 42 | } 43 | proxy = outbound.NewSocks5(*socksOption) 44 | case "http": 45 | httpOption := &outbound.HttpOption{} 46 | err = decoder.Decode(mapping, httpOption) 47 | if err != nil { 48 | break 49 | } 50 | proxy = outbound.NewHttp(*httpOption) 51 | case "vmess": 52 | vmessOption := &outbound.VmessOption{ 53 | HTTPOpts: outbound.HTTPOptions{ 54 | Method: "GET", 55 | Path: []string{"/"}, 56 | }, 57 | } 58 | err = decoder.Decode(mapping, vmessOption) 59 | if err != nil { 60 | break 61 | } 62 | proxy, err = outbound.NewVmess(*vmessOption) 63 | case "snell": 64 | snellOption := &outbound.SnellOption{} 65 | err = decoder.Decode(mapping, snellOption) 66 | if err != nil { 67 | break 68 | } 69 | proxy, err = outbound.NewSnell(*snellOption) 70 | case "trojan": 71 | trojanOption := &outbound.TrojanOption{} 72 | err = decoder.Decode(mapping, trojanOption) 73 | if err != nil { 74 | break 75 | } 76 | proxy, err = outbound.NewTrojan(*trojanOption) 77 | default: 78 | return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) 79 | } 80 | 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return NewProxy(proxy), nil 86 | } 87 | -------------------------------------------------------------------------------- /adapter/provider/healthcheck.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/common/batch" 8 | C "github.com/Dreamacro/clash/constant" 9 | "go.uber.org/atomic" 10 | ) 11 | 12 | const ( 13 | defaultURLTestTimeout = time.Second * 5 14 | ) 15 | 16 | type HealthCheckOption struct { 17 | URL string 18 | Interval uint 19 | } 20 | 21 | type HealthCheck struct { 22 | url string 23 | proxies []C.Proxy 24 | interval uint 25 | lazy bool 26 | lastTouch *atomic.Int64 27 | done chan struct{} 28 | } 29 | 30 | func (hc *HealthCheck) process() { 31 | ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) 32 | 33 | go hc.check() 34 | for { 35 | select { 36 | case <-ticker.C: 37 | now := time.Now().Unix() 38 | if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { 39 | hc.check() 40 | } 41 | case <-hc.done: 42 | ticker.Stop() 43 | return 44 | } 45 | } 46 | } 47 | 48 | func (hc *HealthCheck) setProxy(proxies []C.Proxy) { 49 | hc.proxies = proxies 50 | } 51 | 52 | func (hc *HealthCheck) auto() bool { 53 | return hc.interval != 0 54 | } 55 | 56 | func (hc *HealthCheck) touch() { 57 | hc.lastTouch.Store(time.Now().Unix()) 58 | } 59 | 60 | func (hc *HealthCheck) check() { 61 | b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10)) 62 | for _, proxy := range hc.proxies { 63 | p := proxy 64 | b.Go(p.Name(), func() (any, error) { 65 | ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) 66 | defer cancel() 67 | p.URLTest(ctx, hc.url) 68 | return nil, nil 69 | }) 70 | } 71 | b.Wait() 72 | } 73 | 74 | func (hc *HealthCheck) close() { 75 | hc.done <- struct{}{} 76 | } 77 | 78 | func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck { 79 | return &HealthCheck{ 80 | proxies: proxies, 81 | url: url, 82 | interval: interval, 83 | lazy: lazy, 84 | lastTouch: atomic.NewInt64(0), 85 | done: make(chan struct{}, 1), 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /adapter/provider/parser.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/common/structure" 9 | C "github.com/Dreamacro/clash/constant" 10 | types "github.com/Dreamacro/clash/constant/provider" 11 | ) 12 | 13 | var errVehicleType = errors.New("unsupport vehicle type") 14 | 15 | type healthCheckSchema struct { 16 | Enable bool `provider:"enable"` 17 | URL string `provider:"url"` 18 | Interval int `provider:"interval"` 19 | Lazy bool `provider:"lazy,omitempty"` 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 | Filter string `provider:"filter,omitempty"` 28 | HealthCheck healthCheckSchema `provider:"health-check,omitempty"` 29 | } 30 | 31 | func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { 32 | decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) 33 | 34 | schema := &proxyProviderSchema{ 35 | HealthCheck: healthCheckSchema{ 36 | Lazy: true, 37 | }, 38 | } 39 | if err := decoder.Decode(mapping, schema); err != nil { 40 | return nil, err 41 | } 42 | 43 | var hcInterval uint 44 | if schema.HealthCheck.Enable { 45 | hcInterval = uint(schema.HealthCheck.Interval) 46 | } 47 | hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy) 48 | 49 | path := C.Path.Resolve(schema.Path) 50 | 51 | var vehicle types.Vehicle 52 | switch schema.Type { 53 | case "file": 54 | vehicle = NewFileVehicle(path) 55 | case "http": 56 | vehicle = NewHTTPVehicle(schema.URL, path) 57 | default: 58 | return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) 59 | } 60 | 61 | interval := time.Duration(uint(schema.Interval)) * time.Second 62 | filter := schema.Filter 63 | return NewProxySetProvider(name, interval, filter, vehicle, hc) 64 | } 65 | -------------------------------------------------------------------------------- /adapter/provider/vehicle.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "time" 11 | 12 | "github.com/Dreamacro/clash/component/dialer" 13 | types "github.com/Dreamacro/clash/constant/provider" 14 | ) 15 | 16 | type FileVehicle struct { 17 | path string 18 | } 19 | 20 | func (f *FileVehicle) Type() types.VehicleType { 21 | return types.File 22 | } 23 | 24 | func (f *FileVehicle) Path() string { 25 | return f.path 26 | } 27 | 28 | func (f *FileVehicle) Read() ([]byte, error) { 29 | return os.ReadFile(f.path) 30 | } 31 | 32 | func NewFileVehicle(path string) *FileVehicle { 33 | return &FileVehicle{path: path} 34 | } 35 | 36 | type HTTPVehicle struct { 37 | url string 38 | path string 39 | } 40 | 41 | func (h *HTTPVehicle) Type() types.VehicleType { 42 | return types.HTTP 43 | } 44 | 45 | func (h *HTTPVehicle) Path() string { 46 | return h.path 47 | } 48 | 49 | func (h *HTTPVehicle) Read() ([]byte, error) { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 51 | defer cancel() 52 | 53 | uri, err := url.Parse(h.url) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | req, err := http.NewRequest(http.MethodGet, uri.String(), nil) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | if user := uri.User; user != nil { 64 | password, _ := user.Password() 65 | req.SetBasicAuth(user.Username(), password) 66 | } 67 | 68 | req = req.WithContext(ctx) 69 | 70 | transport := &http.Transport{ 71 | // from http.DefaultTransport 72 | MaxIdleConns: 100, 73 | IdleConnTimeout: 90 * time.Second, 74 | TLSHandshakeTimeout: 10 * time.Second, 75 | ExpectContinueTimeout: 1 * time.Second, 76 | DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { 77 | return dialer.DialContext(ctx, network, address) 78 | }, 79 | } 80 | 81 | client := http.Client{Transport: transport} 82 | resp, err := client.Do(req) 83 | if err != nil { 84 | return nil, err 85 | } 86 | defer resp.Body.Close() 87 | 88 | buf, err := io.ReadAll(resp.Body) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return buf, nil 94 | } 95 | 96 | func NewHTTPVehicle(url string, path string) *HTTPVehicle { 97 | return &HTTPVehicle{url, path} 98 | } 99 | -------------------------------------------------------------------------------- /common/batch/batch.go: -------------------------------------------------------------------------------- 1 | package batch 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | type Option = func(b *Batch) 9 | 10 | type Result struct { 11 | Value any 12 | Err error 13 | } 14 | 15 | type Error struct { 16 | Key string 17 | Err error 18 | } 19 | 20 | func WithConcurrencyNum(n int) Option { 21 | return func(b *Batch) { 22 | q := make(chan struct{}, n) 23 | for i := 0; i < n; i++ { 24 | q <- struct{}{} 25 | } 26 | b.queue = q 27 | } 28 | } 29 | 30 | // Batch similar to errgroup, but can control the maximum number of concurrent 31 | type Batch struct { 32 | result map[string]Result 33 | queue chan struct{} 34 | wg sync.WaitGroup 35 | mux sync.Mutex 36 | err *Error 37 | once sync.Once 38 | cancel func() 39 | } 40 | 41 | func (b *Batch) Go(key string, fn func() (any, error)) { 42 | b.wg.Add(1) 43 | go func() { 44 | defer b.wg.Done() 45 | if b.queue != nil { 46 | <-b.queue 47 | defer func() { 48 | b.queue <- struct{}{} 49 | }() 50 | } 51 | 52 | value, err := fn() 53 | if err != nil { 54 | b.once.Do(func() { 55 | b.err = &Error{key, err} 56 | if b.cancel != nil { 57 | b.cancel() 58 | } 59 | }) 60 | } 61 | 62 | ret := Result{value, err} 63 | b.mux.Lock() 64 | defer b.mux.Unlock() 65 | b.result[key] = ret 66 | }() 67 | } 68 | 69 | func (b *Batch) Wait() *Error { 70 | b.wg.Wait() 71 | if b.cancel != nil { 72 | b.cancel() 73 | } 74 | return b.err 75 | } 76 | 77 | func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) { 78 | err := b.Wait() 79 | return b.Result(), err 80 | } 81 | 82 | func (b *Batch) Result() map[string]Result { 83 | b.mux.Lock() 84 | defer b.mux.Unlock() 85 | copy := map[string]Result{} 86 | for k, v := range b.result { 87 | copy[k] = v 88 | } 89 | return copy 90 | } 91 | 92 | func New(ctx context.Context, opts ...Option) (*Batch, context.Context) { 93 | ctx, cancel := context.WithCancel(ctx) 94 | 95 | b := &Batch{ 96 | result: map[string]Result{}, 97 | } 98 | 99 | for _, o := range opts { 100 | o(b) 101 | } 102 | 103 | b.cancel = cancel 104 | return b, ctx 105 | } 106 | -------------------------------------------------------------------------------- /common/batch/batch_test.go: -------------------------------------------------------------------------------- 1 | package batch 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestBatch(t *testing.T) { 14 | b, _ := New(context.Background()) 15 | 16 | now := time.Now() 17 | b.Go("foo", func() (any, error) { 18 | time.Sleep(time.Millisecond * 100) 19 | return "foo", nil 20 | }) 21 | b.Go("bar", func() (any, error) { 22 | time.Sleep(time.Millisecond * 150) 23 | return "bar", nil 24 | }) 25 | result, err := b.WaitAndGetResult() 26 | 27 | assert.Nil(t, err) 28 | 29 | duration := time.Since(now) 30 | assert.Less(t, duration, time.Millisecond*200) 31 | assert.Equal(t, 2, len(result)) 32 | 33 | for k, v := range result { 34 | assert.NoError(t, v.Err) 35 | assert.Equal(t, k, v.Value.(string)) 36 | } 37 | } 38 | 39 | func TestBatchWithConcurrencyNum(t *testing.T) { 40 | b, _ := New( 41 | context.Background(), 42 | WithConcurrencyNum(3), 43 | ) 44 | 45 | now := time.Now() 46 | for i := 0; i < 7; i++ { 47 | idx := i 48 | b.Go(strconv.Itoa(idx), func() (any, error) { 49 | time.Sleep(time.Millisecond * 100) 50 | return strconv.Itoa(idx), nil 51 | }) 52 | } 53 | result, _ := b.WaitAndGetResult() 54 | duration := time.Since(now) 55 | assert.Greater(t, duration, time.Millisecond*260) 56 | assert.Equal(t, 7, len(result)) 57 | 58 | for k, v := range result { 59 | assert.NoError(t, v.Err) 60 | assert.Equal(t, k, v.Value.(string)) 61 | } 62 | } 63 | 64 | func TestBatchContext(t *testing.T) { 65 | b, ctx := New(context.Background()) 66 | 67 | b.Go("error", func() (any, error) { 68 | time.Sleep(time.Millisecond * 100) 69 | return nil, errors.New("test error") 70 | }) 71 | 72 | b.Go("ctx", func() (any, error) { 73 | <-ctx.Done() 74 | return nil, ctx.Err() 75 | }) 76 | 77 | result, err := b.WaitAndGetResult() 78 | 79 | assert.NotNil(t, err) 80 | assert.Equal(t, "error", err.Key) 81 | 82 | assert.Equal(t, ctx.Err(), result["ctx"].Err) 83 | } 84 | -------------------------------------------------------------------------------- /common/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 any 22 | } 23 | 24 | // Put element in Cache with its ttl 25 | func (c *cache) Put(key any, payload any, 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 any) any { 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 any) (payload any, 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 any) bool { 64 | key := k.(string) 65 | elm := v.(*element) 66 | if time.Since(elm.Expired) > 0 { 67 | c.mapping.Delete(key) 68 | } 69 | return true 70 | }) 71 | } 72 | 73 | type janitor struct { 74 | interval time.Duration 75 | stop chan struct{} 76 | } 77 | 78 | func (j *janitor) process(c *cache) { 79 | ticker := time.NewTicker(j.interval) 80 | for { 81 | select { 82 | case <-ticker.C: 83 | c.cleanup() 84 | case <-j.stop: 85 | ticker.Stop() 86 | return 87 | } 88 | } 89 | } 90 | 91 | func stopJanitor(c *Cache) { 92 | c.janitor.stop <- struct{}{} 93 | } 94 | 95 | // New return *Cache 96 | func New(interval time.Duration) *Cache { 97 | j := &janitor{ 98 | interval: interval, 99 | stop: make(chan struct{}), 100 | } 101 | c := &cache{janitor: j} 102 | go j.process(c) 103 | C := &Cache{c} 104 | runtime.SetFinalizer(C, stopJanitor) 105 | return C 106 | } 107 | -------------------------------------------------------------------------------- /common/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCache_Basic(t *testing.T) { 12 | interval := 200 * time.Millisecond 13 | ttl := 20 * time.Millisecond 14 | c := New(interval) 15 | c.Put("int", 1, ttl) 16 | c.Put("string", "a", ttl) 17 | 18 | i := c.Get("int") 19 | assert.Equal(t, i.(int), 1, "should recv 1") 20 | 21 | s := c.Get("string") 22 | assert.Equal(t, s.(string), "a", "should recv 'a'") 23 | } 24 | 25 | func TestCache_TTL(t *testing.T) { 26 | interval := 200 * time.Millisecond 27 | ttl := 20 * time.Millisecond 28 | now := time.Now() 29 | c := New(interval) 30 | c.Put("int", 1, ttl) 31 | c.Put("int2", 2, ttl) 32 | 33 | i := c.Get("int") 34 | _, expired := c.GetWithExpire("int2") 35 | assert.Equal(t, i.(int), 1, "should recv 1") 36 | assert.True(t, now.Before(expired)) 37 | 38 | time.Sleep(ttl * 2) 39 | i = c.Get("int") 40 | j, _ := c.GetWithExpire("int2") 41 | assert.Nil(t, i, "should recv nil") 42 | assert.Nil(t, j, "should recv nil") 43 | } 44 | 45 | func TestCache_AutoCleanup(t *testing.T) { 46 | interval := 10 * time.Millisecond 47 | ttl := 15 * time.Millisecond 48 | c := New(interval) 49 | c.Put("int", 1, ttl) 50 | 51 | time.Sleep(ttl * 2) 52 | i := c.Get("int") 53 | j, _ := c.GetWithExpire("int") 54 | assert.Nil(t, i, "should recv nil") 55 | assert.Nil(t, j, "should recv nil") 56 | } 57 | 58 | func TestCache_AutoGC(t *testing.T) { 59 | sign := make(chan struct{}) 60 | go func() { 61 | interval := 10 * time.Millisecond 62 | ttl := 15 * time.Millisecond 63 | c := New(interval) 64 | c.Put("int", 1, ttl) 65 | sign <- struct{}{} 66 | }() 67 | 68 | <-sign 69 | runtime.GC() 70 | } 71 | -------------------------------------------------------------------------------- /common/murmur3/murmur.go: -------------------------------------------------------------------------------- 1 | package murmur3 2 | 3 | type bmixer interface { 4 | bmix(p []byte) (tail []byte) 5 | Size() (n int) 6 | reset() 7 | } 8 | 9 | type digest struct { 10 | clen int // Digested input cumulative length. 11 | tail []byte // 0 to Size()-1 bytes view of `buf'. 12 | buf [16]byte // Expected (but not required) to be Size() large. 13 | seed uint32 // Seed for initializing the hash. 14 | bmixer 15 | } 16 | 17 | func (d *digest) BlockSize() int { return 1 } 18 | 19 | func (d *digest) Write(p []byte) (n int, err error) { 20 | n = len(p) 21 | d.clen += n 22 | 23 | if len(d.tail) > 0 { 24 | // Stick back pending bytes. 25 | nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. 26 | if nfree < len(p) { 27 | // One full block can be formed. 28 | block := append(d.tail, p[:nfree]...) 29 | p = p[nfree:] 30 | _ = d.bmix(block) // No tail. 31 | } else { 32 | // Tail's buf is large enough to prevent reallocs. 33 | p = append(d.tail, p...) 34 | } 35 | } 36 | 37 | d.tail = d.bmix(p) 38 | 39 | // Keep own copy of the 0 to Size()-1 pending bytes. 40 | nn := copy(d.buf[:], d.tail) 41 | d.tail = d.buf[:nn] 42 | 43 | return n, nil 44 | } 45 | 46 | func (d *digest) Reset() { 47 | d.clen = 0 48 | d.tail = nil 49 | d.bmixer.reset() 50 | } 51 | -------------------------------------------------------------------------------- /common/net/bufconn.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | ) 7 | 8 | type BufferedConn struct { 9 | r *bufio.Reader 10 | net.Conn 11 | } 12 | 13 | func NewBufferedConn(c net.Conn) *BufferedConn { 14 | if bc, ok := c.(*BufferedConn); ok { 15 | return bc 16 | } 17 | return &BufferedConn{bufio.NewReader(c), c} 18 | } 19 | 20 | // Reader returns the internal bufio.Reader. 21 | func (c *BufferedConn) Reader() *bufio.Reader { 22 | return c.r 23 | } 24 | 25 | // Peek returns the next n bytes without advancing the reader. 26 | func (c *BufferedConn) Peek(n int) ([]byte, error) { 27 | return c.r.Peek(n) 28 | } 29 | 30 | func (c *BufferedConn) Read(p []byte) (int, error) { 31 | return c.r.Read(p) 32 | } 33 | 34 | func (c *BufferedConn) ReadByte() (byte, error) { 35 | return c.r.ReadByte() 36 | } 37 | 38 | func (c *BufferedConn) UnreadByte() error { 39 | return c.r.UnreadByte() 40 | } 41 | 42 | func (c *BufferedConn) Buffered() int { 43 | return c.r.Buffered() 44 | } 45 | -------------------------------------------------------------------------------- /common/net/io.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "io" 4 | 5 | type ReadOnlyReader struct { 6 | io.Reader 7 | } 8 | 9 | type WriteOnlyWriter struct { 10 | io.Writer 11 | } 12 | -------------------------------------------------------------------------------- /common/net/relay.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/common/pool" 9 | ) 10 | 11 | // Relay copies between left and right bidirectionally. 12 | func Relay(leftConn, rightConn net.Conn) { 13 | ch := make(chan error) 14 | 15 | go func() { 16 | buf := pool.Get(pool.RelayBufferSize) 17 | // Wrapping to avoid using *net.TCPConn.(ReadFrom) 18 | // See also https://github.com/Dreamacro/clash/pull/1209 19 | _, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf) 20 | pool.Put(buf) 21 | leftConn.SetReadDeadline(time.Now()) 22 | ch <- err 23 | }() 24 | 25 | buf := pool.Get(pool.RelayBufferSize) 26 | io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf) 27 | pool.Put(buf) 28 | rightConn.SetReadDeadline(time.Now()) 29 | <-ch 30 | } 31 | -------------------------------------------------------------------------------- /common/observable/iterable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | type Iterable <-chan any 4 | -------------------------------------------------------------------------------- /common/observable/observable.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | type Observable struct { 9 | iterable Iterable 10 | listener map[Subscription]*Subscriber 11 | mux sync.Mutex 12 | done bool 13 | } 14 | 15 | func (o *Observable) process() { 16 | for item := range o.iterable { 17 | o.mux.Lock() 18 | for _, sub := range o.listener { 19 | sub.Emit(item) 20 | } 21 | o.mux.Unlock() 22 | } 23 | o.close() 24 | } 25 | 26 | func (o *Observable) close() { 27 | o.mux.Lock() 28 | defer o.mux.Unlock() 29 | 30 | o.done = true 31 | for _, sub := range o.listener { 32 | sub.Close() 33 | } 34 | } 35 | 36 | func (o *Observable) Subscribe() (Subscription, error) { 37 | o.mux.Lock() 38 | defer o.mux.Unlock() 39 | if o.done { 40 | return nil, errors.New("Observable is closed") 41 | } 42 | subscriber := newSubscriber() 43 | o.listener[subscriber.Out()] = subscriber 44 | return subscriber.Out(), nil 45 | } 46 | 47 | func (o *Observable) UnSubscribe(sub Subscription) { 48 | o.mux.Lock() 49 | defer o.mux.Unlock() 50 | subscriber, exist := o.listener[sub] 51 | if !exist { 52 | return 53 | } 54 | delete(o.listener, sub) 55 | subscriber.Close() 56 | } 57 | 58 | func NewObservable(any Iterable) *Observable { 59 | observable := &Observable{ 60 | iterable: any, 61 | listener: map[Subscription]*Subscriber{}, 62 | } 63 | go observable.process() 64 | return observable 65 | } 66 | -------------------------------------------------------------------------------- /common/observable/subscriber.go: -------------------------------------------------------------------------------- 1 | package observable 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Subscription <-chan any 8 | 9 | type Subscriber struct { 10 | buffer chan any 11 | once sync.Once 12 | } 13 | 14 | func (s *Subscriber) Emit(item any) { 15 | s.buffer <- item 16 | } 17 | 18 | func (s *Subscriber) Out() Subscription { 19 | return s.buffer 20 | } 21 | 22 | func (s *Subscriber) Close() { 23 | s.once.Do(func() { 24 | close(s.buffer) 25 | }) 26 | } 27 | 28 | func newSubscriber() *Subscriber { 29 | sub := &Subscriber{ 30 | buffer: make(chan any, 200), 31 | } 32 | return sub 33 | } 34 | -------------------------------------------------------------------------------- /common/picker/picker.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Picker provides synchronization, and Context cancelation 10 | // for groups of goroutines working on subtasks of a common task. 11 | // Inspired by errGroup 12 | type Picker struct { 13 | ctx context.Context 14 | cancel func() 15 | 16 | wg sync.WaitGroup 17 | 18 | once sync.Once 19 | errOnce sync.Once 20 | result any 21 | err error 22 | } 23 | 24 | func newPicker(ctx context.Context, cancel func()) *Picker { 25 | return &Picker{ 26 | ctx: ctx, 27 | cancel: cancel, 28 | } 29 | } 30 | 31 | // WithContext returns a new Picker and an associated Context derived from ctx. 32 | // and cancel when first element return. 33 | func WithContext(ctx context.Context) (*Picker, context.Context) { 34 | ctx, cancel := context.WithCancel(ctx) 35 | return newPicker(ctx, cancel), ctx 36 | } 37 | 38 | // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. 39 | func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { 40 | ctx, cancel := context.WithTimeout(ctx, timeout) 41 | return newPicker(ctx, cancel), ctx 42 | } 43 | 44 | // Wait blocks until all function calls from the Go method have returned, 45 | // then returns the first nil error result (if any) from them. 46 | func (p *Picker) Wait() any { 47 | p.wg.Wait() 48 | if p.cancel != nil { 49 | p.cancel() 50 | } 51 | return p.result 52 | } 53 | 54 | // Error return the first error (if all success return nil) 55 | func (p *Picker) Error() error { 56 | return p.err 57 | } 58 | 59 | // Go calls the given function in a new goroutine. 60 | // The first call to return a nil error cancels the group; its result will be returned by Wait. 61 | func (p *Picker) Go(f func() (any, error)) { 62 | p.wg.Add(1) 63 | 64 | go func() { 65 | defer p.wg.Done() 66 | 67 | if ret, err := f(); err == nil { 68 | p.once.Do(func() { 69 | p.result = ret 70 | if p.cancel != nil { 71 | p.cancel() 72 | } 73 | }) 74 | } else { 75 | p.errOnce.Do(func() { 76 | p.err = err 77 | }) 78 | } 79 | }() 80 | } 81 | -------------------------------------------------------------------------------- /common/picker/picker_test.go: -------------------------------------------------------------------------------- 1 | package picker 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func sleepAndSend(ctx context.Context, delay int, input any) func() (any, error) { 12 | return func() (any, error) { 13 | timer := time.NewTimer(time.Millisecond * time.Duration(delay)) 14 | select { 15 | case <-timer.C: 16 | return input, nil 17 | case <-ctx.Done(): 18 | return nil, ctx.Err() 19 | } 20 | } 21 | } 22 | 23 | func TestPicker_Basic(t *testing.T) { 24 | picker, ctx := WithContext(context.Background()) 25 | picker.Go(sleepAndSend(ctx, 30, 2)) 26 | picker.Go(sleepAndSend(ctx, 20, 1)) 27 | 28 | number := picker.Wait() 29 | assert.NotNil(t, number) 30 | assert.Equal(t, number.(int), 1) 31 | } 32 | 33 | func TestPicker_Timeout(t *testing.T) { 34 | picker, ctx := WithTimeout(context.Background(), time.Millisecond*5) 35 | picker.Go(sleepAndSend(ctx, 20, 1)) 36 | 37 | number := picker.Wait() 38 | assert.Nil(t, number) 39 | assert.NotNil(t, picker.Error()) 40 | } 41 | -------------------------------------------------------------------------------- /common/pool/alloc.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | // Inspired by https://github.com/xtaci/smux/blob/master/alloc.go 4 | 5 | import ( 6 | "errors" 7 | "math/bits" 8 | "sync" 9 | ) 10 | 11 | var DefaultAllocator = NewAllocator() 12 | 13 | // Allocator for incoming frames, optimized to prevent overwriting after zeroing 14 | type Allocator struct { 15 | buffers []sync.Pool 16 | } 17 | 18 | // NewAllocator initiates a []byte allocator for frames less than 65536 bytes, 19 | // the waste(memory fragmentation) of space allocation is guaranteed to be 20 | // no more than 50%. 21 | func NewAllocator() *Allocator { 22 | alloc := new(Allocator) 23 | alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K 24 | for k := range alloc.buffers { 25 | i := k 26 | alloc.buffers[k].New = func() any { 27 | return make([]byte, 1< 65536 { 36 | return nil 37 | } 38 | 39 | bits := msb(size) 40 | if size == 1< 65536 || cap(buf) != 1< host on head of linked list 19 | m.cache.Get(ipToUint(ip.To4())) 20 | return ip, true 21 | } 22 | 23 | return nil, false 24 | } 25 | 26 | // PutByHost implements store.PutByHost 27 | func (m *memoryStore) PutByHost(host string, ip net.IP) { 28 | m.cache.Set(host, ip) 29 | } 30 | 31 | // GetByIP implements store.GetByIP 32 | func (m *memoryStore) GetByIP(ip net.IP) (string, bool) { 33 | if elm, exist := m.cache.Get(ipToUint(ip.To4())); exist { 34 | host := elm.(string) 35 | 36 | // ensure host --> ip on head of linked list 37 | m.cache.Get(host) 38 | return host, true 39 | } 40 | 41 | return "", false 42 | } 43 | 44 | // PutByIP implements store.PutByIP 45 | func (m *memoryStore) PutByIP(ip net.IP, host string) { 46 | m.cache.Set(ipToUint(ip.To4()), host) 47 | } 48 | 49 | // DelByIP implements store.DelByIP 50 | func (m *memoryStore) DelByIP(ip net.IP) { 51 | ipNum := ipToUint(ip.To4()) 52 | if elm, exist := m.cache.Get(ipNum); exist { 53 | m.cache.Delete(elm.(string)) 54 | } 55 | m.cache.Delete(ipNum) 56 | } 57 | 58 | // Exist implements store.Exist 59 | func (m *memoryStore) Exist(ip net.IP) bool { 60 | return m.cache.Exist(ipToUint(ip.To4())) 61 | } 62 | 63 | // CloneTo implements store.CloneTo 64 | // only for memoryStore to memoryStore 65 | func (m *memoryStore) CloneTo(store store) { 66 | if ms, ok := store.(*memoryStore); ok { 67 | m.cache.CloneTo(ms.cache) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /component/mmdb/mmdb.go: -------------------------------------------------------------------------------- 1 | package mmdb 2 | 3 | import ( 4 | "sync" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/Dreamacro/clash/log" 8 | "github.com/oschwald/geoip2-golang" 9 | ) 10 | 11 | var ( 12 | mmdb *geoip2.Reader 13 | once sync.Once 14 | ) 15 | 16 | func LoadFromBytes(buffer []byte) { 17 | once.Do(func() { 18 | var err error 19 | mmdb, err = geoip2.FromBytes(buffer) 20 | if err != nil { 21 | log.Fatalln("Can't load mmdb: %s", err.Error()) 22 | } 23 | }) 24 | } 25 | 26 | func Verify() bool { 27 | instance, err := geoip2.Open(C.Path.MMDB()) 28 | if err == nil { 29 | instance.Close() 30 | } 31 | return err == nil 32 | } 33 | 34 | func Instance() *geoip2.Reader { 35 | once.Do(func() { 36 | var err error 37 | mmdb, err = geoip2.Open(C.Path.MMDB()) 38 | if err != nil { 39 | log.Fatalln("Can't load mmdb: %s", err.Error()) 40 | } 41 | }) 42 | 43 | return mmdb 44 | } 45 | -------------------------------------------------------------------------------- /component/nat/table.go: -------------------------------------------------------------------------------- 1 | package nat 2 | 3 | import ( 4 | "sync" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Table struct { 10 | mapping sync.Map 11 | } 12 | 13 | func (t *Table) Set(key string, pc C.PacketConn) { 14 | t.mapping.Store(key, pc) 15 | } 16 | 17 | func (t *Table) Get(key string) C.PacketConn { 18 | item, exist := t.mapping.Load(key) 19 | if !exist { 20 | return nil 21 | } 22 | return item.(C.PacketConn) 23 | } 24 | 25 | func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { 26 | item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{})) 27 | return item.(*sync.Cond), loaded 28 | } 29 | 30 | func (t *Table) Delete(key string) { 31 | t.mapping.Delete(key) 32 | } 33 | 34 | // New return *Cache 35 | func New() *Table { 36 | return &Table{} 37 | } 38 | -------------------------------------------------------------------------------- /component/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | type Factory = func(context.Context) (any, error) 10 | 11 | type entry struct { 12 | elm any 13 | time time.Time 14 | } 15 | 16 | type Option func(*pool) 17 | 18 | // WithEvict set the evict callback 19 | func WithEvict(cb func(any)) Option { 20 | return func(p *pool) { 21 | p.evict = cb 22 | } 23 | } 24 | 25 | // WithAge defined element max age (millisecond) 26 | func WithAge(maxAge int64) Option { 27 | return func(p *pool) { 28 | p.maxAge = maxAge 29 | } 30 | } 31 | 32 | // WithSize defined max size of Pool 33 | func WithSize(maxSize int) Option { 34 | return func(p *pool) { 35 | p.ch = make(chan any, maxSize) 36 | } 37 | } 38 | 39 | // Pool is for GC, see New for detail 40 | type Pool struct { 41 | *pool 42 | } 43 | 44 | type pool struct { 45 | ch chan any 46 | factory Factory 47 | evict func(any) 48 | maxAge int64 49 | } 50 | 51 | func (p *pool) GetContext(ctx context.Context) (any, error) { 52 | now := time.Now() 53 | for { 54 | select { 55 | case item := <-p.ch: 56 | elm := item.(*entry) 57 | if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge { 58 | if p.evict != nil { 59 | p.evict(elm.elm) 60 | } 61 | continue 62 | } 63 | 64 | return elm.elm, nil 65 | default: 66 | return p.factory(ctx) 67 | } 68 | } 69 | } 70 | 71 | func (p *pool) Get() (any, error) { 72 | return p.GetContext(context.Background()) 73 | } 74 | 75 | func (p *pool) Put(item any) { 76 | e := &entry{ 77 | elm: item, 78 | time: time.Now(), 79 | } 80 | 81 | select { 82 | case p.ch <- e: 83 | return 84 | default: 85 | // pool is full 86 | if p.evict != nil { 87 | p.evict(item) 88 | } 89 | return 90 | } 91 | } 92 | 93 | func recycle(p *Pool) { 94 | for item := range p.pool.ch { 95 | if p.pool.evict != nil { 96 | p.pool.evict(item.(*entry).elm) 97 | } 98 | } 99 | } 100 | 101 | func New(factory Factory, options ...Option) *Pool { 102 | p := &pool{ 103 | ch: make(chan any, 10), 104 | factory: factory, 105 | } 106 | 107 | for _, option := range options { 108 | option(p) 109 | } 110 | 111 | P := &Pool{p} 112 | runtime.SetFinalizer(P, recycle) 113 | return P 114 | } 115 | -------------------------------------------------------------------------------- /component/pool/pool_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func lg() Factory { 12 | initial := -1 13 | return func(context.Context) (any, error) { 14 | initial++ 15 | return initial, nil 16 | } 17 | } 18 | 19 | func TestPool_Basic(t *testing.T) { 20 | g := lg() 21 | pool := New(g) 22 | 23 | elm, _ := pool.Get() 24 | assert.Equal(t, 0, elm.(int)) 25 | pool.Put(elm) 26 | elm, _ = pool.Get() 27 | assert.Equal(t, 0, elm.(int)) 28 | elm, _ = pool.Get() 29 | assert.Equal(t, 1, elm.(int)) 30 | } 31 | 32 | func TestPool_MaxSize(t *testing.T) { 33 | g := lg() 34 | size := 5 35 | pool := New(g, WithSize(size)) 36 | 37 | items := []any{} 38 | 39 | for i := 0; i < size; i++ { 40 | item, _ := pool.Get() 41 | items = append(items, item) 42 | } 43 | 44 | extra, _ := pool.Get() 45 | assert.Equal(t, size, extra.(int)) 46 | 47 | for _, item := range items { 48 | pool.Put(item) 49 | } 50 | 51 | pool.Put(extra) 52 | 53 | for _, item := range items { 54 | elm, _ := pool.Get() 55 | assert.Equal(t, item.(int), elm.(int)) 56 | } 57 | } 58 | 59 | func TestPool_MaxAge(t *testing.T) { 60 | g := lg() 61 | pool := New(g, WithAge(20)) 62 | 63 | elm, _ := pool.Get() 64 | pool.Put(elm) 65 | 66 | elm, _ = pool.Get() 67 | assert.Equal(t, 0, elm.(int)) 68 | pool.Put(elm) 69 | 70 | time.Sleep(time.Millisecond * 22) 71 | elm, _ = pool.Get() 72 | assert.Equal(t, 1, elm.(int)) 73 | } 74 | -------------------------------------------------------------------------------- /component/process/process.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | ) 7 | 8 | var ( 9 | ErrInvalidNetwork = errors.New("invalid network") 10 | ErrPlatformNotSupport = errors.New("not support on this platform") 11 | ErrNotFound = errors.New("process not found") 12 | ) 13 | 14 | const ( 15 | TCP = "tcp" 16 | UDP = "udp" 17 | ) 18 | 19 | func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) { 20 | return findProcessName(network, srcIP, srcPort) 21 | } 22 | -------------------------------------------------------------------------------- /component/process/process_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux && !windows && (!freebsd || !amd64) 2 | 3 | package process 4 | 5 | import "net" 6 | 7 | func findProcessName(network string, ip net.IP, srcPort int) (string, error) { 8 | return "", ErrPlatformNotSupport 9 | } 10 | -------------------------------------------------------------------------------- /component/profile/profile.go: -------------------------------------------------------------------------------- 1 | package profile 2 | 3 | import ( 4 | "go.uber.org/atomic" 5 | ) 6 | 7 | // StoreSelected is a global switch for storing selected proxy to cache 8 | var StoreSelected = atomic.NewBool(true) 9 | -------------------------------------------------------------------------------- /component/resolver/defaults.go: -------------------------------------------------------------------------------- 1 | //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 2 | 3 | package resolver 4 | 5 | import _ "unsafe" 6 | 7 | //go:linkname defaultNS net.defaultNS 8 | var defaultNS []string 9 | 10 | func init() { 11 | defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"} 12 | } 13 | -------------------------------------------------------------------------------- /component/resolver/enhancer.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | var DefaultHostMapper Enhancer 8 | 9 | type Enhancer interface { 10 | FakeIPEnabled() bool 11 | MappingEnabled() bool 12 | IsFakeIP(net.IP) bool 13 | IsExistFakeIP(net.IP) bool 14 | FindHostByIP(net.IP) (string, bool) 15 | } 16 | 17 | func FakeIPEnabled() bool { 18 | if mapper := DefaultHostMapper; mapper != nil { 19 | return mapper.FakeIPEnabled() 20 | } 21 | 22 | return false 23 | } 24 | 25 | func MappingEnabled() bool { 26 | if mapper := DefaultHostMapper; mapper != nil { 27 | return mapper.MappingEnabled() 28 | } 29 | 30 | return false 31 | } 32 | 33 | func IsFakeIP(ip net.IP) bool { 34 | if mapper := DefaultHostMapper; mapper != nil { 35 | return mapper.IsFakeIP(ip) 36 | } 37 | 38 | return false 39 | } 40 | 41 | func IsExistFakeIP(ip net.IP) bool { 42 | if mapper := DefaultHostMapper; mapper != nil { 43 | return mapper.IsExistFakeIP(ip) 44 | } 45 | 46 | return false 47 | } 48 | 49 | func FindHostByIP(ip net.IP) (string, bool) { 50 | if mapper := DefaultHostMapper; mapper != nil { 51 | return mapper.FindHostByIP(ip) 52 | } 53 | 54 | return "", false 55 | } 56 | -------------------------------------------------------------------------------- /component/trie/node.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | // Node is the trie's node 4 | type Node struct { 5 | children map[string]*Node 6 | Data any 7 | } 8 | 9 | func (n *Node) getChild(s string) *Node { 10 | return n.children[s] 11 | } 12 | 13 | func (n *Node) hasChild(s string) bool { 14 | return n.getChild(s) != nil 15 | } 16 | 17 | func (n *Node) addChild(s string, child *Node) { 18 | n.children[s] = child 19 | } 20 | 21 | func newNode(data any) *Node { 22 | return &Node{ 23 | Data: data, 24 | children: map[string]*Node{}, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/initial.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/Dreamacro/clash/component/mmdb" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/log" 12 | ) 13 | 14 | func downloadMMDB(path string) (err error) { 15 | resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb") 16 | if err != nil { 17 | return 18 | } 19 | defer resp.Body.Close() 20 | 21 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) 22 | if err != nil { 23 | return err 24 | } 25 | defer f.Close() 26 | _, err = io.Copy(f, resp.Body) 27 | 28 | return err 29 | } 30 | 31 | func initMMDB() error { 32 | if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { 33 | log.Infoln("Can't find MMDB, start download") 34 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 35 | return fmt.Errorf("can't download MMDB: %s", err.Error()) 36 | } 37 | } 38 | 39 | if !mmdb.Verify() { 40 | log.Warnln("MMDB invalid, remove and download") 41 | if err := os.Remove(C.Path.MMDB()); err != nil { 42 | return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) 43 | } 44 | 45 | if err := downloadMMDB(C.Path.MMDB()); err != nil { 46 | return fmt.Errorf("can't download MMDB: %s", err.Error()) 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // Init prepare necessary files 54 | func Init(dir string) error { 55 | // initial homedir 56 | if _, err := os.Stat(dir); os.IsNotExist(err) { 57 | if err := os.MkdirAll(dir, 0o777); err != nil { 58 | return fmt.Errorf("can't create config directory %s: %s", dir, err.Error()) 59 | } 60 | } 61 | 62 | // initial config.yaml 63 | if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { 64 | log.Infoln("Can't find config, create a initial config file") 65 | f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644) 66 | if err != nil { 67 | return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) 68 | } 69 | f.Write([]byte(`mixed-port: 7890`)) 70 | f.Close() 71 | } 72 | 73 | // initial mmdb 74 | if err := initMMDB(); err != nil { 75 | return fmt.Errorf("can't initial MMDB: %w", err) 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /constant/context.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/gofrs/uuid" 7 | ) 8 | 9 | type PlainContext interface { 10 | ID() uuid.UUID 11 | } 12 | 13 | type ConnContext interface { 14 | PlainContext 15 | Metadata() *Metadata 16 | Conn() net.Conn 17 | } 18 | 19 | type PacketConnContext interface { 20 | PlainContext 21 | Metadata() *Metadata 22 | PacketConn() net.PacketConn 23 | } 24 | -------------------------------------------------------------------------------- /constant/dns.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // DNSModeMapping is a mapping for EnhancedMode enum 9 | var DNSModeMapping = map[string]DNSMode{ 10 | DNSNormal.String(): DNSNormal, 11 | DNSFakeIP.String(): DNSFakeIP, 12 | DNSMapping.String(): DNSMapping, 13 | } 14 | 15 | const ( 16 | DNSNormal DNSMode = iota 17 | DNSFakeIP 18 | DNSMapping 19 | ) 20 | 21 | type DNSMode int 22 | 23 | // UnmarshalYAML unserialize EnhancedMode with yaml 24 | func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error { 25 | var tp string 26 | if err := unmarshal(&tp); err != nil { 27 | return err 28 | } 29 | mode, exist := DNSModeMapping[tp] 30 | if !exist { 31 | return errors.New("invalid mode") 32 | } 33 | *e = mode 34 | return nil 35 | } 36 | 37 | // MarshalYAML serialize EnhancedMode with yaml 38 | func (e DNSMode) MarshalYAML() (any, error) { 39 | return e.String(), nil 40 | } 41 | 42 | // UnmarshalJSON unserialize EnhancedMode with json 43 | func (e *DNSMode) UnmarshalJSON(data []byte) error { 44 | var tp string 45 | json.Unmarshal(data, &tp) 46 | mode, exist := DNSModeMapping[tp] 47 | if !exist { 48 | return errors.New("invalid mode") 49 | } 50 | *e = mode 51 | return nil 52 | } 53 | 54 | // MarshalJSON serialize EnhancedMode with json 55 | func (e DNSMode) MarshalJSON() ([]byte, error) { 56 | return json.Marshal(e.String()) 57 | } 58 | 59 | func (e DNSMode) String() string { 60 | switch e { 61 | case DNSNormal: 62 | return "normal" 63 | case DNSFakeIP: 64 | return "fake-ip" 65 | case DNSMapping: 66 | return "redir-host" 67 | default: 68 | return "unknown" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /constant/listener.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | type Listener interface { 4 | RawAddress() string 5 | Address() string 6 | Close() error 7 | } 8 | -------------------------------------------------------------------------------- /constant/mime/mime.go: -------------------------------------------------------------------------------- 1 | package mime 2 | 3 | import ( 4 | "mime" 5 | ) 6 | 7 | var consensusMimes = map[string]string{ 8 | // rfc4329: text/javascript is obsolete, so we need to overwrite mime's builtin 9 | ".js": "application/javascript; charset=utf-8", 10 | } 11 | 12 | func init() { 13 | for ext, typ := range consensusMimes { 14 | mime.AddExtensionType(ext, typ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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 = func() *path { 13 | homeDir, err := os.UserHomeDir() 14 | if err != nil { 15 | homeDir, _ = os.Getwd() 16 | } 17 | 18 | homeDir = P.Join(homeDir, ".config", Name) 19 | return &path{homeDir: homeDir, configFile: "config.yaml"} 20 | }() 21 | 22 | type path struct { 23 | homeDir string 24 | configFile string 25 | } 26 | 27 | // SetHomeDir is used to set the configuration path 28 | func SetHomeDir(root string) { 29 | Path.homeDir = root 30 | } 31 | 32 | // SetConfig is used to set the configuration file 33 | func SetConfig(file string) { 34 | Path.configFile = file 35 | } 36 | 37 | func (p *path) HomeDir() string { 38 | return p.homeDir 39 | } 40 | 41 | func (p *path) Config() string { 42 | return p.configFile 43 | } 44 | 45 | // Resolve return a absolute path or a relative path with homedir 46 | func (p *path) Resolve(path string) string { 47 | if !filepath.IsAbs(path) { 48 | return filepath.Join(p.HomeDir(), path) 49 | } 50 | 51 | return path 52 | } 53 | 54 | func (p *path) MMDB() string { 55 | return P.Join(p.homeDir, "Country.mmdb") 56 | } 57 | 58 | func (p *path) OldCache() string { 59 | return P.Join(p.homeDir, ".cache") 60 | } 61 | 62 | func (p *path) Cache() string { 63 | return P.Join(p.homeDir, "cache.db") 64 | } 65 | -------------------------------------------------------------------------------- /constant/provider/interface.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | // Vehicle Type 8 | const ( 9 | File VehicleType = iota 10 | HTTP 11 | Compatible 12 | ) 13 | 14 | // VehicleType defined 15 | type VehicleType int 16 | 17 | func (v VehicleType) String() string { 18 | switch v { 19 | case File: 20 | return "File" 21 | case HTTP: 22 | return "HTTP" 23 | case Compatible: 24 | return "Compatible" 25 | default: 26 | return "Unknown" 27 | } 28 | } 29 | 30 | type Vehicle interface { 31 | Read() ([]byte, error) 32 | Path() string 33 | Type() VehicleType 34 | } 35 | 36 | // Provider Type 37 | const ( 38 | Proxy ProviderType = iota 39 | Rule 40 | ) 41 | 42 | // ProviderType defined 43 | type ProviderType int 44 | 45 | func (pt ProviderType) String() string { 46 | switch pt { 47 | case Proxy: 48 | return "Proxy" 49 | case Rule: 50 | return "Rule" 51 | default: 52 | return "Unknown" 53 | } 54 | } 55 | 56 | // Provider interface 57 | type Provider interface { 58 | Name() string 59 | VehicleType() VehicleType 60 | Type() ProviderType 61 | Initial() error 62 | Update() error 63 | } 64 | 65 | // ProxyProvider interface 66 | type ProxyProvider interface { 67 | Provider 68 | Proxies() []constant.Proxy 69 | // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. 70 | // Commonly used in DialContext and DialPacketConn 71 | ProxiesWithTouch() []constant.Proxy 72 | HealthCheck() 73 | } 74 | 75 | // Rule Type 76 | const ( 77 | Domain RuleType = iota 78 | IPCIDR 79 | Classical 80 | ) 81 | 82 | // RuleType defined 83 | type RuleType int 84 | 85 | func (rt RuleType) String() string { 86 | switch rt { 87 | case Domain: 88 | return "Domain" 89 | case IPCIDR: 90 | return "IPCIDR" 91 | case Classical: 92 | return "Classical" 93 | default: 94 | return "Unknown" 95 | } 96 | } 97 | 98 | // RuleProvider interface 99 | type RuleProvider interface { 100 | Provider 101 | Behavior() RuleType 102 | Match(*constant.Metadata) bool 103 | ShouldResolveIP() bool 104 | AsRule(adaptor string) constant.Rule 105 | } 106 | -------------------------------------------------------------------------------- /constant/rule.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // Rule Type 4 | const ( 5 | Domain RuleType = iota 6 | DomainSuffix 7 | DomainKeyword 8 | GEOIP 9 | IPCIDR 10 | SrcIPCIDR 11 | SrcPort 12 | DstPort 13 | Process 14 | ProcessPath 15 | MATCH 16 | ) 17 | 18 | type RuleType int 19 | 20 | func (rt RuleType) String() string { 21 | switch rt { 22 | case Domain: 23 | return "Domain" 24 | case DomainSuffix: 25 | return "DomainSuffix" 26 | case DomainKeyword: 27 | return "DomainKeyword" 28 | case GEOIP: 29 | return "GeoIP" 30 | case IPCIDR: 31 | return "IPCIDR" 32 | case SrcIPCIDR: 33 | return "SrcIPCIDR" 34 | case SrcPort: 35 | return "SrcPort" 36 | case DstPort: 37 | return "DstPort" 38 | case Process: 39 | return "Process" 40 | case ProcessPath: 41 | return "ProcessPath" 42 | case MATCH: 43 | return "Match" 44 | default: 45 | return "Unknown" 46 | } 47 | } 48 | 49 | type Rule interface { 50 | RuleType() RuleType 51 | Match(metadata *Metadata) bool 52 | Adapter() string 53 | Payload() string 54 | ShouldResolveIP() bool 55 | ShouldFindProcess() bool 56 | } 57 | -------------------------------------------------------------------------------- /constant/version.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var ( 4 | Version = "unknown version" 5 | BuildTime = "unknown time" 6 | ) 7 | -------------------------------------------------------------------------------- /context/conn.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/gofrs/uuid" 8 | ) 9 | 10 | type ConnContext struct { 11 | id uuid.UUID 12 | metadata *C.Metadata 13 | conn net.Conn 14 | } 15 | 16 | func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { 17 | id, _ := uuid.NewV4() 18 | return &ConnContext{ 19 | id: id, 20 | metadata: metadata, 21 | conn: conn, 22 | } 23 | } 24 | 25 | // ID implement C.ConnContext ID 26 | func (c *ConnContext) ID() uuid.UUID { 27 | return c.id 28 | } 29 | 30 | // Metadata implement C.ConnContext Metadata 31 | func (c *ConnContext) Metadata() *C.Metadata { 32 | return c.metadata 33 | } 34 | 35 | // Conn implement C.ConnContext Conn 36 | func (c *ConnContext) Conn() net.Conn { 37 | return c.conn 38 | } 39 | -------------------------------------------------------------------------------- /context/dns.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/miekg/dns" 6 | ) 7 | 8 | const ( 9 | DNSTypeHost = "host" 10 | DNSTypeFakeIP = "fakeip" 11 | DNSTypeRaw = "raw" 12 | ) 13 | 14 | type DNSContext struct { 15 | id uuid.UUID 16 | msg *dns.Msg 17 | tp string 18 | } 19 | 20 | func NewDNSContext(msg *dns.Msg) *DNSContext { 21 | id, _ := uuid.NewV4() 22 | return &DNSContext{ 23 | id: id, 24 | msg: msg, 25 | } 26 | } 27 | 28 | // ID implement C.PlainContext ID 29 | func (c *DNSContext) ID() uuid.UUID { 30 | return c.id 31 | } 32 | 33 | // SetType set type of response 34 | func (c *DNSContext) SetType(tp string) { 35 | c.tp = tp 36 | } 37 | 38 | // Type return type of response 39 | func (c *DNSContext) Type() string { 40 | return c.tp 41 | } 42 | -------------------------------------------------------------------------------- /context/packetconn.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | "github.com/gofrs/uuid" 8 | ) 9 | 10 | type PacketConnContext struct { 11 | id uuid.UUID 12 | metadata *C.Metadata 13 | packetConn net.PacketConn 14 | } 15 | 16 | func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext { 17 | id, _ := uuid.NewV4() 18 | return &PacketConnContext{ 19 | id: id, 20 | metadata: metadata, 21 | } 22 | } 23 | 24 | // ID implement C.PacketConnContext ID 25 | func (pc *PacketConnContext) ID() uuid.UUID { 26 | return pc.id 27 | } 28 | 29 | // Metadata implement C.PacketConnContext Metadata 30 | func (pc *PacketConnContext) Metadata() *C.Metadata { 31 | return pc.metadata 32 | } 33 | 34 | // PacketConn implement C.PacketConnContext PacketConn 35 | func (pc *PacketConnContext) PacketConn() net.PacketConn { 36 | return pc.packetConn 37 | } 38 | 39 | // InjectPacketConn injectPacketConn manually 40 | func (pc *PacketConnContext) InjectPacketConn(pconn C.PacketConn) { 41 | pc.packetConn = pconn 42 | } 43 | -------------------------------------------------------------------------------- /dns/client.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "strings" 9 | 10 | "github.com/Dreamacro/clash/component/dialer" 11 | "github.com/Dreamacro/clash/component/resolver" 12 | D "github.com/miekg/dns" 13 | ) 14 | 15 | type client struct { 16 | *D.Client 17 | r *Resolver 18 | port string 19 | host string 20 | iface string 21 | } 22 | 23 | func (c *client) Exchange(m *D.Msg) (*D.Msg, error) { 24 | return c.ExchangeContext(context.Background(), m) 25 | } 26 | 27 | func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { 28 | var ( 29 | ip net.IP 30 | err error 31 | ) 32 | if c.r == nil { 33 | // a default ip dns 34 | if ip = net.ParseIP(c.host); ip == nil { 35 | return nil, fmt.Errorf("dns %s not a valid ip", c.host) 36 | } 37 | } else { 38 | if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil { 39 | return nil, fmt.Errorf("use default dns resolve failed: %w", err) 40 | } 41 | } 42 | 43 | network := "udp" 44 | if strings.HasPrefix(c.Client.Net, "tcp") { 45 | network = "tcp" 46 | } 47 | 48 | options := []dialer.Option{} 49 | if c.iface != "" { 50 | options = append(options, dialer.WithInterface(c.iface)) 51 | } 52 | conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...) 53 | if err != nil { 54 | return nil, err 55 | } 56 | defer conn.Close() 57 | 58 | // miekg/dns ExchangeContext doesn't respond to context cancel. 59 | // this is a workaround 60 | type result struct { 61 | msg *D.Msg 62 | err error 63 | } 64 | ch := make(chan result, 1) 65 | go func() { 66 | if strings.HasSuffix(c.Client.Net, "tls") { 67 | conn = tls.Client(conn, c.Client.TLSConfig) 68 | } 69 | 70 | msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ 71 | Conn: conn, 72 | UDPSize: c.Client.UDPSize, 73 | TsigSecret: c.Client.TsigSecret, 74 | TsigProvider: c.Client.TsigProvider, 75 | }) 76 | 77 | ch <- result{msg, err} 78 | }() 79 | 80 | select { 81 | case <-ctx.Done(): 82 | return nil, ctx.Err() 83 | case ret := <-ch: 84 | return ret.msg, ret.err 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /dns/enhancer.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/cache" 7 | "github.com/Dreamacro/clash/component/fakeip" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type ResolverEnhancer struct { 12 | mode C.DNSMode 13 | fakePool *fakeip.Pool 14 | mapping *cache.LruCache 15 | } 16 | 17 | func (h *ResolverEnhancer) FakeIPEnabled() bool { 18 | return h.mode == C.DNSFakeIP 19 | } 20 | 21 | func (h *ResolverEnhancer) MappingEnabled() bool { 22 | return h.mode == C.DNSFakeIP || h.mode == C.DNSMapping 23 | } 24 | 25 | func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool { 26 | if !h.FakeIPEnabled() { 27 | return false 28 | } 29 | 30 | if pool := h.fakePool; pool != nil { 31 | return pool.Exist(ip) 32 | } 33 | 34 | return false 35 | } 36 | 37 | func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { 38 | if !h.FakeIPEnabled() { 39 | return false 40 | } 41 | 42 | if pool := h.fakePool; pool != nil { 43 | return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(ip) 44 | } 45 | 46 | return false 47 | } 48 | 49 | func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { 50 | if pool := h.fakePool; pool != nil { 51 | if host, existed := pool.LookBack(ip); existed { 52 | return host, true 53 | } 54 | } 55 | 56 | if mapping := h.mapping; mapping != nil { 57 | if host, existed := h.mapping.Get(ip.String()); existed { 58 | return host.(string), true 59 | } 60 | } 61 | 62 | return "", false 63 | } 64 | 65 | func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { 66 | if h.mapping != nil && o.mapping != nil { 67 | o.mapping.CloneTo(h.mapping) 68 | } 69 | 70 | if h.fakePool != nil && o.fakePool != nil { 71 | h.fakePool.CloneFrom(o.fakePool) 72 | } 73 | } 74 | 75 | func NewEnhancer(cfg Config) *ResolverEnhancer { 76 | var fakePool *fakeip.Pool 77 | var mapping *cache.LruCache 78 | 79 | if cfg.EnhancedMode != C.DNSNormal { 80 | fakePool = cfg.Pool 81 | mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)) 82 | } 83 | 84 | return &ResolverEnhancer{ 85 | mode: cfg.EnhancedMode, 86 | fakePool: fakePool, 87 | mapping: mapping, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dns/filters.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/Dreamacro/clash/component/mmdb" 8 | "github.com/Dreamacro/clash/component/trie" 9 | ) 10 | 11 | type fallbackIPFilter interface { 12 | Match(net.IP) bool 13 | } 14 | 15 | type geoipFilter struct { 16 | code string 17 | } 18 | 19 | func (gf *geoipFilter) Match(ip net.IP) bool { 20 | record, _ := mmdb.Instance().Country(ip) 21 | return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate() 22 | } 23 | 24 | type ipnetFilter struct { 25 | ipnet *net.IPNet 26 | } 27 | 28 | func (inf *ipnetFilter) Match(ip net.IP) bool { 29 | return inf.ipnet.Contains(ip) 30 | } 31 | 32 | type fallbackDomainFilter interface { 33 | Match(domain string) bool 34 | } 35 | 36 | type domainFilter struct { 37 | tree *trie.DomainTrie 38 | } 39 | 40 | func NewDomainFilter(domains []string) *domainFilter { 41 | df := domainFilter{tree: trie.New()} 42 | for _, domain := range domains { 43 | df.tree.Insert(domain, "") 44 | } 45 | return &df 46 | } 47 | 48 | func (df *domainFilter) Match(domain string) bool { 49 | return df.tree.Search(domain) != nil 50 | } 51 | -------------------------------------------------------------------------------- /dns/server.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/common/sockopt" 8 | "github.com/Dreamacro/clash/context" 9 | "github.com/Dreamacro/clash/log" 10 | D "github.com/miekg/dns" 11 | ) 12 | 13 | var ( 14 | address string 15 | server = &Server{} 16 | 17 | dnsDefaultTTL uint32 = 600 18 | ) 19 | 20 | type Server struct { 21 | *D.Server 22 | handler handler 23 | } 24 | 25 | // ServeDNS implement D.Handler ServeDNS 26 | func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { 27 | msg, err := handlerWithContext(s.handler, r) 28 | if err != nil { 29 | D.HandleFailed(w, r) 30 | return 31 | } 32 | msg.Compress = true 33 | w.WriteMsg(msg) 34 | } 35 | 36 | func handlerWithContext(handler handler, msg *D.Msg) (*D.Msg, error) { 37 | if len(msg.Question) == 0 { 38 | return nil, errors.New("at least one question is required") 39 | } 40 | 41 | ctx := context.NewDNSContext(msg) 42 | return handler(ctx, msg) 43 | } 44 | 45 | func (s *Server) setHandler(handler handler) { 46 | s.handler = handler 47 | } 48 | 49 | func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) { 50 | if addr == address && resolver != nil { 51 | handler := newHandler(resolver, mapper) 52 | server.setHandler(handler) 53 | return 54 | } 55 | 56 | if server.Server != nil { 57 | server.Shutdown() 58 | server = &Server{} 59 | address = "" 60 | } 61 | 62 | if addr == "" { 63 | return 64 | } 65 | 66 | var err error 67 | defer func() { 68 | if err != nil { 69 | log.Errorln("Start DNS server error: %s", err.Error()) 70 | } 71 | }() 72 | 73 | _, port, err := net.SplitHostPort(addr) 74 | if port == "0" || port == "" || err != nil { 75 | return 76 | } 77 | 78 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 79 | if err != nil { 80 | return 81 | } 82 | 83 | p, err := net.ListenUDP("udp", udpAddr) 84 | if err != nil { 85 | return 86 | } 87 | 88 | err = sockopt.UDPReuseaddr(p) 89 | if err != nil { 90 | log.Warnln("Failed to Reuse UDP Address: %s", err) 91 | 92 | err = nil 93 | } 94 | 95 | address = addr 96 | handler := newHandler(resolver, mapper) 97 | server = &Server{handler: handler} 98 | server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} 99 | 100 | go func() { 101 | server.ActivateAndServe() 102 | }() 103 | 104 | log.Infoln("DNS server listening at: %s", p.LocalAddr().String()) 105 | } 106 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/clash/c63dac03d24302513924cc952b07f225b9d8f62f/docs/logo.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Dreamacro/clash 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.0.7 7 | github.com/go-chi/cors v1.2.1 8 | github.com/go-chi/render v1.0.1 9 | github.com/gofrs/uuid v4.2.0+incompatible 10 | github.com/gorilla/websocket v1.5.0 11 | github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f 12 | github.com/miekg/dns v1.1.48 13 | github.com/oschwald/geoip2-golang v1.7.0 14 | github.com/sagernet/sing v0.0.0-20220606113732-5efacc1c7db5 15 | github.com/sagernet/sing-shadowsocks v0.0.0-20220607021139-f9c820eb0fc8 16 | github.com/sirupsen/logrus v1.8.1 17 | github.com/stretchr/testify v1.7.1 18 | go.etcd.io/bbolt v1.3.6 19 | go.uber.org/atomic v1.9.0 20 | go.uber.org/automaxprocs v1.5.1 21 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e 22 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 23 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 24 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a 25 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 26 | ) 27 | 28 | require ( 29 | github.com/davecgh/go-spew v1.1.1 // indirect 30 | github.com/klauspost/cpuid/v2 v2.0.12 // indirect 31 | github.com/kr/text v0.2.0 // indirect 32 | github.com/oschwald/maxminddb-golang v1.9.0 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect 35 | golang.org/x/mod v0.4.2 // indirect 36 | golang.org/x/text v0.3.7 // indirect 37 | golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect 38 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 39 | lukechampine.com/blake3 v1.1.7 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /hub/hub.go: -------------------------------------------------------------------------------- 1 | package hub 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/config" 5 | "github.com/Dreamacro/clash/hub/executor" 6 | "github.com/Dreamacro/clash/hub/route" 7 | ) 8 | 9 | type Option func(*config.Config) 10 | 11 | func WithExternalUI(externalUI string) Option { 12 | return func(cfg *config.Config) { 13 | cfg.General.ExternalUI = externalUI 14 | } 15 | } 16 | 17 | func WithExternalController(externalController string) Option { 18 | return func(cfg *config.Config) { 19 | cfg.General.ExternalController = externalController 20 | } 21 | } 22 | 23 | func WithSecret(secret string) Option { 24 | return func(cfg *config.Config) { 25 | cfg.General.Secret = secret 26 | } 27 | } 28 | 29 | // Parse call at the beginning of clash 30 | func Parse(options ...Option) error { 31 | cfg, err := executor.Parse() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | for _, option := range options { 37 | option(cfg) 38 | } 39 | 40 | if cfg.General.ExternalUI != "" { 41 | route.SetUIPath(cfg.General.ExternalUI) 42 | } 43 | 44 | if cfg.General.ExternalController != "" { 45 | go route.Start(cfg.General.ExternalController, cfg.General.Secret) 46 | } 47 | 48 | executor.ApplyConfig(cfg, true) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /hub/route/common.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | // When name is composed of a partial escape string, Golang does not unescape it 11 | func getEscapeParam(r *http.Request, paramName string) string { 12 | param := chi.URLParam(r, paramName) 13 | if newParam, err := url.PathUnescape(param); err == nil { 14 | param = newParam 15 | } 16 | return param 17 | } 18 | -------------------------------------------------------------------------------- /hub/route/connections.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/Dreamacro/clash/tunnel/statistic" 11 | "github.com/go-chi/chi/v5" 12 | "github.com/go-chi/render" 13 | "github.com/gorilla/websocket" 14 | ) 15 | 16 | func connectionRouter() http.Handler { 17 | r := chi.NewRouter() 18 | r.Get("/", getConnections) 19 | r.Delete("/", closeAllConnections) 20 | r.Delete("/{id}", closeConnection) 21 | return r 22 | } 23 | 24 | func getConnections(w http.ResponseWriter, r *http.Request) { 25 | if !websocket.IsWebSocketUpgrade(r) { 26 | snapshot := statistic.DefaultManager.Snapshot() 27 | render.JSON(w, r, snapshot) 28 | return 29 | } 30 | 31 | conn, err := upgrader.Upgrade(w, r, nil) 32 | if err != nil { 33 | return 34 | } 35 | 36 | intervalStr := r.URL.Query().Get("interval") 37 | interval := 1000 38 | if intervalStr != "" { 39 | t, err := strconv.Atoi(intervalStr) 40 | if err != nil { 41 | render.Status(r, http.StatusBadRequest) 42 | render.JSON(w, r, ErrBadRequest) 43 | return 44 | } 45 | 46 | interval = t 47 | } 48 | 49 | buf := &bytes.Buffer{} 50 | sendSnapshot := func() error { 51 | buf.Reset() 52 | snapshot := statistic.DefaultManager.Snapshot() 53 | if err := json.NewEncoder(buf).Encode(snapshot); err != nil { 54 | return err 55 | } 56 | 57 | return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) 58 | } 59 | 60 | if err := sendSnapshot(); err != nil { 61 | return 62 | } 63 | 64 | tick := time.NewTicker(time.Millisecond * time.Duration(interval)) 65 | defer tick.Stop() 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 := statistic.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 := statistic.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/constant/provider" 8 | "github.com/Dreamacro/clash/tunnel" 9 | "github.com/go-chi/chi/v5" 10 | "github.com/go-chi/render" 11 | ) 12 | 13 | func proxyProviderRouter() http.Handler { 14 | r := chi.NewRouter() 15 | r.Get("/", getProviders) 16 | 17 | r.Route("/{name}", func(r chi.Router) { 18 | r.Use(parseProviderName, findProviderByName) 19 | r.Get("/", getProvider) 20 | r.Put("/", updateProvider) 21 | r.Get("/healthcheck", healthCheckProvider) 22 | }) 23 | return r 24 | } 25 | 26 | func getProviders(w http.ResponseWriter, r *http.Request) { 27 | providers := tunnel.Providers() 28 | render.JSON(w, r, render.M{ 29 | "providers": providers, 30 | }) 31 | } 32 | 33 | func getProvider(w http.ResponseWriter, r *http.Request) { 34 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 35 | render.JSON(w, r, provider) 36 | } 37 | 38 | func updateProvider(w http.ResponseWriter, r *http.Request) { 39 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 40 | if err := provider.Update(); err != nil { 41 | render.Status(r, http.StatusServiceUnavailable) 42 | render.JSON(w, r, newError(err.Error())) 43 | return 44 | } 45 | render.NoContent(w, r) 46 | } 47 | 48 | func healthCheckProvider(w http.ResponseWriter, r *http.Request) { 49 | provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) 50 | provider.HealthCheck() 51 | render.NoContent(w, r) 52 | } 53 | 54 | func parseProviderName(next http.Handler) http.Handler { 55 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 | name := getEscapeParam(r, "name") 57 | ctx := context.WithValue(r.Context(), CtxKeyProviderName, name) 58 | next.ServeHTTP(w, r.WithContext(ctx)) 59 | }) 60 | } 61 | 62 | func findProviderByName(next http.Handler) http.Handler { 63 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 64 | name := r.Context().Value(CtxKeyProviderName).(string) 65 | providers := tunnel.Providers() 66 | provider, exist := providers[name] 67 | if !exist { 68 | render.Status(r, http.StatusNotFound) 69 | render.JSON(w, r, ErrNotFound) 70 | return 71 | } 72 | 73 | ctx := context.WithValue(r.Context(), CtxKeyProvider, provider) 74 | next.ServeHTTP(w, r.WithContext(ctx)) 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /hub/route/rules.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Dreamacro/clash/tunnel" 7 | "github.com/go-chi/chi/v5" 8 | "github.com/go-chi/render" 9 | ) 10 | 11 | func ruleRouter() http.Handler { 12 | r := chi.NewRouter() 13 | r.Get("/", getRules) 14 | return r 15 | } 16 | 17 | type Rule struct { 18 | Type string `json:"type"` 19 | Payload string `json:"payload"` 20 | Proxy string `json:"proxy"` 21 | } 22 | 23 | func getRules(w http.ResponseWriter, r *http.Request) { 24 | rawRules := tunnel.Rules() 25 | 26 | rules := []Rule{} 27 | for _, rule := range rawRules { 28 | rules = append(rules, Rule{ 29 | Type: rule.RuleType().String(), 30 | Payload: rule.Payload(), 31 | Proxy: rule.Adapter(), 32 | }) 33 | } 34 | 35 | render.JSON(w, r, render.M{ 36 | "rules": rules, 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /listener/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/auth" 5 | ) 6 | 7 | var authenticator auth.Authenticator 8 | 9 | func Authenticator() auth.Authenticator { 10 | return authenticator 11 | } 12 | 13 | func SetAuthenticator(au auth.Authenticator) { 14 | authenticator = au 15 | } 16 | -------------------------------------------------------------------------------- /listener/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/Dreamacro/clash/adapter/inbound" 11 | C "github.com/Dreamacro/clash/constant" 12 | "github.com/Dreamacro/clash/transport/socks5" 13 | ) 14 | 15 | func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client { 16 | return &http.Client{ 17 | Transport: &http.Transport{ 18 | // from http.DefaultTransport 19 | MaxIdleConns: 100, 20 | IdleConnTimeout: 90 * time.Second, 21 | TLSHandshakeTimeout: 10 * time.Second, 22 | ExpectContinueTimeout: 1 * time.Second, 23 | DialContext: func(context context.Context, network, address string) (net.Conn, error) { 24 | if network != "tcp" && network != "tcp4" && network != "tcp6" { 25 | return nil, errors.New("unsupported network " + network) 26 | } 27 | 28 | dstAddr := socks5.ParseAddr(address) 29 | if dstAddr == nil { 30 | return nil, socks5.ErrAddressNotSupported 31 | } 32 | 33 | left, right := net.Pipe() 34 | 35 | in <- inbound.NewHTTP(dstAddr, source, right) 36 | 37 | return left, nil 38 | }, 39 | }, 40 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 41 | return http.ErrUseLastResponse 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /listener/http/hack.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "net/http" 6 | _ "unsafe" 7 | ) 8 | 9 | //go:linkname ReadRequest net/http.readRequest 10 | func ReadRequest(b *bufio.Reader) (req *http.Request, err error) 11 | -------------------------------------------------------------------------------- /listener/http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/common/cache" 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type Listener struct { 12 | listener net.Listener 13 | addr string 14 | closed bool 15 | } 16 | 17 | // RawAddress implements C.Listener 18 | func (l *Listener) RawAddress() string { 19 | return l.addr 20 | } 21 | 22 | // Address implements C.Listener 23 | func (l *Listener) Address() string { 24 | return l.listener.Addr().String() 25 | } 26 | 27 | // Close implements C.Listener 28 | func (l *Listener) Close() error { 29 | l.closed = true 30 | return l.listener.Close() 31 | } 32 | 33 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 34 | return NewWithAuthenticate(addr, in, true) 35 | } 36 | 37 | func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) { 38 | l, err := net.Listen("tcp", addr) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | var c *cache.Cache 44 | if authenticate { 45 | c = cache.New(time.Second * 30) 46 | } 47 | 48 | hl := &Listener{ 49 | listener: l, 50 | addr: addr, 51 | } 52 | go func() { 53 | for { 54 | conn, err := hl.listener.Accept() 55 | if err != nil { 56 | if hl.closed { 57 | break 58 | } 59 | continue 60 | } 61 | go HandleConn(conn, in, c) 62 | } 63 | }() 64 | 65 | return hl, nil 66 | } 67 | -------------------------------------------------------------------------------- /listener/http/upgrade.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/adapter/inbound" 9 | N "github.com/Dreamacro/clash/common/net" 10 | C "github.com/Dreamacro/clash/constant" 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | ) 13 | 14 | func isUpgradeRequest(req *http.Request) bool { 15 | for _, header := range req.Header["Connection"] { 16 | for _, elm := range strings.Split(header, ",") { 17 | if strings.EqualFold(strings.TrimSpace(elm), "Upgrade") { 18 | return true 19 | } 20 | } 21 | } 22 | 23 | return false 24 | } 25 | 26 | func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext) { 27 | defer conn.Close() 28 | 29 | removeProxyHeaders(request.Header) 30 | removeExtraHTTPHostPort(request) 31 | 32 | address := request.Host 33 | if _, _, err := net.SplitHostPort(address); err != nil { 34 | address = net.JoinHostPort(address, "80") 35 | } 36 | 37 | dstAddr := socks5.ParseAddr(address) 38 | if dstAddr == nil { 39 | return 40 | } 41 | 42 | left, right := net.Pipe() 43 | 44 | in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right) 45 | 46 | bufferedLeft := N.NewBufferedConn(left) 47 | defer bufferedLeft.Close() 48 | 49 | err := request.Write(bufferedLeft) 50 | if err != nil { 51 | return 52 | } 53 | 54 | resp, err := http.ReadResponse(bufferedLeft.Reader(), request) 55 | if err != nil { 56 | return 57 | } 58 | 59 | removeProxyHeaders(resp.Header) 60 | 61 | err = resp.Write(conn) 62 | if err != nil { 63 | return 64 | } 65 | 66 | if resp.StatusCode == http.StatusSwitchingProtocols { 67 | N.Relay(bufferedLeft, conn) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /listener/http/utils.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "net" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | // removeHopByHopHeaders remove Proxy-* headers 12 | func removeProxyHeaders(header http.Header) { 13 | header.Del("Proxy-Connection") 14 | header.Del("Proxy-Authenticate") 15 | header.Del("Proxy-Authorization") 16 | } 17 | 18 | // removeHopByHopHeaders remove hop-by-hop header 19 | func removeHopByHopHeaders(header http.Header) { 20 | // Strip hop-by-hop header based on RFC: 21 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 22 | // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do 23 | 24 | removeProxyHeaders(header) 25 | 26 | header.Del("TE") 27 | header.Del("Trailers") 28 | header.Del("Transfer-Encoding") 29 | header.Del("Upgrade") 30 | 31 | connections := header.Get("Connection") 32 | header.Del("Connection") 33 | if len(connections) == 0 { 34 | return 35 | } 36 | for _, h := range strings.Split(connections, ",") { 37 | header.Del(strings.TrimSpace(h)) 38 | } 39 | } 40 | 41 | // removeExtraHTTPHostPort remove extra host port (example.com:80 --> example.com) 42 | // It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com) 43 | func removeExtraHTTPHostPort(req *http.Request) { 44 | host := req.Host 45 | if host == "" { 46 | host = req.URL.Host 47 | } 48 | 49 | if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" { 50 | host = pHost 51 | } 52 | 53 | req.Host = host 54 | req.URL.Host = host 55 | } 56 | 57 | // parseBasicProxyAuthorization parse header Proxy-Authorization and return base64-encoded credential 58 | func parseBasicProxyAuthorization(request *http.Request) string { 59 | value := request.Header.Get("Proxy-Authorization") 60 | if !strings.HasPrefix(value, "Basic ") { 61 | return "" 62 | } 63 | 64 | return value[6:] // value[len("Basic "):] 65 | } 66 | 67 | // decodeBasicProxyAuthorization decode base64-encoded credential 68 | func decodeBasicProxyAuthorization(credential string) (string, string, error) { 69 | plain, err := base64.StdEncoding.DecodeString(credential) 70 | if err != nil { 71 | return "", "", err 72 | } 73 | 74 | user, pass, found := strings.Cut(string(plain), ":") 75 | if !found { 76 | return "", "", errors.New("invalid login") 77 | } 78 | 79 | return user, pass, nil 80 | } 81 | -------------------------------------------------------------------------------- /listener/mixed/mixed.go: -------------------------------------------------------------------------------- 1 | package mixed 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/Dreamacro/clash/common/cache" 8 | N "github.com/Dreamacro/clash/common/net" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/listener/http" 11 | "github.com/Dreamacro/clash/listener/socks" 12 | "github.com/Dreamacro/clash/transport/socks4" 13 | "github.com/Dreamacro/clash/transport/socks5" 14 | ) 15 | 16 | type Listener struct { 17 | listener net.Listener 18 | addr string 19 | cache *cache.Cache 20 | closed bool 21 | } 22 | 23 | // RawAddress implements C.Listener 24 | func (l *Listener) RawAddress() string { 25 | return l.addr 26 | } 27 | 28 | // Address implements C.Listener 29 | func (l *Listener) Address() string { 30 | return l.listener.Addr().String() 31 | } 32 | 33 | // Close implements C.Listener 34 | func (l *Listener) Close() error { 35 | l.closed = true 36 | return l.listener.Close() 37 | } 38 | 39 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 40 | l, err := net.Listen("tcp", addr) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | ml := &Listener{ 46 | listener: l, 47 | addr: addr, 48 | cache: cache.New(30 * time.Second), 49 | } 50 | go func() { 51 | for { 52 | c, err := ml.listener.Accept() 53 | if err != nil { 54 | if ml.closed { 55 | break 56 | } 57 | continue 58 | } 59 | go handleConn(c, in, ml.cache) 60 | } 61 | }() 62 | 63 | return ml, nil 64 | } 65 | 66 | func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.Cache) { 67 | conn.(*net.TCPConn).SetKeepAlive(true) 68 | 69 | bufConn := N.NewBufferedConn(conn) 70 | head, err := bufConn.Peek(1) 71 | if err != nil { 72 | return 73 | } 74 | 75 | switch head[0] { 76 | case socks4.Version: 77 | socks.HandleSocks4(bufConn, in) 78 | case socks5.Version: 79 | socks.HandleSocks5(bufConn, in) 80 | default: 81 | http.HandleConn(bufConn, in, cache) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /listener/redir/tcp.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type Listener struct { 11 | listener net.Listener 12 | addr string 13 | closed bool 14 | } 15 | 16 | // RawAddress implements C.Listener 17 | func (l *Listener) RawAddress() string { 18 | return l.addr 19 | } 20 | 21 | // Address implements C.Listener 22 | func (l *Listener) Address() string { 23 | return l.listener.Addr().String() 24 | } 25 | 26 | // Close implements C.Listener 27 | func (l *Listener) Close() error { 28 | l.closed = true 29 | return l.listener.Close() 30 | } 31 | 32 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 33 | l, err := net.Listen("tcp", addr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | rl := &Listener{ 38 | listener: l, 39 | addr: addr, 40 | } 41 | 42 | go func() { 43 | for { 44 | c, err := l.Accept() 45 | if err != nil { 46 | if rl.closed { 47 | break 48 | } 49 | continue 50 | } 51 | go handleRedir(c, in) 52 | } 53 | }() 54 | 55 | return rl, nil 56 | } 57 | 58 | func handleRedir(conn net.Conn, in chan<- C.ConnContext) { 59 | target, err := parserPacket(conn) 60 | if err != nil { 61 | conn.Close() 62 | return 63 | } 64 | conn.(*net.TCPConn).SetKeepAlive(true) 65 | in <- inbound.NewSocket(target, conn, C.REDIR) 66 | } 67 | -------------------------------------------------------------------------------- /listener/redir/tcp_darwin.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | "unsafe" 7 | 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | func parserPacket(c net.Conn) (socks5.Addr, error) { 12 | const ( 13 | PfInout = 0 14 | PfIn = 1 15 | PfOut = 2 16 | IOCOut = 0x40000000 17 | IOCIn = 0x80000000 18 | IOCInOut = IOCIn | IOCOut 19 | IOCPARMMask = 0x1FFF 20 | LEN = 4*16 + 4*4 + 4*1 21 | // #define _IOC(inout,group,num,len) (inout | ((len & IOCPARMMask) << 16) | ((group) << 8) | (num)) 22 | // #define _IOWR(g,n,t) _IOC(IOCInOut, (g), (n), sizeof(t)) 23 | // #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) 24 | DIOCNATLOOK = IOCInOut | ((LEN & IOCPARMMask) << 16) | ('D' << 8) | 23 25 | ) 26 | 27 | fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) 28 | if err != nil { 29 | return nil, err 30 | } 31 | defer syscall.Close(fd) 32 | 33 | nl := struct { // struct pfioc_natlook 34 | saddr, daddr, rsaddr, rdaddr [16]byte 35 | sxport, dxport, rsxport, rdxport [4]byte 36 | af, proto, protoVariant, direction uint8 37 | }{ 38 | af: syscall.AF_INET, 39 | proto: syscall.IPPROTO_TCP, 40 | direction: PfOut, 41 | } 42 | saddr := c.RemoteAddr().(*net.TCPAddr) 43 | daddr := c.LocalAddr().(*net.TCPAddr) 44 | copy(nl.saddr[:], saddr.IP) 45 | copy(nl.daddr[:], daddr.IP) 46 | nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port) 47 | nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) 48 | 49 | if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { 50 | return nil, errno 51 | } 52 | 53 | addr := make([]byte, 1+net.IPv4len+2) 54 | addr[0] = socks5.AtypIPv4 55 | copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) 56 | copy(addr[1+net.IPv4len:], nl.rdxport[:2]) 57 | return addr, nil 58 | } 59 | -------------------------------------------------------------------------------- /listener/redir/tcp_freebsd.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/Dreamacro/clash/transport/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 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/Dreamacro/clash/transport/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 := uint32(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 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux_386.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 9 | 10 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 11 | var a [6]uintptr 12 | a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 13 | if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { 14 | return errno 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /listener/redir/tcp_linux_other.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !386 2 | 3 | package redir 4 | 5 | import "syscall" 6 | 7 | const GETSOCKOPT = syscall.SYS_GETSOCKOPT 8 | 9 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 10 | if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { 11 | return errno 12 | } 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /listener/redir/tcp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !linux && !freebsd 2 | 3 | package redir 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | ) 11 | 12 | func parserPacket(conn net.Conn) (socks5.Addr, error) { 13 | return nil, errors.New("system not support yet") 14 | } 15 | -------------------------------------------------------------------------------- /listener/socks/udp.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | "github.com/Dreamacro/clash/common/pool" 8 | "github.com/Dreamacro/clash/common/sockopt" 9 | C "github.com/Dreamacro/clash/constant" 10 | "github.com/Dreamacro/clash/log" 11 | "github.com/Dreamacro/clash/transport/socks5" 12 | ) 13 | 14 | type UDPListener struct { 15 | packetConn net.PacketConn 16 | addr string 17 | closed bool 18 | } 19 | 20 | // RawAddress implements C.Listener 21 | func (l *UDPListener) RawAddress() string { 22 | return l.addr 23 | } 24 | 25 | // Address implements C.Listener 26 | func (l *UDPListener) Address() string { 27 | return l.packetConn.LocalAddr().String() 28 | } 29 | 30 | // Close implements C.Listener 31 | func (l *UDPListener) Close() error { 32 | l.closed = true 33 | return l.packetConn.Close() 34 | } 35 | 36 | func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { 37 | l, err := net.ListenPacket("udp", addr) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil { 43 | log.Warnln("Failed to Reuse UDP Address: %s", err) 44 | } 45 | 46 | sl := &UDPListener{ 47 | packetConn: l, 48 | addr: addr, 49 | } 50 | go func() { 51 | for { 52 | buf := pool.Get(pool.UDPBufferSize) 53 | n, remoteAddr, err := l.ReadFrom(buf) 54 | if err != nil { 55 | pool.Put(buf) 56 | if sl.closed { 57 | break 58 | } 59 | continue 60 | } 61 | handleSocksUDP(l, in, buf[:n], remoteAddr) 62 | } 63 | }() 64 | 65 | return sl, nil 66 | } 67 | 68 | func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { 69 | target, payload, err := socks5.DecodeUDPPacket(buf) 70 | if err != nil { 71 | // Unresolved UDP packet, return buffer to the pool 72 | pool.Put(buf) 73 | return 74 | } 75 | packet := &packet{ 76 | pc: pc, 77 | rAddr: addr, 78 | payload: payload, 79 | bufRef: buf, 80 | } 81 | select { 82 | case in <- inbound.NewPacket(target, packet, C.SOCKS5): 83 | default: 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /listener/socks/utils.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | "github.com/Dreamacro/clash/transport/socks5" 8 | ) 9 | 10 | type packet struct { 11 | pc net.PacketConn 12 | rAddr net.Addr 13 | payload []byte 14 | bufRef []byte 15 | } 16 | 17 | func (c *packet) Data() []byte { 18 | return c.payload 19 | } 20 | 21 | // WriteBack write UDP packet with source(ip, port) = `addr` 22 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 23 | packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) 24 | if err != nil { 25 | return 26 | } 27 | return c.pc.WriteTo(packet, c.rAddr) 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *packet) LocalAddr() net.Addr { 32 | return c.rAddr 33 | } 34 | 35 | func (c *packet) Drop() { 36 | pool.Put(c.bufRef) 37 | } 38 | -------------------------------------------------------------------------------- /listener/tproxy/packet.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | type packet struct { 10 | lAddr *net.UDPAddr 11 | buf []byte 12 | } 13 | 14 | func (c *packet) Data() []byte { 15 | return c.buf 16 | } 17 | 18 | // WriteBack opens a new socket binding `addr` to write UDP packet back 19 | func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 20 | tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) 21 | if err != nil { 22 | n = 0 23 | return 24 | } 25 | n, err = tc.Write(b) 26 | tc.Close() 27 | return 28 | } 29 | 30 | // LocalAddr returns the source IP/Port of UDP Packet 31 | func (c *packet) LocalAddr() net.Addr { 32 | return c.lAddr 33 | } 34 | 35 | func (c *packet) Drop() { 36 | pool.Put(c.buf) 37 | } 38 | -------------------------------------------------------------------------------- /listener/tproxy/setsockopt_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | func setsockopt(rc syscall.RawConn, addr string) error { 11 | isIPv6 := true 12 | host, _, err := net.SplitHostPort(addr) 13 | if err != nil { 14 | return err 15 | } 16 | ip := net.ParseIP(host) 17 | if ip != nil && ip.To4() != nil { 18 | isIPv6 = false 19 | } 20 | 21 | rc.Control(func(fd uintptr) { 22 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 23 | 24 | if err == nil { 25 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) 26 | } 27 | if err == nil && isIPv6 { 28 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) 29 | } 30 | 31 | if err == nil { 32 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) 33 | } 34 | if err == nil && isIPv6 { 35 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) 36 | } 37 | }) 38 | 39 | return err 40 | } 41 | -------------------------------------------------------------------------------- /listener/tproxy/setsockopt_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "errors" 7 | "syscall" 8 | ) 9 | 10 | func setsockopt(rc syscall.RawConn, addr string) error { 11 | return errors.New("not supported on current platform") 12 | } 13 | -------------------------------------------------------------------------------- /listener/tproxy/tproxy.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | C "github.com/Dreamacro/clash/constant" 8 | "github.com/Dreamacro/clash/transport/socks5" 9 | ) 10 | 11 | type Listener struct { 12 | listener net.Listener 13 | addr string 14 | closed bool 15 | } 16 | 17 | // RawAddress implements C.Listener 18 | func (l *Listener) RawAddress() string { 19 | return l.addr 20 | } 21 | 22 | // Address implements C.Listener 23 | func (l *Listener) Address() string { 24 | return l.listener.Addr().String() 25 | } 26 | 27 | // Close implements C.Listener 28 | func (l *Listener) Close() error { 29 | l.closed = true 30 | return l.listener.Close() 31 | } 32 | 33 | func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) { 34 | target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) 35 | conn.(*net.TCPConn).SetKeepAlive(true) 36 | in <- inbound.NewSocket(target, conn, C.TPROXY) 37 | } 38 | 39 | func New(addr string, in chan<- C.ConnContext) (*Listener, error) { 40 | l, err := net.Listen("tcp", addr) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | tl := l.(*net.TCPListener) 46 | rc, err := tl.SyscallConn() 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | err = setsockopt(rc, addr) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | rl := &Listener{ 57 | listener: l, 58 | addr: addr, 59 | } 60 | 61 | go func() { 62 | for { 63 | c, err := l.Accept() 64 | if err != nil { 65 | if rl.closed { 66 | break 67 | } 68 | continue 69 | } 70 | go rl.handleTProxy(c, in) 71 | } 72 | }() 73 | 74 | return rl, nil 75 | } 76 | -------------------------------------------------------------------------------- /listener/tproxy/udp.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/adapter/inbound" 7 | "github.com/Dreamacro/clash/common/pool" 8 | C "github.com/Dreamacro/clash/constant" 9 | "github.com/Dreamacro/clash/transport/socks5" 10 | ) 11 | 12 | type UDPListener struct { 13 | packetConn net.PacketConn 14 | addr string 15 | closed bool 16 | } 17 | 18 | // RawAddress implements C.Listener 19 | func (l *UDPListener) RawAddress() string { 20 | return l.addr 21 | } 22 | 23 | // Address implements C.Listener 24 | func (l *UDPListener) Address() string { 25 | return l.packetConn.LocalAddr().String() 26 | } 27 | 28 | // Close implements C.Listener 29 | func (l *UDPListener) Close() error { 30 | l.closed = true 31 | return l.packetConn.Close() 32 | } 33 | 34 | func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { 35 | l, err := net.ListenPacket("udp", addr) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | rl := &UDPListener{ 41 | packetConn: l, 42 | addr: addr, 43 | } 44 | 45 | c := l.(*net.UDPConn) 46 | 47 | rc, err := c.SyscallConn() 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | err = setsockopt(rc, addr) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | go func() { 58 | oob := make([]byte, 1024) 59 | for { 60 | buf := pool.Get(pool.UDPBufferSize) 61 | n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) 62 | if err != nil { 63 | pool.Put(buf) 64 | if rl.closed { 65 | break 66 | } 67 | continue 68 | } 69 | 70 | rAddr, err := getOrigDst(oob, oobn) 71 | if err != nil { 72 | continue 73 | } 74 | handlePacketConn(l, in, buf[:n], lAddr, rAddr) 75 | } 76 | }() 77 | 78 | return rl, nil 79 | } 80 | 81 | func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { 82 | target := socks5.ParseAddrToSocksAddr(rAddr) 83 | pkt := &packet{ 84 | lAddr: lAddr, 85 | buf: buf, 86 | } 87 | select { 88 | case in <- inbound.NewPacket(target, pkt, C.TPROXY): 89 | default: 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /listener/tproxy/udp_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tproxy 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { 11 | return nil, errors.New("UDP redir not supported on current platform") 12 | } 13 | 14 | func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { 15 | return nil, errors.New("UDP redir not supported on current platform") 16 | } 17 | -------------------------------------------------------------------------------- /log/level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // LogLevelMapping is a mapping for LogLevel enum 9 | var LogLevelMapping = map[string]LogLevel{ 10 | ERROR.String(): ERROR, 11 | WARNING.String(): WARNING, 12 | INFO.String(): INFO, 13 | DEBUG.String(): DEBUG, 14 | SILENT.String(): SILENT, 15 | } 16 | 17 | const ( 18 | DEBUG LogLevel = iota 19 | INFO 20 | WARNING 21 | ERROR 22 | SILENT 23 | ) 24 | 25 | type LogLevel int 26 | 27 | // UnmarshalYAML unserialize LogLevel with yaml 28 | func (l *LogLevel) UnmarshalYAML(unmarshal func(any) error) error { 29 | var tp string 30 | unmarshal(&tp) 31 | level, exist := LogLevelMapping[tp] 32 | if !exist { 33 | return errors.New("invalid mode") 34 | } 35 | *l = level 36 | return nil 37 | } 38 | 39 | // UnmarshalJSON unserialize LogLevel with json 40 | func (l *LogLevel) UnmarshalJSON(data []byte) error { 41 | var tp string 42 | json.Unmarshal(data, &tp) 43 | level, exist := LogLevelMapping[tp] 44 | if !exist { 45 | return errors.New("invalid mode") 46 | } 47 | *l = level 48 | return nil 49 | } 50 | 51 | // MarshalJSON serialize LogLevel with json 52 | func (l LogLevel) MarshalJSON() ([]byte, error) { 53 | return json.Marshal(l.String()) 54 | } 55 | 56 | // MarshalYAML serialize LogLevel with yaml 57 | func (l LogLevel) MarshalYAML() (any, error) { 58 | return l.String(), nil 59 | } 60 | 61 | func (l LogLevel) String() string { 62 | switch l { 63 | case INFO: 64 | return "info" 65 | case WARNING: 66 | return "warning" 67 | case ERROR: 68 | return "error" 69 | case DEBUG: 70 | return "debug" 71 | case SILENT: 72 | return "silent" 73 | default: 74 | return "unknown" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Dreamacro/clash/common/observable" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var ( 12 | logCh = make(chan any) 13 | source = observable.NewObservable(logCh) 14 | level = INFO 15 | ) 16 | 17 | func init() { 18 | log.SetOutput(os.Stdout) 19 | log.SetLevel(log.DebugLevel) 20 | } 21 | 22 | type Event struct { 23 | LogLevel LogLevel 24 | Payload string 25 | } 26 | 27 | func (e *Event) Type() string { 28 | return e.LogLevel.String() 29 | } 30 | 31 | func Infoln(format string, v ...any) { 32 | event := newLog(INFO, format, v...) 33 | logCh <- event 34 | print(event) 35 | } 36 | 37 | func Warnln(format string, v ...any) { 38 | event := newLog(WARNING, format, v...) 39 | logCh <- event 40 | print(event) 41 | } 42 | 43 | func Errorln(format string, v ...any) { 44 | event := newLog(ERROR, format, v...) 45 | logCh <- event 46 | print(event) 47 | } 48 | 49 | func Debugln(format string, v ...any) { 50 | event := newLog(DEBUG, format, v...) 51 | logCh <- event 52 | print(event) 53 | } 54 | 55 | func Fatalln(format string, v ...any) { 56 | log.Fatalf(format, v...) 57 | } 58 | 59 | func Subscribe() observable.Subscription { 60 | sub, _ := source.Subscribe() 61 | return sub 62 | } 63 | 64 | func UnSubscribe(sub observable.Subscription) { 65 | source.UnSubscribe(sub) 66 | } 67 | 68 | func Level() LogLevel { 69 | return level 70 | } 71 | 72 | func SetLevel(newLevel LogLevel) { 73 | level = newLevel 74 | } 75 | 76 | func print(data Event) { 77 | if data.LogLevel < level { 78 | return 79 | } 80 | 81 | switch data.LogLevel { 82 | case INFO: 83 | log.Infoln(data.Payload) 84 | case WARNING: 85 | log.Warnln(data.Payload) 86 | case ERROR: 87 | log.Errorln(data.Payload) 88 | case DEBUG: 89 | log.Debugln(data.Payload) 90 | } 91 | } 92 | 93 | func newLog(logLevel LogLevel, format string, v ...any) Event { 94 | return Event{ 95 | LogLevel: logLevel, 96 | Payload: fmt.Sprintf(format, v...), 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /rule/base.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | errPayload = errors.New("payload error") 9 | 10 | noResolve = "no-resolve" 11 | ) 12 | 13 | func HasNoResolve(params []string) bool { 14 | for _, p := range params { 15 | if p == noResolve { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | -------------------------------------------------------------------------------- /rule/domain.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Domain struct { 10 | domain string 11 | adapter string 12 | } 13 | 14 | func (d *Domain) RuleType() C.RuleType { 15 | return C.Domain 16 | } 17 | 18 | func (d *Domain) Match(metadata *C.Metadata) bool { 19 | 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) ShouldResolveIP() bool { 34 | return false 35 | } 36 | 37 | func (d *Domain) ShouldFindProcess() bool { 38 | return false 39 | } 40 | 41 | func NewDomain(domain string, adapter string) *Domain { 42 | return &Domain{ 43 | domain: strings.ToLower(domain), 44 | adapter: adapter, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rule/domain_keyword.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | 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) ShouldResolveIP() bool { 35 | return false 36 | } 37 | 38 | func (dk *DomainKeyword) ShouldFindProcess() bool { 39 | return false 40 | } 41 | 42 | func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { 43 | return &DomainKeyword{ 44 | keyword: strings.ToLower(keyword), 45 | adapter: adapter, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rule/domain_suffix.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type DomainSuffix struct { 10 | suffix string 11 | adapter string 12 | } 13 | 14 | func (ds *DomainSuffix) RuleType() C.RuleType { 15 | return C.DomainSuffix 16 | } 17 | 18 | func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { 19 | 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) ShouldResolveIP() bool { 35 | return false 36 | } 37 | 38 | func (ds *DomainSuffix) ShouldFindProcess() bool { 39 | return false 40 | } 41 | 42 | func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { 43 | return &DomainSuffix{ 44 | suffix: strings.ToLower(suffix), 45 | adapter: adapter, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rule/final.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | ) 6 | 7 | type Match struct { 8 | adapter string 9 | } 10 | 11 | func (f *Match) RuleType() C.RuleType { 12 | return C.MATCH 13 | } 14 | 15 | func (f *Match) Match(metadata *C.Metadata) bool { 16 | return true 17 | } 18 | 19 | func (f *Match) Adapter() string { 20 | return f.adapter 21 | } 22 | 23 | func (f *Match) Payload() string { 24 | return "" 25 | } 26 | 27 | func (f *Match) ShouldResolveIP() bool { 28 | return false 29 | } 30 | 31 | func (f *Match) ShouldFindProcess() bool { 32 | return false 33 | } 34 | 35 | func NewMatch(adapter string) *Match { 36 | return &Match{ 37 | adapter: adapter, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rule/geoip.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Dreamacro/clash/component/mmdb" 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type GEOIP struct { 11 | country string 12 | adapter string 13 | noResolveIP bool 14 | } 15 | 16 | func (g *GEOIP) RuleType() C.RuleType { 17 | return C.GEOIP 18 | } 19 | 20 | func (g *GEOIP) Match(metadata *C.Metadata) bool { 21 | ip := metadata.DstIP 22 | if ip == nil { 23 | return false 24 | } 25 | 26 | if strings.EqualFold(g.country, "LAN") { 27 | return ip.IsPrivate() 28 | } 29 | record, _ := mmdb.Instance().Country(ip) 30 | return strings.EqualFold(record.Country.IsoCode, g.country) 31 | } 32 | 33 | func (g *GEOIP) Adapter() string { 34 | return g.adapter 35 | } 36 | 37 | func (g *GEOIP) Payload() string { 38 | return g.country 39 | } 40 | 41 | func (g *GEOIP) ShouldResolveIP() bool { 42 | return !g.noResolveIP 43 | } 44 | 45 | func (g *GEOIP) ShouldFindProcess() bool { 46 | return false 47 | } 48 | 49 | func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { 50 | geoip := &GEOIP{ 51 | country: country, 52 | adapter: adapter, 53 | noResolveIP: noResolveIP, 54 | } 55 | 56 | return geoip 57 | } 58 | -------------------------------------------------------------------------------- /rule/ipcidr.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "net" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type IPCIDROption func(*IPCIDR) 10 | 11 | func WithIPCIDRSourceIP(b bool) IPCIDROption { 12 | return func(i *IPCIDR) { 13 | i.isSourceIP = b 14 | } 15 | } 16 | 17 | func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { 18 | return func(i *IPCIDR) { 19 | i.noResolveIP = noResolve 20 | } 21 | } 22 | 23 | type IPCIDR struct { 24 | ipnet *net.IPNet 25 | adapter string 26 | isSourceIP bool 27 | noResolveIP bool 28 | } 29 | 30 | func (i *IPCIDR) RuleType() C.RuleType { 31 | if i.isSourceIP { 32 | return C.SrcIPCIDR 33 | } 34 | return C.IPCIDR 35 | } 36 | 37 | func (i *IPCIDR) Match(metadata *C.Metadata) bool { 38 | ip := metadata.DstIP 39 | if i.isSourceIP { 40 | ip = metadata.SrcIP 41 | } 42 | return ip != nil && i.ipnet.Contains(ip) 43 | } 44 | 45 | func (i *IPCIDR) Adapter() string { 46 | return i.adapter 47 | } 48 | 49 | func (i *IPCIDR) Payload() string { 50 | return i.ipnet.String() 51 | } 52 | 53 | func (i *IPCIDR) ShouldResolveIP() bool { 54 | return !i.noResolveIP 55 | } 56 | 57 | func (i *IPCIDR) ShouldFindProcess() bool { 58 | return false 59 | } 60 | 61 | func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { 62 | _, ipnet, err := net.ParseCIDR(s) 63 | if err != nil { 64 | return nil, errPayload 65 | } 66 | 67 | ipcidr := &IPCIDR{ 68 | ipnet: ipnet, 69 | adapter: adapter, 70 | } 71 | 72 | for _, o := range opts { 73 | o(ipcidr) 74 | } 75 | 76 | return ipcidr, nil 77 | } 78 | -------------------------------------------------------------------------------- /rule/parser.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "fmt" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { 10 | var ( 11 | parseErr error 12 | parsed C.Rule 13 | ) 14 | 15 | switch tp { 16 | case "DOMAIN": 17 | parsed = NewDomain(payload, target) 18 | case "DOMAIN-SUFFIX": 19 | parsed = NewDomainSuffix(payload, target) 20 | case "DOMAIN-KEYWORD": 21 | parsed = NewDomainKeyword(payload, target) 22 | case "GEOIP": 23 | noResolve := HasNoResolve(params) 24 | parsed = NewGEOIP(payload, target, noResolve) 25 | case "IP-CIDR", "IP-CIDR6": 26 | noResolve := HasNoResolve(params) 27 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) 28 | case "SRC-IP-CIDR": 29 | parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) 30 | case "SRC-PORT": 31 | parsed, parseErr = NewPort(payload, target, true) 32 | case "DST-PORT": 33 | parsed, parseErr = NewPort(payload, target, false) 34 | case "PROCESS-NAME": 35 | parsed, parseErr = NewProcess(payload, target, true) 36 | case "PROCESS-PATH": 37 | parsed, parseErr = NewProcess(payload, target, false) 38 | case "MATCH": 39 | parsed = NewMatch(target) 40 | default: 41 | parseErr = fmt.Errorf("unsupported rule type %s", tp) 42 | } 43 | 44 | return parsed, parseErr 45 | } 46 | -------------------------------------------------------------------------------- /rule/port.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "strconv" 5 | 6 | C "github.com/Dreamacro/clash/constant" 7 | ) 8 | 9 | type Port struct { 10 | adapter string 11 | port string 12 | isSource bool 13 | } 14 | 15 | func (p *Port) RuleType() C.RuleType { 16 | if p.isSource { 17 | return C.SrcPort 18 | } 19 | return C.DstPort 20 | } 21 | 22 | func (p *Port) Match(metadata *C.Metadata) bool { 23 | if p.isSource { 24 | return metadata.SrcPort == p.port 25 | } 26 | return metadata.DstPort == p.port 27 | } 28 | 29 | func (p *Port) Adapter() string { 30 | return p.adapter 31 | } 32 | 33 | func (p *Port) Payload() string { 34 | return p.port 35 | } 36 | 37 | func (p *Port) ShouldResolveIP() bool { 38 | return false 39 | } 40 | 41 | func (p *Port) ShouldFindProcess() bool { 42 | return false 43 | } 44 | 45 | func NewPort(port string, adapter string, isSource bool) (*Port, error) { 46 | _, err := strconv.ParseUint(port, 10, 16) 47 | if err != nil { 48 | return nil, errPayload 49 | } 50 | return &Port{ 51 | adapter: adapter, 52 | port: port, 53 | isSource: isSource, 54 | }, nil 55 | } 56 | -------------------------------------------------------------------------------- /rule/process.go: -------------------------------------------------------------------------------- 1 | package rules 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | 7 | C "github.com/Dreamacro/clash/constant" 8 | ) 9 | 10 | type Process struct { 11 | adapter string 12 | process string 13 | nameOnly bool 14 | } 15 | 16 | func (ps *Process) RuleType() C.RuleType { 17 | return C.Process 18 | } 19 | 20 | func (ps *Process) Match(metadata *C.Metadata) bool { 21 | if ps.nameOnly { 22 | return strings.EqualFold(filepath.Base(metadata.ProcessPath), ps.process) 23 | } 24 | 25 | return strings.EqualFold(metadata.ProcessPath, ps.process) 26 | } 27 | 28 | func (ps *Process) Adapter() string { 29 | return ps.adapter 30 | } 31 | 32 | func (ps *Process) Payload() string { 33 | return ps.process 34 | } 35 | 36 | func (ps *Process) ShouldResolveIP() bool { 37 | return false 38 | } 39 | 40 | func (ps *Process) ShouldFindProcess() bool { 41 | return true 42 | } 43 | 44 | func NewProcess(process string, adapter string, nameOnly bool) (*Process, error) { 45 | return &Process{ 46 | adapter: adapter, 47 | process: process, 48 | nameOnly: nameOnly, 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /test/.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - govet 6 | - gci 7 | - staticcheck 8 | 9 | linters-settings: 10 | gci: 11 | sections: 12 | - standard 13 | - prefix(github.com/Dreamacro/clash) 14 | - default 15 | staticcheck: 16 | go: '1.18' 17 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | GOOS=darwin golangci-lint run ./... 3 | GOOS=linux golangci-lint run ./... 4 | 5 | test: 6 | go test -p 1 -v ./... 7 | 8 | benchmark: 9 | go test -benchmem -run=^$$ -bench . 10 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## Clash testing suit 2 | 3 | ### Protocol testing suit 4 | 5 | * TCP pingpong test 6 | * UDP pingpong test 7 | * TCP large data test 8 | * UDP large data test 9 | 10 | ### Protocols 11 | 12 | - [x] Shadowsocks 13 | - [x] Normal 14 | - [x] ObfsHTTP 15 | - [x] ObfsTLS 16 | - [x] ObfsV2rayPlugin 17 | - [x] Vmess 18 | - [x] Normal 19 | - [x] AEAD 20 | - [x] HTTP 21 | - [x] HTTP2 22 | - [x] TLS 23 | - [x] Websocket 24 | - [x] Websocket TLS 25 | - [x] gRPC 26 | - [x] Trojan 27 | - [x] Normal 28 | - [x] gRPC 29 | - [x] Snell 30 | - [x] Normal 31 | - [x] ObfsHTTP 32 | - [x] ObfsTLS 33 | 34 | ### Features 35 | 36 | - [ ] DNS 37 | - [x] DNS Server 38 | - [x] FakeIP 39 | - [x] Host 40 | 41 | ### Command 42 | 43 | Prerequisite 44 | 45 | * docker (support Linux and macOS) 46 | 47 | ``` 48 | $ make test 49 | ``` 50 | 51 | benchmark (Linux) 52 | 53 | > Cannot represent the throughput of the protocol on your machine 54 | > but you can compare the corresponding throughput of the protocol on clash 55 | > (change chunkSize to measure the maximum throughput of clash on your machine) 56 | 57 | ``` 58 | $ make benchmark 59 | ``` 60 | -------------------------------------------------------------------------------- /test/config/example.org-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5 3 | 5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo 4 | PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE 5 | sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R 6 | i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5 7 | LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge 8 | gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+ 9 | y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO 10 | jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z 11 | ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv 12 | H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG 13 | o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ 14 | CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49 15 | aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33 16 | bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7 17 | Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh 18 | ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO 19 | pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT 20 | 4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi 21 | GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA 22 | vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB 23 | fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z 24 | zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X 25 | DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28 26 | 9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP 27 | XCar+uxMBXI1zbXqd9QdEwy4Ig== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/config/example.org.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB 3 | hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh 4 | bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl 5 | cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz 6 | MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl 7 | bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy 8 | by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 9 | ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8 10 | 3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI 11 | YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b 12 | IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo 13 | UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2 14 | I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG 15 | A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln 16 | F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP 17 | TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa 18 | Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1 19 | Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz 20 | Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG 21 | WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6 22 | AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6 23 | gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS 24 | tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /test/config/snell-http.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | obfs = http 5 | -------------------------------------------------------------------------------- /test/config/snell-tls.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | obfs = tls 5 | -------------------------------------------------------------------------------- /test/config/snell.conf: -------------------------------------------------------------------------------- 1 | [snell-server] 2 | listen = 0.0.0.0:10002 3 | psk = password 4 | -------------------------------------------------------------------------------- /test/config/trojan-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "trojan", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "password": "example", 11 | "email": "grpc@example.com" 12 | } 13 | ] 14 | }, 15 | "streamSettings": { 16 | "network": "grpc", 17 | "security": "tls", 18 | "tlsSettings": { 19 | "certificates": [ 20 | { 21 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 22 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 23 | } 24 | ] 25 | }, 26 | "grpcSettings": { 27 | "serviceName": "example" 28 | } 29 | } 30 | } 31 | ], 32 | "outbounds": [ 33 | { 34 | "protocol": "freedom" 35 | } 36 | ], 37 | "log": { 38 | "loglevel": "debug" 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/trojan-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "disable_http_check": true, 6 | "password": [ 7 | "example" 8 | ], 9 | "websocket": { 10 | "enabled": true, 11 | "path": "/", 12 | "host": "example.org" 13 | }, 14 | "ssl": { 15 | "verify": true, 16 | "cert": "/fullchain.pem", 17 | "key": "/privkey.pem", 18 | "sni": "example.org" 19 | } 20 | } -------------------------------------------------------------------------------- /test/config/trojan.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 10002, 5 | "password": [ 6 | "password" 7 | ], 8 | "log_level": 1, 9 | "ssl": { 10 | "cert": "/path/to/certificate.crt", 11 | "key": "/path/to/private.key", 12 | "key_password": "", 13 | "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384", 14 | "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", 15 | "prefer_server_cipher": true, 16 | "alpn": [ 17 | "http/1.1" 18 | ], 19 | "alpn_port_override": { 20 | "h2": 81 21 | }, 22 | "reuse_session": true, 23 | "session_ticket": false, 24 | "session_timeout": 600, 25 | "plain_http_response": "", 26 | "curves": "", 27 | "dhparam": "" 28 | }, 29 | "tcp": { 30 | "prefer_ipv4": false, 31 | "no_delay": true, 32 | "keep_alive": true, 33 | "reuse_port": false, 34 | "fast_open": false, 35 | "fast_open_qlen": 20 36 | }, 37 | "mysql": { 38 | "enabled": false 39 | } 40 | } -------------------------------------------------------------------------------- /test/config/vmess-grpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "grpc", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "grpcSettings": { 26 | "serviceName": "example!" 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom" 34 | } 35 | ], 36 | "log": { 37 | "loglevel": "debug" 38 | } 39 | } -------------------------------------------------------------------------------- /test/config/vmess-http.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp", 16 | "tcpSettings": { 17 | "header": { 18 | "type": "http", 19 | "response": { 20 | "version": "1.1", 21 | "status": "200", 22 | "reason": "OK", 23 | "headers": { 24 | "Content-Type": [ 25 | "application/octet-stream", 26 | "video/mpeg", 27 | "application/x-msdownload", 28 | "text/html", 29 | "application/x-shockwave-flash" 30 | ], 31 | "Transfer-Encoding": [ 32 | "chunked" 33 | ], 34 | "Connection": [ 35 | "keep-alive" 36 | ], 37 | "Pragma": "no-cache" 38 | } 39 | } 40 | } 41 | }, 42 | "security": "none" 43 | } 44 | } 45 | ], 46 | "outbounds": [ 47 | { 48 | "protocol": "freedom" 49 | } 50 | ], 51 | "log": { 52 | "loglevel": "debug" 53 | } 54 | } -------------------------------------------------------------------------------- /test/config/vmess-http2.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "http", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | }, 25 | "httpSettings": { 26 | "host": [ 27 | "example.org" 28 | ], 29 | "path": "/test" 30 | } 31 | } 32 | } 33 | ], 34 | "outbounds": [ 35 | { 36 | "protocol": "freedom" 37 | } 38 | ], 39 | "log": { 40 | "loglevel": "debug" 41 | } 42 | } -------------------------------------------------------------------------------- /test/config/vmess-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ], 33 | "log": { 34 | "loglevel": "debug" 35 | } 36 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-0rtt.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "none", 17 | "wsSettings": { 18 | "maxEarlyData": 128, 19 | "earlyDataHeaderName": "Sec-WebSocket-Protocol" 20 | } 21 | } 22 | } 23 | ], 24 | "outbounds": [ 25 | { 26 | "protocol": "freedom" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/config/vmess-ws-tls.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "tls", 17 | "tlsSettings": { 18 | "certificates": [ 19 | { 20 | "certificateFile": "/etc/ssl/v2ray/fullchain.pem", 21 | "keyFile": "/etc/ssl/v2ray/privkey.pem" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | ], 28 | "outbounds": [ 29 | { 30 | "protocol": "freedom" 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /test/config/vmess-ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "ws", 16 | "security": "none" 17 | } 18 | } 19 | ], 20 | "outbounds": [ 21 | { 22 | "protocol": "freedom" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /test/config/vmess.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "port": 10002, 5 | "listen": "0.0.0.0", 6 | "protocol": "vmess", 7 | "settings": { 8 | "clients": [ 9 | { 10 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 11 | } 12 | ] 13 | }, 14 | "streamSettings": { 15 | "network": "tcp" 16 | } 17 | } 18 | ], 19 | "outbounds": [ 20 | { 21 | "protocol": "freedom" 22 | } 23 | ], 24 | "log": { 25 | "loglevel": "debug" 26 | } 27 | } -------------------------------------------------------------------------------- /test/docker_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/docker/docker/api/types" 8 | "github.com/docker/docker/api/types/container" 9 | "github.com/docker/docker/client" 10 | ) 11 | 12 | func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) { 13 | c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 14 | if err != nil { 15 | return "", err 16 | } 17 | defer c.Close() 18 | 19 | if !isDarwin { 20 | hostCfg.NetworkMode = "host" 21 | } 22 | 23 | container, err := c.ContainerCreate(context.Background(), cfg, hostCfg, nil, nil, name) 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | if err = c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil { 29 | return "", err 30 | } 31 | 32 | go func() { 33 | response, err := c.ContainerAttach(context.Background(), container.ID, types.ContainerAttachOptions{ 34 | Stdout: true, 35 | Stderr: true, 36 | Logs: true, 37 | }) 38 | if err != nil { 39 | return 40 | } 41 | response.Reader.WriteTo(os.Stderr) 42 | }() 43 | 44 | return container.ID, nil 45 | } 46 | 47 | func cleanContainer(id string) error { 48 | c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 49 | if err != nil { 50 | return err 51 | } 52 | defer c.Close() 53 | 54 | removeOpts := types.ContainerRemoveOptions{Force: true} 55 | return c.ContainerRemove(context.Background(), id, removeOpts) 56 | } 57 | -------------------------------------------------------------------------------- /test/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | ) 8 | 9 | func Listen(network, address string) (net.Listener, error) { 10 | lc := net.ListenConfig{} 11 | 12 | var lastErr error 13 | for i := 0; i < 5; i++ { 14 | l, err := lc.Listen(context.Background(), network, address) 15 | if err == nil { 16 | return l, nil 17 | } 18 | 19 | lastErr = err 20 | time.Sleep(time.Millisecond * 200) 21 | } 22 | return nil, lastErr 23 | } 24 | 25 | func ListenPacket(network, address string) (net.PacketConn, error) { 26 | var lastErr error 27 | for i := 0; i < 5; i++ { 28 | l, err := net.ListenPacket(network, address) 29 | if err == nil { 30 | return l, nil 31 | } 32 | 33 | lastErr = err 34 | time.Sleep(time.Millisecond * 200) 35 | } 36 | return nil, lastErr 37 | } 38 | 39 | func TCPing(addr string) bool { 40 | for i := 0; i < 10; i++ { 41 | conn, err := net.Dial("tcp", addr) 42 | if err == nil { 43 | conn.Close() 44 | return true 45 | } 46 | time.Sleep(time.Millisecond * 500) 47 | } 48 | 49 | return false 50 | } 51 | -------------------------------------------------------------------------------- /test/util_darwin_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "syscall" 8 | 9 | "golang.org/x/net/route" 10 | ) 11 | 12 | func defaultRouteIP() (net.IP, error) { 13 | idx, err := defaultRouteInterfaceIndex() 14 | if err != nil { 15 | return nil, err 16 | } 17 | iface, err := net.InterfaceByIndex(idx) 18 | if err != nil { 19 | return nil, err 20 | } 21 | addrs, err := iface.Addrs() 22 | if err != nil { 23 | return nil, err 24 | } 25 | for _, addr := range addrs { 26 | ip := addr.(*net.IPNet).IP 27 | if ip.To4() != nil { 28 | return ip, nil 29 | } 30 | } 31 | 32 | return nil, errors.New("no ipv4 addr") 33 | } 34 | 35 | func defaultRouteInterfaceIndex() (int, error) { 36 | rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) 37 | if err != nil { 38 | return 0, fmt.Errorf("route.FetchRIB: %w", err) 39 | } 40 | msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) 41 | if err != nil { 42 | return 0, fmt.Errorf("route.ParseRIB: %w", err) 43 | } 44 | for _, message := range msgs { 45 | routeMessage := message.(*route.RouteMessage) 46 | if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 { 47 | continue 48 | } 49 | 50 | addresses := routeMessage.Addrs 51 | 52 | destination, ok := addresses[0].(*route.Inet4Addr) 53 | if !ok { 54 | continue 55 | } 56 | 57 | if destination.IP != [4]byte{0, 0, 0, 0} { 58 | continue 59 | } 60 | 61 | switch addresses[1].(type) { 62 | case *route.Inet4Addr: 63 | return routeMessage.Index, nil 64 | default: 65 | continue 66 | } 67 | } 68 | 69 | return 0, fmt.Errorf("ambiguous gateway interfaces found") 70 | } 71 | -------------------------------------------------------------------------------- /test/util_other_test.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func defaultRouteIP() (net.IP, error) { 11 | return nil, errors.New("not supported") 12 | } 13 | -------------------------------------------------------------------------------- /transport/shadowsocks/README.md: -------------------------------------------------------------------------------- 1 | ## Embedded go-shadowsocks2 2 | 3 | from https://github.com/Dreamacro/go-shadowsocks2 4 | 5 | origin https://github.com/riobard/go-shadowsocks2 6 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowstream/packet.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "io" 7 | "net" 8 | 9 | "github.com/Dreamacro/clash/common/pool" 10 | ) 11 | 12 | // ErrShortPacket means the packet is too short to be a valid encrypted packet. 13 | var ErrShortPacket = errors.New("short packet") 14 | 15 | // Pack encrypts plaintext using stream cipher s and a random IV. 16 | // Returns a slice of dst containing random IV and ciphertext. 17 | // Ensure len(dst) >= s.IVSize() + len(plaintext). 18 | func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { 19 | if len(dst) < s.IVSize()+len(plaintext) { 20 | return nil, io.ErrShortBuffer 21 | } 22 | iv := dst[:s.IVSize()] 23 | _, err := rand.Read(iv) 24 | if err != nil { 25 | return nil, err 26 | } 27 | s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext) 28 | return dst[:len(iv)+len(plaintext)], nil 29 | } 30 | 31 | // Unpack decrypts pkt using stream cipher s. 32 | // Returns a slice of dst containing decrypted plaintext. 33 | func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { 34 | if len(pkt) < s.IVSize() { 35 | return nil, ErrShortPacket 36 | } 37 | if len(dst) < len(pkt)-s.IVSize() { 38 | return nil, io.ErrShortBuffer 39 | } 40 | iv := pkt[:s.IVSize()] 41 | s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):]) 42 | return dst[:len(pkt)-len(iv)], nil 43 | } 44 | 45 | type PacketConn struct { 46 | net.PacketConn 47 | Cipher 48 | } 49 | 50 | // NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption. 51 | func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn { 52 | return &PacketConn{PacketConn: c, Cipher: ciph} 53 | } 54 | 55 | const maxPacketSize = 64 * 1024 56 | 57 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 58 | buf := pool.Get(maxPacketSize) 59 | defer pool.Put(buf) 60 | buf, err := Pack(buf, b, c.Cipher) 61 | if err != nil { 62 | return 0, err 63 | } 64 | _, err = c.PacketConn.WriteTo(buf, addr) 65 | return len(b), err 66 | } 67 | 68 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 69 | n, addr, err := c.PacketConn.ReadFrom(b) 70 | if err != nil { 71 | return n, addr, err 72 | } 73 | bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher) 74 | if err != nil { 75 | return n, addr, err 76 | } 77 | copy(b, bb) 78 | return len(bb), addr, err 79 | } 80 | -------------------------------------------------------------------------------- /transport/simple-obfs/http.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net" 10 | "net/http" 11 | 12 | "github.com/Dreamacro/clash/common/pool" 13 | ) 14 | 15 | // HTTPObfs is shadowsocks http simple-obfs implementation 16 | type HTTPObfs struct { 17 | net.Conn 18 | host string 19 | port string 20 | buf []byte 21 | offset int 22 | firstRequest bool 23 | firstResponse bool 24 | } 25 | 26 | func (ho *HTTPObfs) Read(b []byte) (int, error) { 27 | if ho.buf != nil { 28 | n := copy(b, ho.buf[ho.offset:]) 29 | ho.offset += n 30 | if ho.offset == len(ho.buf) { 31 | pool.Put(ho.buf) 32 | ho.buf = nil 33 | } 34 | return n, nil 35 | } 36 | 37 | if ho.firstResponse { 38 | buf := pool.Get(pool.RelayBufferSize) 39 | n, err := ho.Conn.Read(buf) 40 | if err != nil { 41 | pool.Put(buf) 42 | return 0, err 43 | } 44 | idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) 45 | if idx == -1 { 46 | pool.Put(buf) 47 | return 0, io.EOF 48 | } 49 | ho.firstResponse = false 50 | length := n - (idx + 4) 51 | n = copy(b, buf[idx+4:n]) 52 | if length > n { 53 | ho.buf = buf[:idx+4+length] 54 | ho.offset = idx + 4 + n 55 | } else { 56 | pool.Put(buf) 57 | } 58 | return n, nil 59 | } 60 | return ho.Conn.Read(b) 61 | } 62 | 63 | func (ho *HTTPObfs) Write(b []byte) (int, error) { 64 | if ho.firstRequest { 65 | randBytes := make([]byte, 16) 66 | rand.Read(randBytes) 67 | req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:])) 68 | req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2)) 69 | req.Header.Set("Upgrade", "websocket") 70 | req.Header.Set("Connection", "Upgrade") 71 | req.Host = ho.host 72 | if ho.port != "80" { 73 | req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) 74 | } 75 | req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) 76 | req.ContentLength = int64(len(b)) 77 | err := req.Write(ho.Conn) 78 | ho.firstRequest = false 79 | return len(b), err 80 | } 81 | 82 | return ho.Conn.Write(b) 83 | } 84 | 85 | // NewHTTPObfs return a HTTPObfs 86 | func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { 87 | return &HTTPObfs{ 88 | Conn: conn, 89 | firstRequest: true, 90 | firstResponse: true, 91 | host: host, 92 | port: port, 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /transport/snell/cipher.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | 7 | "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" 8 | "golang.org/x/crypto/argon2" 9 | "golang.org/x/crypto/chacha20poly1305" 10 | ) 11 | 12 | type snellCipher struct { 13 | psk []byte 14 | keySize int 15 | makeAEAD func(key []byte) (cipher.AEAD, error) 16 | } 17 | 18 | func (sc *snellCipher) KeySize() int { return sc.keySize } 19 | func (sc *snellCipher) SaltSize() int { return 16 } 20 | func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { 21 | return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) 22 | } 23 | 24 | func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { 25 | return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) 26 | } 27 | 28 | func snellKDF(psk, salt []byte, keySize int) []byte { 29 | // snell use a special kdf function 30 | return argon2.IDKey(psk, salt, 3, 8, 1, 32)[:keySize] 31 | } 32 | 33 | func aesGCM(key []byte) (cipher.AEAD, error) { 34 | blk, err := aes.NewCipher(key) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return cipher.NewGCM(blk) 39 | } 40 | 41 | func NewAES128GCM(psk []byte) shadowaead.Cipher { 42 | return &snellCipher{ 43 | psk: psk, 44 | keySize: 16, 45 | makeAEAD: aesGCM, 46 | } 47 | } 48 | 49 | func NewChacha20Poly1305(psk []byte) shadowaead.Cipher { 50 | return &snellCipher{ 51 | psk: psk, 52 | keySize: 32, 53 | makeAEAD: chacha20poly1305.New, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /transport/snell/pool.go: -------------------------------------------------------------------------------- 1 | package snell 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/Dreamacro/clash/component/pool" 9 | "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" 10 | ) 11 | 12 | type Pool struct { 13 | pool *pool.Pool 14 | } 15 | 16 | func (p *Pool) Get() (net.Conn, error) { 17 | return p.GetContext(context.Background()) 18 | } 19 | 20 | func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) { 21 | elm, err := p.pool.GetContext(ctx) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &PoolConn{elm.(*Snell), p}, nil 27 | } 28 | 29 | func (p *Pool) Put(conn net.Conn) { 30 | if err := HalfClose(conn); err != nil { 31 | conn.Close() 32 | return 33 | } 34 | 35 | p.pool.Put(conn) 36 | } 37 | 38 | type PoolConn struct { 39 | *Snell 40 | pool *Pool 41 | } 42 | 43 | func (pc *PoolConn) Read(b []byte) (int, error) { 44 | // save old status of reply (it mutable by Read) 45 | reply := pc.Snell.reply 46 | 47 | n, err := pc.Snell.Read(b) 48 | if err == shadowaead.ErrZeroChunk { 49 | // if reply is false, it should be client halfclose. 50 | // ignore error and read data again. 51 | if !reply { 52 | pc.Snell.reply = false 53 | return pc.Snell.Read(b) 54 | } 55 | } 56 | return n, err 57 | } 58 | 59 | func (pc *PoolConn) Write(b []byte) (int, error) { 60 | return pc.Snell.Write(b) 61 | } 62 | 63 | func (pc *PoolConn) Close() error { 64 | // clash use SetReadDeadline to break bidirectional copy between client and server. 65 | // reset it before reuse connection to avoid io timeout error. 66 | pc.Snell.Conn.SetReadDeadline(time.Time{}) 67 | pc.pool.Put(pc.Snell) 68 | return nil 69 | } 70 | 71 | func NewPool(factory func(context.Context) (*Snell, error)) *Pool { 72 | p := pool.New( 73 | func(ctx context.Context) (any, error) { 74 | return factory(ctx) 75 | }, 76 | pool.WithAge(15000), 77 | pool.WithSize(10), 78 | pool.WithEvict(func(item any) { 79 | item.(*Snell).Close() 80 | }), 81 | ) 82 | 83 | return &Pool{p} 84 | } 85 | -------------------------------------------------------------------------------- /transport/ssr/obfs/base.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | type Base struct { 4 | Host string 5 | Port int 6 | Key []byte 7 | IVSize int 8 | Param string 9 | } 10 | -------------------------------------------------------------------------------- /transport/ssr/obfs/http_post.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | func init() { 4 | register("http_post", newHTTPPost, 0) 5 | } 6 | 7 | func newHTTPPost(b *Base) Obfs { 8 | return &httpObfs{Base: b, post: true} 9 | } 10 | -------------------------------------------------------------------------------- /transport/ssr/obfs/obfs.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | var ( 10 | errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") 11 | errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") 12 | errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") 13 | ) 14 | 15 | type authData struct { 16 | clientID [32]byte 17 | } 18 | 19 | type Obfs interface { 20 | StreamConn(net.Conn) net.Conn 21 | } 22 | 23 | type obfsCreator func(b *Base) Obfs 24 | 25 | var obfsList = make(map[string]struct { 26 | overhead int 27 | new obfsCreator 28 | }) 29 | 30 | func register(name string, c obfsCreator, o int) { 31 | obfsList[name] = struct { 32 | overhead int 33 | new obfsCreator 34 | }{overhead: o, new: c} 35 | } 36 | 37 | func PickObfs(name string, b *Base) (Obfs, int, error) { 38 | if choice, ok := obfsList[name]; ok { 39 | return choice.new(b), choice.overhead, nil 40 | } 41 | return nil, 0, fmt.Errorf("Obfs %s not supported", name) 42 | } 43 | -------------------------------------------------------------------------------- /transport/ssr/obfs/plain.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import "net" 4 | 5 | type plain struct{} 6 | 7 | func init() { 8 | register("plain", newPlain, 0) 9 | } 10 | 11 | func newPlain(b *Base) Obfs { 12 | return &plain{} 13 | } 14 | 15 | func (p *plain) StreamConn(c net.Conn) net.Conn { return c } 16 | -------------------------------------------------------------------------------- /transport/ssr/obfs/random_head.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "encoding/binary" 5 | "hash/crc32" 6 | "math/rand" 7 | "net" 8 | 9 | "github.com/Dreamacro/clash/common/pool" 10 | ) 11 | 12 | func init() { 13 | register("random_head", newRandomHead, 0) 14 | } 15 | 16 | type randomHead struct { 17 | *Base 18 | } 19 | 20 | func newRandomHead(b *Base) Obfs { 21 | return &randomHead{Base: b} 22 | } 23 | 24 | type randomHeadConn struct { 25 | net.Conn 26 | *randomHead 27 | hasSentHeader bool 28 | rawTransSent bool 29 | rawTransRecv bool 30 | buf []byte 31 | } 32 | 33 | func (r *randomHead) StreamConn(c net.Conn) net.Conn { 34 | return &randomHeadConn{Conn: c, randomHead: r} 35 | } 36 | 37 | func (c *randomHeadConn) Read(b []byte) (int, error) { 38 | if c.rawTransRecv { 39 | return c.Conn.Read(b) 40 | } 41 | buf := pool.Get(pool.RelayBufferSize) 42 | defer pool.Put(buf) 43 | c.Conn.Read(buf) 44 | c.rawTransRecv = true 45 | c.Write(nil) 46 | return 0, nil 47 | } 48 | 49 | func (c *randomHeadConn) Write(b []byte) (int, error) { 50 | if c.rawTransSent { 51 | return c.Conn.Write(b) 52 | } 53 | c.buf = append(c.buf, b...) 54 | if !c.hasSentHeader { 55 | c.hasSentHeader = true 56 | dataLength := rand.Intn(96) + 4 57 | buf := pool.Get(dataLength + 4) 58 | defer pool.Put(buf) 59 | rand.Read(buf[:dataLength]) 60 | binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength])) 61 | _, err := c.Conn.Write(buf) 62 | return len(b), err 63 | } 64 | if c.rawTransRecv { 65 | _, err := c.Conn.Write(c.buf) 66 | c.buf = nil 67 | c.rawTransSent = true 68 | return len(b), err 69 | } 70 | return len(b), nil 71 | } 72 | -------------------------------------------------------------------------------- /transport/ssr/protocol/auth_aes128_md5.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "github.com/Dreamacro/clash/transport/ssr/tools" 4 | 5 | func init() { 6 | register("auth_aes128_md5", newAuthAES128MD5, 9) 7 | } 8 | 9 | func newAuthAES128MD5(b *Base) Protocol { 10 | a := &authAES128{ 11 | Base: b, 12 | authData: &authData{}, 13 | authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, 14 | userData: &userData{}, 15 | } 16 | a.initUserData() 17 | return a 18 | } 19 | -------------------------------------------------------------------------------- /transport/ssr/protocol/base.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "encoding/base64" 8 | "encoding/binary" 9 | "math/rand" 10 | "sync" 11 | "time" 12 | 13 | "github.com/Dreamacro/clash/common/pool" 14 | "github.com/Dreamacro/clash/log" 15 | "github.com/Dreamacro/clash/transport/shadowsocks/core" 16 | ) 17 | 18 | type Base struct { 19 | Key []byte 20 | Overhead int 21 | Param string 22 | } 23 | 24 | type userData struct { 25 | userKey []byte 26 | userID [4]byte 27 | } 28 | 29 | type authData struct { 30 | clientID [4]byte 31 | connectionID uint32 32 | mutex sync.Mutex 33 | } 34 | 35 | func (a *authData) next() *authData { 36 | r := &authData{} 37 | a.mutex.Lock() 38 | defer a.mutex.Unlock() 39 | if a.connectionID > 0xff000000 || a.connectionID == 0 { 40 | rand.Read(a.clientID[:]) 41 | a.connectionID = rand.Uint32() & 0xffffff 42 | } 43 | a.connectionID++ 44 | copy(r.clientID[:], a.clientID[:]) 45 | r.connectionID = a.connectionID 46 | return r 47 | } 48 | 49 | func (a *authData) putAuthData(buf *bytes.Buffer) { 50 | binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix())) 51 | buf.Write(a.clientID[:]) 52 | binary.Write(buf, binary.LittleEndian, a.connectionID) 53 | } 54 | 55 | func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error { 56 | encrypt := pool.Get(16) 57 | defer pool.Put(encrypt) 58 | binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix())) 59 | copy(encrypt[4:], a.clientID[:]) 60 | binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) 61 | binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0])) 62 | binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1])) 63 | 64 | cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16) 65 | block, err := aes.NewCipher(cipherKey) 66 | if err != nil { 67 | log.Warnln("New cipher error: %s", err.Error()) 68 | return err 69 | } 70 | iv := bytes.Repeat([]byte{0}, 16) 71 | cbcCipher := cipher.NewCBCEncrypter(block, iv) 72 | 73 | cbcCipher.CryptBlocks(encrypt, encrypt) 74 | 75 | b.Write(encrypt) 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /transport/ssr/protocol/origin.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | ) 7 | 8 | type origin struct{} 9 | 10 | func init() { register("origin", newOrigin, 0) } 11 | 12 | func newOrigin(b *Base) Protocol { return &origin{} } 13 | 14 | func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } 15 | 16 | func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c } 17 | 18 | func (o *origin) Decode(dst, src *bytes.Buffer) error { 19 | dst.ReadFrom(src) 20 | return nil 21 | } 22 | 23 | func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { 24 | buf.Write(b) 25 | return nil 26 | } 27 | 28 | func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } 29 | 30 | func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { 31 | buf.Write(b) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /transport/ssr/protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | type PacketConn struct { 10 | net.PacketConn 11 | Protocol 12 | } 13 | 14 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 15 | buf := pool.GetBuffer() 16 | defer pool.PutBuffer(buf) 17 | err := c.EncodePacket(buf, b) 18 | if err != nil { 19 | return 0, err 20 | } 21 | _, err = c.PacketConn.WriteTo(buf.Bytes(), addr) 22 | return len(b), err 23 | } 24 | 25 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 26 | n, addr, err := c.PacketConn.ReadFrom(b) 27 | if err != nil { 28 | return n, addr, err 29 | } 30 | decoded, err := c.DecodePacket(b[:n]) 31 | if err != nil { 32 | return n, addr, err 33 | } 34 | copy(b, decoded) 35 | return len(decoded), addr, nil 36 | } 37 | -------------------------------------------------------------------------------- /transport/ssr/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | ) 10 | 11 | var ( 12 | errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32") 13 | errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length") 14 | errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32") 15 | errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac") 16 | errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length") 17 | errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum") 18 | errAuthChainLengthError = errors.New("auth_chain decode data wrong length") 19 | errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum") 20 | ) 21 | 22 | type Protocol interface { 23 | StreamConn(net.Conn, []byte) net.Conn 24 | PacketConn(net.PacketConn) net.PacketConn 25 | Decode(dst, src *bytes.Buffer) error 26 | Encode(buf *bytes.Buffer, b []byte) error 27 | DecodePacket([]byte) ([]byte, error) 28 | EncodePacket(buf *bytes.Buffer, b []byte) error 29 | } 30 | 31 | type protocolCreator func(b *Base) Protocol 32 | 33 | var protocolList = make(map[string]struct { 34 | overhead int 35 | new protocolCreator 36 | }) 37 | 38 | func register(name string, c protocolCreator, o int) { 39 | protocolList[name] = struct { 40 | overhead int 41 | new protocolCreator 42 | }{overhead: o, new: c} 43 | } 44 | 45 | func PickProtocol(name string, b *Base) (Protocol, error) { 46 | if choice, ok := protocolList[name]; ok { 47 | b.Overhead += choice.overhead 48 | return choice.new(b), nil 49 | } 50 | return nil, fmt.Errorf("protocol %s not supported", name) 51 | } 52 | 53 | func getHeadSize(b []byte, defaultValue int) int { 54 | if len(b) < 2 { 55 | return defaultValue 56 | } 57 | headType := b[0] & 7 58 | switch headType { 59 | case 1: 60 | return 7 61 | case 4: 62 | return 19 63 | case 3: 64 | return 4 + int(b[1]) 65 | } 66 | return defaultValue 67 | } 68 | 69 | func getDataLength(b []byte) int { 70 | bLength := len(b) 71 | dataLength := getHeadSize(b, 30) + rand.Intn(32) 72 | if bLength < dataLength { 73 | return bLength 74 | } 75 | return dataLength 76 | } 77 | -------------------------------------------------------------------------------- /transport/ssr/protocol/stream.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | "github.com/Dreamacro/clash/common/pool" 8 | ) 9 | 10 | type Conn struct { 11 | net.Conn 12 | Protocol 13 | decoded bytes.Buffer 14 | underDecoded bytes.Buffer 15 | } 16 | 17 | func (c *Conn) Read(b []byte) (int, error) { 18 | if c.decoded.Len() > 0 { 19 | return c.decoded.Read(b) 20 | } 21 | 22 | buf := pool.Get(pool.RelayBufferSize) 23 | defer pool.Put(buf) 24 | n, err := c.Conn.Read(buf) 25 | if err != nil { 26 | return 0, err 27 | } 28 | c.underDecoded.Write(buf[:n]) 29 | err = c.Decode(&c.decoded, &c.underDecoded) 30 | if err != nil { 31 | return 0, err 32 | } 33 | n, _ = c.decoded.Read(b) 34 | return n, nil 35 | } 36 | 37 | func (c *Conn) Write(b []byte) (int, error) { 38 | bLength := len(b) 39 | buf := pool.GetBuffer() 40 | defer pool.PutBuffer(buf) 41 | err := c.Encode(buf, b) 42 | if err != nil { 43 | return 0, err 44 | } 45 | _, err = c.Conn.Write(buf.Bytes()) 46 | if err != nil { 47 | return 0, err 48 | } 49 | return bLength, nil 50 | } 51 | -------------------------------------------------------------------------------- /transport/ssr/tools/bufPool.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io" 7 | ) 8 | 9 | func AppendRandBytes(b *bytes.Buffer, length int) { 10 | b.ReadFrom(io.LimitReader(rand.Reader, int64(length))) 11 | } 12 | -------------------------------------------------------------------------------- /transport/ssr/tools/crypto.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | ) 8 | 9 | const HmacSHA1Len = 10 10 | 11 | func HmacMD5(key, data []byte) []byte { 12 | hmacMD5 := hmac.New(md5.New, key) 13 | hmacMD5.Write(data) 14 | return hmacMD5.Sum(nil) 15 | } 16 | 17 | func HmacSHA1(key, data []byte) []byte { 18 | hmacSHA1 := hmac.New(sha1.New, key) 19 | hmacSHA1.Write(data) 20 | return hmacSHA1.Sum(nil) 21 | } 22 | 23 | func MD5Sum(b []byte) []byte { 24 | h := md5.New() 25 | h.Write(b) 26 | return h.Sum(nil) 27 | } 28 | 29 | func SHA1Sum(b []byte) []byte { 30 | h := sha1.New() 31 | h.Write(b) 32 | return h.Sum(nil) 33 | } 34 | -------------------------------------------------------------------------------- /transport/ssr/tools/random.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/Dreamacro/clash/common/pool" 7 | ) 8 | 9 | // XorShift128Plus - a pseudorandom number generator 10 | type XorShift128Plus struct { 11 | s [2]uint64 12 | } 13 | 14 | func (r *XorShift128Plus) Next() uint64 { 15 | x := r.s[0] 16 | y := r.s[1] 17 | r.s[0] = y 18 | x ^= x << 23 19 | x ^= y ^ (x >> 17) ^ (y >> 26) 20 | r.s[1] = x 21 | return x + y 22 | } 23 | 24 | func (r *XorShift128Plus) InitFromBin(bin []byte) { 25 | var full []byte 26 | if len(bin) < 16 { 27 | full := pool.Get(16)[:0] 28 | defer pool.Put(full) 29 | full = append(full, bin...) 30 | for len(full) < 16 { 31 | full = append(full, 0) 32 | } 33 | } else { 34 | full = bin 35 | } 36 | r.s[0] = binary.LittleEndian.Uint64(full[:8]) 37 | r.s[1] = binary.LittleEndian.Uint64(full[8:16]) 38 | } 39 | 40 | func (r *XorShift128Plus) InitFromBinAndLength(bin []byte, length int) { 41 | var full []byte 42 | if len(bin) < 16 { 43 | full := pool.Get(16)[:0] 44 | defer pool.Put(full) 45 | full = append(full, bin...) 46 | for len(full) < 16 { 47 | full = append(full, 0) 48 | } 49 | } 50 | full = bin 51 | binary.LittleEndian.PutUint16(full, uint16(length)) 52 | r.s[0] = binary.LittleEndian.Uint64(full[:8]) 53 | r.s[1] = binary.LittleEndian.Uint64(full[8:16]) 54 | for i := 0; i < 4; i++ { 55 | r.Next() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /transport/v2ray-plugin/websocket.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/Dreamacro/clash/transport/vmess" 9 | ) 10 | 11 | // Option is options of websocket obfs 12 | type Option struct { 13 | Host string 14 | Port string 15 | Path string 16 | Headers map[string]string 17 | TLS bool 18 | SkipCertVerify bool 19 | Mux bool 20 | } 21 | 22 | // NewV2rayObfs return a HTTPObfs 23 | func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { 24 | header := http.Header{} 25 | for k, v := range option.Headers { 26 | header.Add(k, v) 27 | } 28 | 29 | config := &vmess.WebsocketConfig{ 30 | Host: option.Host, 31 | Port: option.Port, 32 | Path: option.Path, 33 | Headers: header, 34 | } 35 | 36 | if option.TLS { 37 | config.TLS = true 38 | config.TLSConfig = &tls.Config{ 39 | ServerName: option.Host, 40 | InsecureSkipVerify: option.SkipCertVerify, 41 | NextProtos: []string{"http/1.1"}, 42 | } 43 | if host := config.Headers.Get("Host"); host != "" { 44 | config.TLSConfig.ServerName = host 45 | } 46 | } 47 | 48 | var err error 49 | conn, err = vmess.StreamWebsocketConn(conn, config) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | if option.Mux { 55 | conn = NewMux(conn, MuxOption{ 56 | ID: [2]byte{0, 0}, 57 | Host: "127.0.0.1", 58 | Port: 0, 59 | }) 60 | } 61 | return conn, nil 62 | } 63 | -------------------------------------------------------------------------------- /transport/vmess/chunk.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | 8 | "github.com/Dreamacro/clash/common/pool" 9 | ) 10 | 11 | const ( 12 | lenSize = 2 13 | chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 14 | maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() 15 | ) 16 | 17 | type chunkReader struct { 18 | io.Reader 19 | buf []byte 20 | sizeBuf []byte 21 | offset int 22 | } 23 | 24 | func newChunkReader(reader io.Reader) *chunkReader { 25 | return &chunkReader{Reader: reader, sizeBuf: make([]byte, lenSize)} 26 | } 27 | 28 | func newChunkWriter(writer io.WriteCloser) *chunkWriter { 29 | return &chunkWriter{Writer: writer} 30 | } 31 | 32 | func (cr *chunkReader) Read(b []byte) (int, error) { 33 | if cr.buf != nil { 34 | n := copy(b, cr.buf[cr.offset:]) 35 | cr.offset += n 36 | if cr.offset == len(cr.buf) { 37 | pool.Put(cr.buf) 38 | cr.buf = nil 39 | } 40 | return n, nil 41 | } 42 | 43 | _, err := io.ReadFull(cr.Reader, cr.sizeBuf) 44 | if err != nil { 45 | return 0, err 46 | } 47 | 48 | size := int(binary.BigEndian.Uint16(cr.sizeBuf)) 49 | if size > maxSize { 50 | return 0, errors.New("buffer is larger than standard") 51 | } 52 | 53 | if len(b) >= size { 54 | _, err := io.ReadFull(cr.Reader, b[:size]) 55 | if err != nil { 56 | return 0, err 57 | } 58 | 59 | return size, nil 60 | } 61 | 62 | buf := pool.Get(size) 63 | _, err = io.ReadFull(cr.Reader, buf) 64 | if err != nil { 65 | pool.Put(buf) 66 | return 0, err 67 | } 68 | n := copy(b, buf) 69 | cr.offset = n 70 | cr.buf = buf 71 | return n, nil 72 | } 73 | 74 | type chunkWriter struct { 75 | io.Writer 76 | } 77 | 78 | func (cw *chunkWriter) Write(b []byte) (n int, err error) { 79 | buf := pool.Get(pool.RelayBufferSize) 80 | defer pool.Put(buf) 81 | length := len(b) 82 | for { 83 | if length == 0 { 84 | break 85 | } 86 | readLen := chunkSize 87 | if length < chunkSize { 88 | readLen = length 89 | } 90 | payloadBuf := buf[lenSize : lenSize+chunkSize] 91 | copy(payloadBuf, b[n:n+readLen]) 92 | 93 | binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen)) 94 | _, err = cw.Writer.Write(buf[:lenSize+readLen]) 95 | if err != nil { 96 | break 97 | } 98 | n += readLen 99 | length -= readLen 100 | } 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /transport/vmess/h2.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "io" 5 | "math/rand" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | 10 | "golang.org/x/net/http2" 11 | ) 12 | 13 | type h2Conn struct { 14 | net.Conn 15 | *http2.ClientConn 16 | pwriter *io.PipeWriter 17 | res *http.Response 18 | cfg *H2Config 19 | } 20 | 21 | type H2Config struct { 22 | Hosts []string 23 | Path string 24 | } 25 | 26 | func (hc *h2Conn) establishConn() error { 27 | preader, pwriter := io.Pipe() 28 | 29 | host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))] 30 | path := hc.cfg.Path 31 | // TODO: connect use VMess Host instead of H2 Host 32 | req := http.Request{ 33 | Method: "PUT", 34 | Host: host, 35 | URL: &url.URL{ 36 | Scheme: "https", 37 | Host: host, 38 | Path: path, 39 | }, 40 | Proto: "HTTP/2", 41 | ProtoMajor: 2, 42 | ProtoMinor: 0, 43 | Body: preader, 44 | Header: map[string][]string{ 45 | "Accept-Encoding": {"identity"}, 46 | }, 47 | } 48 | 49 | // it will be close at : `func (hc *h2Conn) Close() error` 50 | res, err := hc.ClientConn.RoundTrip(&req) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | hc.pwriter = pwriter 56 | hc.res = res 57 | 58 | return nil 59 | } 60 | 61 | // Read implements net.Conn.Read() 62 | func (hc *h2Conn) Read(b []byte) (int, error) { 63 | if hc.res != nil && !hc.res.Close { 64 | n, err := hc.res.Body.Read(b) 65 | return n, err 66 | } 67 | 68 | if err := hc.establishConn(); err != nil { 69 | return 0, err 70 | } 71 | return hc.res.Body.Read(b) 72 | } 73 | 74 | // Write implements io.Writer. 75 | func (hc *h2Conn) Write(b []byte) (int, error) { 76 | if hc.pwriter != nil { 77 | return hc.pwriter.Write(b) 78 | } 79 | 80 | if err := hc.establishConn(); err != nil { 81 | return 0, err 82 | } 83 | return hc.pwriter.Write(b) 84 | } 85 | 86 | func (hc *h2Conn) Close() error { 87 | if err := hc.pwriter.Close(); err != nil { 88 | return err 89 | } 90 | if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil { 91 | return err 92 | } 93 | return hc.Conn.Close() 94 | } 95 | 96 | func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) { 97 | transport := &http2.Transport{} 98 | 99 | cconn, err := transport.NewClientConn(conn) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return &h2Conn{ 105 | Conn: conn, 106 | ClientConn: cconn, 107 | cfg: cfg, 108 | }, nil 109 | } 110 | -------------------------------------------------------------------------------- /transport/vmess/http.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | "net/http" 10 | "net/textproto" 11 | ) 12 | 13 | type httpConn struct { 14 | net.Conn 15 | cfg *HTTPConfig 16 | reader *bufio.Reader 17 | whandshake bool 18 | } 19 | 20 | type HTTPConfig struct { 21 | Method string 22 | Host string 23 | Path []string 24 | Headers map[string][]string 25 | } 26 | 27 | // Read implements net.Conn.Read() 28 | func (hc *httpConn) Read(b []byte) (int, error) { 29 | if hc.reader != nil { 30 | n, err := hc.reader.Read(b) 31 | return n, err 32 | } 33 | 34 | reader := textproto.NewConn(hc.Conn) 35 | // First line: GET /index.html HTTP/1.0 36 | if _, err := reader.ReadLine(); err != nil { 37 | return 0, err 38 | } 39 | 40 | if _, err := reader.ReadMIMEHeader(); err != nil { 41 | return 0, err 42 | } 43 | 44 | hc.reader = reader.R 45 | return reader.R.Read(b) 46 | } 47 | 48 | // Write implements io.Writer. 49 | func (hc *httpConn) Write(b []byte) (int, error) { 50 | if hc.whandshake { 51 | return hc.Conn.Write(b) 52 | } 53 | 54 | path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] 55 | host := hc.cfg.Host 56 | if header := hc.cfg.Headers["Host"]; len(header) != 0 { 57 | host = header[rand.Intn(len(header))] 58 | } 59 | 60 | u := fmt.Sprintf("http://%s%s", host, path) 61 | req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b)) 62 | for key, list := range hc.cfg.Headers { 63 | req.Header.Set(key, list[rand.Intn(len(list))]) 64 | } 65 | req.ContentLength = int64(len(b)) 66 | if err := req.Write(hc.Conn); err != nil { 67 | return 0, err 68 | } 69 | hc.whandshake = true 70 | return len(b), nil 71 | } 72 | 73 | func (hc *httpConn) Close() error { 74 | return hc.Conn.Close() 75 | } 76 | 77 | func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn { 78 | return &httpConn{ 79 | Conn: conn, 80 | cfg: cfg, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /transport/vmess/tls.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "net" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | type TLSConfig struct { 12 | Host string 13 | SkipCertVerify bool 14 | NextProtos []string 15 | } 16 | 17 | func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { 18 | tlsConfig := &tls.Config{ 19 | ServerName: cfg.Host, 20 | InsecureSkipVerify: cfg.SkipCertVerify, 21 | NextProtos: cfg.NextProtos, 22 | } 23 | 24 | tlsConn := tls.Client(conn, tlsConfig) 25 | 26 | // fix tls handshake not timeout 27 | ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) 28 | defer cancel() 29 | err := tlsConn.HandshakeContext(ctx) 30 | return tlsConn, err 31 | } 32 | -------------------------------------------------------------------------------- /transport/vmess/user.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | 7 | "github.com/gofrs/uuid" 8 | ) 9 | 10 | // ID cmdKey length 11 | const ( 12 | IDBytesLen = 16 13 | ) 14 | 15 | // The ID of en entity, in the form of a UUID. 16 | type ID struct { 17 | UUID *uuid.UUID 18 | CmdKey []byte 19 | } 20 | 21 | // newID returns an ID with given UUID. 22 | func newID(uuid *uuid.UUID) *ID { 23 | id := &ID{UUID: uuid, CmdKey: make([]byte, IDBytesLen)} 24 | md5hash := md5.New() 25 | md5hash.Write(uuid.Bytes()) 26 | md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) 27 | md5hash.Sum(id.CmdKey[:0]) 28 | return id 29 | } 30 | 31 | func nextID(u *uuid.UUID) *uuid.UUID { 32 | md5hash := md5.New() 33 | md5hash.Write(u.Bytes()) 34 | md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) 35 | var newid uuid.UUID 36 | for { 37 | md5hash.Sum(newid[:0]) 38 | if !bytes.Equal(newid.Bytes(), u.Bytes()) { 39 | return &newid 40 | } 41 | md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) 42 | } 43 | } 44 | 45 | func newAlterIDs(primary *ID, alterIDCount uint16) []*ID { 46 | alterIDs := make([]*ID, alterIDCount) 47 | prevID := primary.UUID 48 | for idx := range alterIDs { 49 | newid := nextID(prevID) 50 | alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]} 51 | prevID = newid 52 | } 53 | alterIDs = append(alterIDs, primary) 54 | return alterIDs 55 | } 56 | -------------------------------------------------------------------------------- /tunnel/connection.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "time" 7 | 8 | N "github.com/Dreamacro/clash/common/net" 9 | "github.com/Dreamacro/clash/common/pool" 10 | "github.com/Dreamacro/clash/component/resolver" 11 | C "github.com/Dreamacro/clash/constant" 12 | ) 13 | 14 | func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { 15 | defer packet.Drop() 16 | 17 | // local resolve UDP dns 18 | if !metadata.Resolved() { 19 | ip, err := resolver.ResolveIP(metadata.Host) 20 | if err != nil { 21 | return err 22 | } 23 | metadata.DstIP = ip 24 | } 25 | 26 | addr := metadata.UDPAddr() 27 | if addr == nil { 28 | return errors.New("udp addr invalid") 29 | } 30 | 31 | if _, err := pc.WriteTo(packet.Data(), addr); err != nil { 32 | return err 33 | } 34 | // reset timeout 35 | pc.SetReadDeadline(time.Now().Add(udpTimeout)) 36 | 37 | return nil 38 | } 39 | 40 | func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { 41 | buf := pool.Get(pool.UDPBufferSize) 42 | defer pool.Put(buf) 43 | defer natTable.Delete(key) 44 | defer pc.Close() 45 | 46 | for { 47 | pc.SetReadDeadline(time.Now().Add(udpTimeout)) 48 | n, from, err := pc.ReadFrom(buf) 49 | if err != nil { 50 | return 51 | } 52 | 53 | if fAddr != nil { 54 | from = fAddr 55 | } 56 | 57 | _, err = packet.WriteBack(buf[:n], from) 58 | if err != nil { 59 | return 60 | } 61 | } 62 | } 63 | 64 | func handleSocket(ctx C.ConnContext, outbound net.Conn) { 65 | N.Relay(ctx.Conn(), outbound) 66 | } 67 | -------------------------------------------------------------------------------- /tunnel/mode.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | type TunnelMode int 10 | 11 | // ModeMapping is a mapping for Mode enum 12 | var ModeMapping = map[string]TunnelMode{ 13 | Global.String(): Global, 14 | Rule.String(): Rule, 15 | Direct.String(): Direct, 16 | } 17 | 18 | const ( 19 | Global TunnelMode = iota 20 | Rule 21 | Direct 22 | ) 23 | 24 | // UnmarshalJSON unserialize Mode 25 | func (m *TunnelMode) UnmarshalJSON(data []byte) error { 26 | var tp string 27 | json.Unmarshal(data, &tp) 28 | mode, exist := ModeMapping[strings.ToLower(tp)] 29 | if !exist { 30 | return errors.New("invalid mode") 31 | } 32 | *m = mode 33 | return nil 34 | } 35 | 36 | // UnmarshalYAML unserialize Mode with yaml 37 | func (m *TunnelMode) UnmarshalYAML(unmarshal func(any) error) error { 38 | var tp string 39 | unmarshal(&tp) 40 | mode, exist := ModeMapping[strings.ToLower(tp)] 41 | if !exist { 42 | return errors.New("invalid mode") 43 | } 44 | *m = mode 45 | return nil 46 | } 47 | 48 | // MarshalJSON serialize Mode 49 | func (m TunnelMode) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(m.String()) 51 | } 52 | 53 | // MarshalYAML serialize TunnelMode with yaml 54 | func (m TunnelMode) MarshalYAML() (any, error) { 55 | return m.String(), nil 56 | } 57 | 58 | func (m TunnelMode) String() string { 59 | switch m { 60 | case Global: 61 | return "global" 62 | case Rule: 63 | return "rule" 64 | case Direct: 65 | return "direct" 66 | default: 67 | return "Unknown" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tunnel/statistic/manager.go: -------------------------------------------------------------------------------- 1 | package statistic 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "go.uber.org/atomic" 8 | ) 9 | 10 | var DefaultManager *Manager 11 | 12 | func init() { 13 | DefaultManager = &Manager{ 14 | uploadTemp: atomic.NewInt64(0), 15 | downloadTemp: atomic.NewInt64(0), 16 | uploadBlip: atomic.NewInt64(0), 17 | downloadBlip: atomic.NewInt64(0), 18 | uploadTotal: atomic.NewInt64(0), 19 | downloadTotal: atomic.NewInt64(0), 20 | } 21 | 22 | go DefaultManager.handle() 23 | } 24 | 25 | type Manager struct { 26 | connections sync.Map 27 | uploadTemp *atomic.Int64 28 | downloadTemp *atomic.Int64 29 | uploadBlip *atomic.Int64 30 | downloadBlip *atomic.Int64 31 | uploadTotal *atomic.Int64 32 | downloadTotal *atomic.Int64 33 | } 34 | 35 | func (m *Manager) Join(c tracker) { 36 | m.connections.Store(c.ID(), c) 37 | } 38 | 39 | func (m *Manager) Leave(c tracker) { 40 | m.connections.Delete(c.ID()) 41 | } 42 | 43 | func (m *Manager) PushUploaded(size int64) { 44 | m.uploadTemp.Add(size) 45 | m.uploadTotal.Add(size) 46 | } 47 | 48 | func (m *Manager) PushDownloaded(size int64) { 49 | m.downloadTemp.Add(size) 50 | m.downloadTotal.Add(size) 51 | } 52 | 53 | func (m *Manager) Now() (up int64, down int64) { 54 | return m.uploadBlip.Load(), m.downloadBlip.Load() 55 | } 56 | 57 | func (m *Manager) Snapshot() *Snapshot { 58 | connections := []tracker{} 59 | m.connections.Range(func(key, value any) bool { 60 | connections = append(connections, value.(tracker)) 61 | return true 62 | }) 63 | 64 | return &Snapshot{ 65 | UploadTotal: m.uploadTotal.Load(), 66 | DownloadTotal: m.downloadTotal.Load(), 67 | Connections: connections, 68 | } 69 | } 70 | 71 | func (m *Manager) ResetStatistic() { 72 | m.uploadTemp.Store(0) 73 | m.uploadBlip.Store(0) 74 | m.uploadTotal.Store(0) 75 | m.downloadTemp.Store(0) 76 | m.downloadBlip.Store(0) 77 | m.downloadTotal.Store(0) 78 | } 79 | 80 | func (m *Manager) handle() { 81 | ticker := time.NewTicker(time.Second) 82 | 83 | for range ticker.C { 84 | m.uploadBlip.Store(m.uploadTemp.Load()) 85 | m.uploadTemp.Store(0) 86 | m.downloadBlip.Store(m.downloadTemp.Load()) 87 | m.downloadTemp.Store(0) 88 | } 89 | } 90 | 91 | type Snapshot struct { 92 | DownloadTotal int64 `json:"downloadTotal"` 93 | UploadTotal int64 `json:"uploadTotal"` 94 | Connections []tracker `json:"connections"` 95 | } 96 | --------------------------------------------------------------------------------