├── .Dockerfile ├── .github └── workflows │ ├── build.yml │ └── stale.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── README.md ├── config.go ├── config ├── README.md ├── dnsrecord.inc.conf.example ├── examples │ ├── 1.simple_proxy_service │ │ └── glider.conf │ ├── 2.one_forwarder │ │ └── glider.conf │ ├── 3.forward_chain │ │ └── glider.conf │ ├── 4.multiple_forwarders │ │ └── glider.conf │ ├── 5.rule_default_direct │ │ ├── glider.conf │ │ └── office.rule │ ├── 6.rule_default_forwarder │ │ ├── bypass.rule │ │ └── glider.conf │ ├── 7.rule_multiple_rule_files │ │ ├── glider.conf │ │ └── rules.d │ │ │ ├── home.rule │ │ │ └── office.rule │ ├── 8.transparent_proxy_with_dnsmasq │ │ ├── README.md │ │ └── glider.conf │ ├── 9.transparent_proxy_without_dnsmasq │ │ ├── README.md │ │ ├── glider.conf │ │ └── rules.d │ │ │ ├── home.rule │ │ │ ├── office.list │ │ │ └── office.rule │ └── README.md ├── glider.conf.example └── rules.d │ ├── direct.rule.example │ ├── office.list.example │ ├── office.rule.example │ └── reject.rule.example ├── dns ├── cache.go ├── client.go ├── message.go ├── server.go └── upstream.go ├── feature.go ├── feature_linux.go ├── go.mod ├── go.sum ├── ipset ├── ipset_linux.go └── ipset_other.go ├── main.go ├── pkg ├── log │ └── log.go ├── pool │ ├── buffer.go │ ├── bufreader.go │ └── bytesbuffer.go ├── smux │ ├── LICENSE │ ├── frame.go │ ├── mux.go │ ├── mux_test.go │ ├── session.go │ ├── session_test.go │ ├── shaper.go │ ├── shaper_test.go │ └── stream.go ├── sockopt │ ├── sockopt.go │ ├── sockopt_darwin.go │ ├── sockopt_linux.go │ └── sockopt_others.go └── socks │ └── socks.go ├── proxy ├── conn.go ├── dialer.go ├── direct.go ├── http │ ├── client.go │ ├── http.go │ ├── request.go │ └── server.go ├── kcp │ └── kcp.go ├── mixed │ └── mixed.go ├── obfs │ ├── http.go │ ├── obfs.go │ └── tls.go ├── proxy.go ├── pxyproto │ └── server.go ├── redir │ ├── redir_linux.go │ ├── redir_linux_386.go │ └── redir_linux_other.go ├── reject │ └── reject.go ├── server.go ├── smux │ ├── client.go │ ├── server.go │ └── smux.go ├── socks4 │ └── socks4.go ├── socks5 │ ├── client.go │ ├── packet.go │ ├── server.go │ └── socks5.go ├── ss │ ├── cipher │ │ ├── cipher.go │ │ ├── shadowaead │ │ │ ├── cipher.go │ │ │ ├── conn.go │ │ │ ├── packet.go │ │ │ └── stream.go │ │ └── shadowstream │ │ │ ├── cipher.go │ │ │ ├── conn.go │ │ │ ├── packet.go │ │ │ └── stream.go │ ├── client.go │ ├── packet.go │ ├── server.go │ └── ss.go ├── ssh │ └── ssh.go ├── ssr │ ├── internal │ │ ├── cipher │ │ │ └── cipher.go │ │ ├── client.go │ │ ├── obfs │ │ │ ├── base.go │ │ │ ├── http_post.go │ │ │ ├── http_simple.go │ │ │ ├── plain.go │ │ │ ├── random_head.go │ │ │ └── tls12_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 │ │ │ └── verify_sha1.go │ │ ├── ssr │ │ │ ├── adler32.go │ │ │ ├── crc32.go │ │ │ └── obfs.go │ │ └── tools │ │ │ ├── encrypt.go │ │ │ └── obfsutil.go │ └── ssr.go ├── tcp │ └── tcp.go ├── tls │ └── tls.go ├── tproxy │ ├── server.go │ └── tproxy.go ├── trojan │ ├── client.go │ ├── packet.go │ ├── server.go │ └── trojan.go ├── udp │ └── udp.go ├── unix │ ├── client.go │ ├── server.go │ └── unix.go ├── vless │ ├── addr.go │ ├── client.go │ ├── packet.go │ ├── server.go │ └── vless.go ├── vmess │ ├── addr.go │ ├── aead.go │ ├── auth.go │ ├── chunk.go │ ├── client.go │ ├── packet.go │ ├── size.go │ ├── user.go │ └── vmess.go ├── vsock │ ├── client.go │ ├── server.go │ ├── socket.go │ └── vsock.go └── ws │ ├── client.go │ ├── frame.go │ ├── server.go │ └── ws.go ├── rule ├── check.go ├── config.go ├── forward.go ├── group.go └── proxy.go ├── service ├── dhcpd │ ├── cilent.go │ ├── dhcpd.go │ └── pool.go └── service.go └── systemd ├── README.md ├── glider@.service ├── postinstall.sh ├── postremove.sh └── preremove.sh /.Dockerfile: -------------------------------------------------------------------------------- 1 | # Build Stage 2 | FROM --platform=$BUILDPLATFORM alpine AS build-env 3 | 4 | COPY ./dist /dist 5 | RUN apk add --no-cache ca-certificates 6 | 7 | ARG TARGETPLATFORM 8 | RUN case $TARGETPLATFORM in \ 9 | 'linux/386') \ 10 | export FOLDER='default_linux_386'; \ 11 | ;; \ 12 | 'linux/amd64') \ 13 | export FOLDER='default_linux_amd64_v1'; \ 14 | ;; \ 15 | 'linux/arm/v6') \ 16 | export FOLDER='default_linux_arm_6'; \ 17 | ;; \ 18 | 'linux/arm/v7') \ 19 | export FOLDER='default_linux_arm_7'; \ 20 | ;; \ 21 | 'linux/arm64') \ 22 | export FOLDER='default_linux_arm64'; \ 23 | ;; \ 24 | 'linux/riscv64') \ 25 | export FOLDER='default_linux_riscv64'; \ 26 | ;; \ 27 | *) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \ 28 | esac \ 29 | && mv /dist/$FOLDER /app 30 | 31 | 32 | # Final Stage 33 | FROM scratch 34 | COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 35 | COPY --from=build-env /app /app 36 | WORKDIR /app 37 | USER 1000 38 | ENTRYPOINT ["./glider"] 39 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | tags: 7 | - '*' 8 | pull_request: 9 | 10 | env: 11 | APP_NAME: glider 12 | PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Set Vars 25 | run: | 26 | echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV 27 | 28 | - name: Set up Go 29 | uses: actions/setup-go@v4 30 | with: 31 | check-latest: true 32 | go-version-file: 'go.mod' 33 | cache: true 34 | 35 | - name: Test 36 | run: go test -v . 37 | 38 | - name: Build 39 | uses: goreleaser/goreleaser-action@v5 40 | if: "!startsWith(github.ref, 'refs/tags/')" 41 | with: 42 | version: latest 43 | args: build --snapshot --clean 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Upload Artifact - Linux amd64 48 | uses: actions/upload-artifact@v3 49 | if: "!startsWith(github.ref, 'refs/tags/')" 50 | with: 51 | name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-linux-amd64 52 | path: | 53 | ./dist/default_linux_amd64_v1/${{ env.APP_NAME }} 54 | 55 | - name: Upload Artifact - Linux arm64 56 | uses: actions/upload-artifact@v3 57 | if: "!startsWith(github.ref, 'refs/tags/')" 58 | with: 59 | name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-linux-arm64 60 | path: | 61 | ./dist/default_linux_arm64/${{ env.APP_NAME }} 62 | 63 | - name: Upload Artifact - Darwin arm64 64 | uses: actions/upload-artifact@v3 65 | if: "!startsWith(github.ref, 'refs/tags/')" 66 | with: 67 | name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-darwin-arm64 68 | path: | 69 | ./dist/default_darwin_arm64/${{ env.APP_NAME }} 70 | 71 | - name: Upload Artifact - Windows amd64 72 | uses: actions/upload-artifact@v3 73 | if: "!startsWith(github.ref, 'refs/tags/')" 74 | with: 75 | name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}-windows-amd64 76 | path: | 77 | ./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe 78 | 79 | - name: Release 80 | uses: goreleaser/goreleaser-action@v5 81 | if: startsWith(github.ref, 'refs/tags/') 82 | with: 83 | version: latest 84 | args: release --clean 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v5 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | days-before-stale: 90 14 | days-before-close: 5 15 | exempt-issue-labels: "bug,enhancement" 16 | exempt-pr-labels: "bug,enhancement" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | 17 | # custom 18 | .idea 19 | .vscode 20 | .DS_Store 21 | 22 | # dev test only 23 | /dev/ 24 | dev*.go 25 | 26 | 27 | dist 28 | 29 | *.zip 30 | /*.conf 31 | /*.rule 32 | 33 | config/rules.d/*.rule 34 | config/rules.d/*.list 35 | 36 | glider 37 | /bak/ 38 | /rules.d/ 39 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - id: default 7 | env: 8 | - CGO_ENABLED=0 9 | goos: 10 | - windows 11 | - linux 12 | - darwin 13 | - freebsd 14 | goarch: 15 | - 386 16 | - amd64 17 | - arm 18 | - arm64 19 | - mips 20 | - mipsle 21 | - mips64 22 | - mips64le 23 | - riscv64 24 | goamd64: 25 | - v1 26 | - v3 27 | goarm: 28 | - 6 29 | - 7 30 | gomips: 31 | - hardfloat 32 | - softfloat 33 | 34 | archives: 35 | - id: default 36 | builds: 37 | - default 38 | wrap_in_directory: true 39 | format: tar.gz 40 | format_overrides: 41 | - goos: windows 42 | format: zip 43 | files: 44 | - LICENSE 45 | - README.md 46 | - config/**/* 47 | - systemd/* 48 | 49 | snapshot: 50 | name_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}' 51 | 52 | checksum: 53 | name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" 54 | 55 | release: 56 | prerelease: true 57 | draft: true 58 | 59 | nfpms: 60 | - id: glider 61 | package_name: glider 62 | vendor: meoww-bot 63 | homepage: https://github.com/meoww-bot/glider 64 | maintainer: meoww-bot 65 | description: Glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq). 66 | license: GPL-3.0 License 67 | formats: 68 | # - apk 69 | - deb 70 | # - rpm 71 | dependencies: 72 | - libsystemd0 73 | bindir: /usr/bin 74 | release: 1 75 | epoch: 1 76 | version_metadata: git 77 | section: default 78 | priority: extra 79 | contents: 80 | - src: systemd/glider@.service 81 | dst: /etc/systemd/system/glider@.service 82 | 83 | - src: config/glider.conf.example 84 | dst: /etc/glider/glider.conf.example 85 | 86 | scripts: 87 | postinstall: "systemd/postinstall.sh" 88 | preremove: "systemd/preremove.sh" 89 | postremove: "systemd/postremove.sh" 90 | 91 | deb: 92 | triggers: 93 | interest_noawait: 94 | - /lib/systemd/systemd 95 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build Stage 2 | FROM golang:1.20-alpine AS build-env 3 | ADD . /src 4 | RUN apk --no-cache add git \ 5 | && cd /src && go build -v -ldflags "-s -w" 6 | 7 | # Final Stage 8 | FROM alpine 9 | COPY --from=build-env /src/glider /app/ 10 | WORKDIR /app 11 | RUN apk -U upgrade --no-cache \ 12 | && apk --no-cache add ca-certificates 13 | USER 1000 14 | ENTRYPOINT ["./glider"] 15 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Config File 3 | Command: 4 | ```bash 5 | glider -config glider.conf 6 | ``` 7 | Config file, **just use the command line flag name as key name**: 8 | ```bash 9 | # COMMENT LINE 10 | KEY=VALUE 11 | KEY=VALUE 12 | # KEY equals to command line flag name: listen forward strategy... 13 | ``` 14 | 15 | Example: 16 | ```bash 17 | ### glider config file 18 | 19 | # verbose mode, print logs 20 | verbose 21 | 22 | # listen on 8443, serve as http/socks5 proxy on the same port. 23 | listen=:8443 24 | 25 | # upstream forward proxy 26 | forward=socks5://192.168.1.10:1080 27 | 28 | # upstream forward proxy 29 | forward=ss://method:pass@1.1.1.1:8443 30 | 31 | # upstream forward proxy (forward chain) 32 | forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080 33 | 34 | # multiple upstream proxies forward strategy 35 | strategy=rr 36 | 37 | # forwarder health check 38 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 39 | 40 | # check interval 41 | checkinterval=30 42 | 43 | 44 | # Setup a dns forwarding server 45 | dns=:53 46 | # global remote dns server (you can specify different dns server in rule file) 47 | dnsserver=8.8.8.8:53 48 | 49 | # RULE FILES 50 | rules-dir=rules.d 51 | #rulefile=office.rule 52 | #rulefile=home.rule 53 | 54 | # INCLUDE MORE CONFIG FILES 55 | #include=dnsrecord.inc.conf 56 | #include=more.inc.conf 57 | ``` 58 | See: 59 | - [glider.conf.example](glider.conf.example) 60 | - [examples](examples) 61 | 62 | ## Rule File 63 | Rule file, **same as the config file but specify forwarders based on destinations**: 64 | ```bash 65 | # YOU CAN USE ALL KEYS IN THE GLOBAL CONFIG FILE EXCEPT "listen", "rulefile" 66 | forward=socks5://192.168.1.10:1080 67 | forward=ss://method:pass@1.1.1.1:8443 68 | forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080 69 | strategy=rr 70 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 71 | checkinterval=30 72 | 73 | # DNS SERVER for domains in this rule file 74 | dnsserver=208.67.222.222:53 75 | 76 | # IPSET MANAGEMENT 77 | # ---------------- 78 | # Create and mange ipset on linux based on destinations in rule files 79 | # - add ip/cidrs in rule files on startup 80 | # - add resolved ips for domains in rule files by dns forwarding server 81 | # Usually used in transparent proxy mode on linux 82 | ipset=glider 83 | 84 | # YOU CAN SPECIFY DESTINATIONS TO USE THE ABOVE FORWARDERS 85 | # matches abc.com and *.abc.com 86 | domain=abc.com 87 | 88 | # matches 1.1.1.1 89 | ip=1.1.1.1 90 | 91 | # matches 192.168.100.0/24 92 | cidr=192.168.100.0/24 93 | 94 | # we can include a list file with only destinations settings 95 | include=office.list.example 96 | 97 | ``` 98 | See: 99 | - [office.rule.example](rules.d/office.rule.example) 100 | - [examples](examples) 101 | -------------------------------------------------------------------------------- /config/dnsrecord.inc.conf.example: -------------------------------------------------------------------------------- 1 | 2 | # intranet 3 | dnsrecord=oa.yourcompany.local/10.0.0.1 4 | dnsrecord=git.yourcompany.local/10.0.0.2 5 | 6 | # ad 7 | #dnsrecord=ad.domain/127.0.0.1 8 | -------------------------------------------------------------------------------- /config/examples/1.simple_proxy_service/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=:8443 -------------------------------------------------------------------------------- /config/examples/2.one_forwarder/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=:8443 6 | 7 | forward=socks5://192.168.1.10:1080 -------------------------------------------------------------------------------- /config/examples/3.forward_chain/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=:8443 6 | 7 | # first connect forwarder1 then forwarder2 then internet 8 | forward=http://forwarder1:8080,socks5://forwarder2:1080 -------------------------------------------------------------------------------- /config/examples/4.multiple_forwarders/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=:8443 6 | 7 | # first connect forwarder1 then forwarder2 then internet 8 | forward=http://forwarder1:8080,socks5://forwarder2:1080 9 | forward=http://1.1.1.1:8080 10 | 11 | # Setup a dns forwarding server 12 | dns=:53 13 | # global remote dns server (you can specify different dns server in rule file) 14 | dnsserver=1.1.1.1 15 | dnsserver=8.8.8.8 16 | 17 | # Round Robin mode: rr 18 | # High Availability mode: ha 19 | strategy=rr 20 | 21 | # forwarder health check 22 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 23 | 24 | # check interval(seconds) 25 | checkinterval=30 26 | 27 | # forward subscribe provider, only support base64 and non-clash format 28 | forwardprovider=https://xxx.com/subscribe?token=xxxxx 29 | forwardprovider=https://xxx2.com/subscribe?token=xxxxx 30 | 31 | # include keyword when subscribe to forwardprovider 32 | # if u config forwardsinclude and forwardsexclude, forwardsexclude will be omitted 33 | # forwards will be added to pool when meet include condition 34 | # forwardsinclude=HK 35 | # forwardsinclude=香港 36 | 37 | 38 | # exclude keyword when subscribe to forwardprovider 39 | # forwardsexclude=JP 40 | # forwardsexclude=日本 41 | -------------------------------------------------------------------------------- /config/examples/5.rule_default_direct/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=:8443 6 | 7 | # NOTE HERE: 8 | # Specify a rule file 9 | rulefile=office.rule -------------------------------------------------------------------------------- /config/examples/5.rule_default_direct/office.rule: -------------------------------------------------------------------------------- 1 | 2 | 3 | # first connect forwarder1 then forwarder2 then internet 4 | forward=http://forwarder1:8080,socks5://forwarder2:1080 5 | forward=http://1.1.1.1:8080 6 | 7 | # Round Robin mode: rr 8 | # High Availability mode: ha 9 | strategy=rr 10 | 11 | # forwarder health check 12 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 13 | checkinterval=30 14 | 15 | 16 | # matches abc.com and *.abc.com 17 | domain=abc.com 18 | 19 | # matches 1.1.1.1 20 | ip=1.1.1.1 21 | 22 | # matches 192.168.100.0/24 23 | cidr=192.168.100.0/24 24 | 25 | domain=example1.com 26 | domain=example2.com 27 | domain=example3.com 28 | ip=2.2.2.2 29 | ip=3.3.3.3 30 | cidr=172.16.0.0/24 31 | -------------------------------------------------------------------------------- /config/examples/6.rule_default_forwarder/bypass.rule: -------------------------------------------------------------------------------- 1 | 2 | # matches abc.com and *.abc.com 3 | domain=abc.com 4 | 5 | ip=127.0.0.1 6 | cidr=192.168.0.0/24 7 | cidr=192.168.1.0/24 8 | cidr=172.16.0.0/24 -------------------------------------------------------------------------------- /config/examples/6.rule_default_forwarder/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Verbose mode, print logs 4 | verbose=True 5 | 6 | listen=:8443 7 | 8 | # first connect forwarder1 then forwarder2 then internet 9 | forward=http://forwarder1:8080,socks5://forwarder2:1080 10 | forward=http://1.1.1.1:8080 11 | 12 | # Round Robin mode: rr 13 | # High Availability mode: ha 14 | strategy=rr 15 | 16 | # forwarder health check 17 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 18 | checkinterval=30 19 | 20 | 21 | # NOTE HERE: 22 | # Specify a rule file 23 | rulefile=bypass.rule -------------------------------------------------------------------------------- /config/examples/7.rule_multiple_rule_files/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=:8443 6 | 7 | # parse all *.rule files in rules.d folder 8 | rules-dir=rules.d 9 | -------------------------------------------------------------------------------- /config/examples/7.rule_multiple_rule_files/rules.d/home.rule: -------------------------------------------------------------------------------- 1 | 2 | 3 | forward=http://forwarder1:8080 4 | 5 | # first connect forwarder1 then forwarder2 then internet 6 | forward=http://forwarder1:8080,socks5://forwarder2:1080 7 | 8 | 9 | # Round Robin mode: rr 10 | # High Availability mode: ha 11 | strategy=rr 12 | 13 | # forwarder health check 14 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 15 | checkinterval=30 16 | 17 | 18 | # matches 192.168.0.0/16 19 | cidr=192.168.0.0/16 20 | -------------------------------------------------------------------------------- /config/examples/7.rule_multiple_rule_files/rules.d/office.rule: -------------------------------------------------------------------------------- 1 | 2 | 3 | forward=http://forwarder1:8080 4 | 5 | # first connect forwarder2 then forwarder3 then internet 6 | forward=http://forwarder2:8080,socks5://forwarder3:1080 7 | 8 | 9 | # Round Robin mode: rr 10 | # High Availability mode: ha 11 | strategy=rr 12 | 13 | # forwarder health check 14 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 15 | checkinterval=30 16 | 17 | 18 | # matches 172.16.0.0/24 19 | cidr=172.16.0.0/24 20 | -------------------------------------------------------------------------------- /config/examples/8.transparent_proxy_with_dnsmasq/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 8. Transparent Proxy with dnsmasq 3 | 4 | #### Setup a redirect proxy and a dns server with glider 5 | glider.conf 6 | ```bash 7 | verbose=True 8 | listen=redir://:1081 9 | forward=http://forwarder1:8080,socks5://forwarder2:1080 10 | forward=http://1.1.1.1:8080 11 | dns=:5353 12 | dnsserver=8.8.8.8:53 13 | strategy=rr 14 | checkinterval=30 15 | ``` 16 | 17 | #### Create a ipset manually 18 | ```bash 19 | ipset create myset hash:net 20 | ``` 21 | 22 | #### Config dnsmasq 23 | ```bash 24 | server=/example1.com/127.0.0.1#5353 25 | ipset=/example1.com/myset 26 | server=/example2.com/127.0.0.1#5353 27 | ipset=/example2.com/myset 28 | server=/example3.com/127.0.0.1#5353 29 | ipset=/example4.com/myset 30 | ``` 31 | 32 | #### Config iptables on your linux gateway 33 | ```bash 34 | iptables -t nat -I PREROUTING -p tcp -m set --match-set myset dst -j REDIRECT --to-ports 1081 35 | #iptables -t nat -I OUTPUT -p tcp -m set --match-set myset dst -j REDIRECT --to-ports 1081 36 | ``` 37 | 38 | #### When client requests network, the whole process: 39 | 1. all dns requests for domain example1.com will be forward to glider(:5353) by dnsmasq 40 | 2. glider will forward dns requests to 8.8.8.8:53 in tcp via forwarders 41 | 3. the resolved ip address will be added to ipset "myset" by dnsmasq 42 | 4. all tcp requests to example1.com will be redirect to glider(:1081) by iptables 43 | 5. glider then forward requests to example1.com via forwarders 44 | -------------------------------------------------------------------------------- /config/examples/8.transparent_proxy_with_dnsmasq/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | listen=redir://:1081 6 | 7 | forward=http://forwarder1:8080,socks5://forwarder2:1080 8 | forward=http://1.1.1.1:8080 9 | 10 | dns=:5353 11 | dnsserver=8.8.8.8:53 12 | 13 | 14 | strategy=rr 15 | # forwarder health check 16 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 17 | checkinterval=30 18 | -------------------------------------------------------------------------------- /config/examples/9.transparent_proxy_without_dnsmasq/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 9. Transparent Proxy without dnsmasq 3 | 4 | PC Client -> Gateway with glider running(linux box) -> Upstream Forwarders -> Internet 5 | 6 | #### In this mode, glider will act as the following roles: 7 | 1. A transparent proxy server 8 | 2. A dns forwarding server 9 | 3. A ipset manager 10 | 11 | so you don't need any dns server in your network. 12 | 13 | #### Create a ipset manually 14 | ```bash 15 | ipset create glider hash:net 16 | ``` 17 | 18 | #### Glider Configuration 19 | ##### glider.conf 20 | ```bash 21 | verbose=True 22 | 23 | # as a redir proxy 24 | listen=redir://:1081 25 | 26 | # as a dns forwarding server 27 | dns=:53 28 | dnsserver=8.8.8.8:53 29 | dnsserver=8.8.4.4:53 30 | 31 | # specify rule files 32 | rules-dir=rules.d 33 | ``` 34 | 35 | ##### office.rule 36 | ```bash 37 | # add your forwarders 38 | forward=http://forwarder1:8080,socks5://forwarder2:1080 39 | forward=http://1.1.1.1:8080 40 | strategy=rr 41 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 42 | 43 | # specify a different dns server(if need) 44 | dnsserver=208.67.222.222:53 45 | 46 | # as a ipset manager 47 | ipset=glider 48 | 49 | # specify destinations 50 | include=office.list 51 | 52 | domain=example1.com 53 | domain=example2.com 54 | # matches ip 55 | ip=1.1.1.1 56 | ip=2.2.2.2 57 | # matches a ip net 58 | cidr=192.168.100.0/24 59 | cidr=172.16.100.0/24 60 | ``` 61 | 62 | ##### office.list 63 | ```bash 64 | # destinations list 65 | domain=mycompany.com 66 | domain=mycompany1.com 67 | ip=4.4.4.4 68 | ip=5.5.5.5 69 | cidr=172.16.101.0/24 70 | cidr=172.16.102.0/24 71 | ``` 72 | 73 | #### Configure iptables on your linux gateway 74 | ```bash 75 | iptables -t nat -I PREROUTING -p tcp -m set --match-set glider dst -j REDIRECT --to-ports 1081 76 | iptables -t nat -I OUTPUT -p tcp -m set --match-set glider dst -j REDIRECT --to-ports 1081 77 | ``` 78 | 79 | #### Server DNS settings 80 | Set server's nameserver to glider: 81 | ```bash 82 | echo nameserver 127.0.0.1 > /etc/resolv.conf 83 | ``` 84 | 85 | #### Client settings 86 | Use the linux server's ip as your gateway. 87 | Use the linux server's ip as your dns server. 88 | 89 | #### When client requesting to access http://example1.com (in office.rule), the whole process: 90 | DNS Resolving: 91 | 1. client sends a udp dns request to linux server, and glider will receive the request(as it listens on the default dns port :53) 92 | 2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen) 93 | 3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy). 94 | 4. glider updates it's office rule config, adds the resolved ip address to it. 95 | 5. glider adds the resolved ip into ipset "glider", and returns the dns answer to client. 96 | 97 | Destination Accessing: 98 | 1. client sends http request to the resolved ip of example1.com. 99 | 2. linux gateway server will get the request. 100 | 3. iptables matches the ip in ipset "glider" and redirect this request to :1081(glider) 101 | 4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request. 102 | -------------------------------------------------------------------------------- /config/examples/9.transparent_proxy_without_dnsmasq/glider.conf: -------------------------------------------------------------------------------- 1 | 2 | # Verbose mode, print logs 3 | verbose=True 4 | 5 | # as a redir proxy 6 | listen=redir://:1081 7 | 8 | # as a dns forwarding server 9 | dns=:53 10 | dnsserver=8.8.8.8:53 11 | 12 | # parse all *.rule files in rules.d folder 13 | rules-dir=rules.d 14 | -------------------------------------------------------------------------------- /config/examples/9.transparent_proxy_without_dnsmasq/rules.d/home.rule: -------------------------------------------------------------------------------- 1 | 2 | 3 | forward=http://forwarder1:8080 4 | 5 | # first connect forwarder1 then forwarder2 then internet 6 | forward=http://forwarder1:8080,socks5://forwarder2:1080 7 | 8 | 9 | # Round Robin mode: rr 10 | # High Availability mode: ha 11 | strategy=rr 12 | 13 | # forwarder health check 14 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 15 | checkinterval=30 16 | 17 | # as a ipset manager 18 | ipset=glider 19 | 20 | # matches 192.168.0.0/16 21 | cidr=192.168.0.0/16 22 | -------------------------------------------------------------------------------- /config/examples/9.transparent_proxy_without_dnsmasq/rules.d/office.list: -------------------------------------------------------------------------------- 1 | 2 | domain=mycompany.com 3 | domain=mycompany1.com 4 | ip=4.4.4.4 5 | ip=5.5.5.5 6 | cidr=172.16.101.0/24 7 | cidr=172.16.102.0/24 8 | -------------------------------------------------------------------------------- /config/examples/9.transparent_proxy_without_dnsmasq/rules.d/office.rule: -------------------------------------------------------------------------------- 1 | 2 | 3 | forward=http://forwarder1:8080 4 | 5 | # first connect forwarder2 then forwarder3 then internet 6 | forward=http://forwarder2:8080,socks5://forwarder3:1080 7 | 8 | 9 | # Round Robin mode: rr 10 | # High Availability mode: ha 11 | strategy=rr 12 | # forwarder health check 13 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 14 | checkinterval=30 15 | 16 | # specify a different dns server(if need) 17 | dnsserver=208.67.222.222:53 18 | 19 | # as a ipset manager 20 | ipset=glider 21 | 22 | # specify destinations 23 | include=office.list 24 | 25 | domain=example1.com 26 | domain=example2.com 27 | # matches ip 28 | ip=1.1.1.1 29 | ip=2.2.2.2 30 | # matches a ip net 31 | cidr=192.168.100.0/24 32 | cidr=172.16.100.0/24 33 | -------------------------------------------------------------------------------- /config/examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Glider Configuration Examples 3 | 4 | ## 1. Simple Proxy Service 5 | Just listen on 8443 as HTTP/SOCKS5 proxy on the same port, forward all requests directly. 6 | 7 | ``` 8 | Clients --> Listener --> Internet 9 | ``` 10 | 11 | - [simple_proxy_service](1.simple_proxy_service) 12 | 13 | ## 2. One remote upstream proxy 14 | 15 | ``` 16 | Clients --> Listener --> Forwarder --> Internet 17 | ``` 18 | 19 | - [one_forwarder](2.one_forwarder) 20 | 21 | ## 3. One remote upstream PROXY CHAIN 22 | 23 | ``` 24 | Clients --> Listener --> Forwarder1 --> Forwarder2 --> Internet 25 | ``` 26 | 27 | - [forward_chain](3.forward_chain) 28 | 29 | ## 4. Multiple upstream proxies 30 | 31 | ``` 32 | |Forwarder ----------------->| 33 | Clients --> Listener --> | | Internet 34 | |Forwarder --> Forwarder->...| 35 | ``` 36 | 37 | - [multiple_forwarders](4.multiple_forwarders) 38 | 39 | 40 | ## 5. With Rule File: Default Direct, Rule file use forwarder 41 | 42 | Default: 43 | ``` 44 | Clients --> Listener --> Internet 45 | ``` 46 | Destinations specified in rule file: 47 | ``` 48 | |Forwarder ----------------->| 49 | Clients --> Listener --> | | Internet 50 | |Forwarder --> Forwarder->...| 51 | ``` 52 | 53 | - [rule_default_direct](5.rule_default_direct) 54 | 55 | 56 | ## 6. With Rule File: Default use forwarder, rule file use direct 57 | 58 | Default: 59 | ``` 60 | |Forwarder ----------------->| 61 | Clients --> Listener --> | | Internet 62 | |Forwarder --> Forwarder->...| 63 | ``` 64 | 65 | Destinations specified in rule file: 66 | ``` 67 | Clients --> Listener --> Internet 68 | ``` 69 | 70 | - [rule_default_forwarder](6.rule_default_forwarder) 71 | 72 | 73 | ## 7. With Rule File: multiple rule files 74 | 75 | Default: 76 | ``` 77 | Clients --> Listener --> Internet 78 | ``` 79 | Destinations specified in rule file1: 80 | ``` 81 | |Forwarder1 ----------------->| 82 | Clients --> Listener --> | | Internet 83 | |Forwarder2 --> Forwarder3->...| 84 | ``` 85 | Destinations specified in rule file2: 86 | ``` 87 | |Forwarder4 ----------------->| 88 | Clients --> Listener --> | | Internet 89 | |Forwarder5 --> Forwarder6->...| 90 | ``` 91 | 92 | - [rule_multiple_rule_files](7.rule_multiple_rule_files) 93 | 94 | ## 8. Transparent Proxy with Dnsmasq 95 | - [transparent_proxy_with_dnsmasq](8.transparent_proxy_with_dnsmasq) 96 | 97 | ## 9. Transparent Proxy without Dnsmasq 98 | - [transparent_proxy_without_dnsmasq](9.transparent_proxy_without_dnsmasq) -------------------------------------------------------------------------------- /config/rules.d/direct.rule.example: -------------------------------------------------------------------------------- 1 | 2 | # Specify destinations in rule file without forwarders, so glider will bypass 3 | # all forwarders and direct connect them instead 4 | 5 | ip=127.0.0.1 6 | cidr=192.168.1.0/24 7 | domain=bypass.com 8 | -------------------------------------------------------------------------------- /config/rules.d/office.list.example: -------------------------------------------------------------------------------- 1 | 2 | domain=mycompany.com 3 | domain=mycompany1.com 4 | ip=4.4.4.4 5 | ip=5.5.5.5 6 | cidr=172.16.101.0/24 7 | cidr=172.16.102.0/24 8 | -------------------------------------------------------------------------------- /config/rules.d/office.rule.example: -------------------------------------------------------------------------------- 1 | # Glider rule configuration file. 2 | # 3 | # Format is the same as glider main config file. 4 | # EXCEPTION: Listeners are NOT allowed to setup here. 5 | 6 | # FORWARDERS 7 | # ---------- 8 | # Forwarders, we can setup multiple forwarders. 9 | forward=socks5://192.168.1.10:1080 10 | forward=ss://method:pass@1.1.1.1:8443 11 | forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080 12 | 13 | # STRATEGY for multiple forwarders. rr|ha 14 | strategy=rr 15 | 16 | # FORWARDER CHECK SETTINGS 17 | check=http://www.msftconnecttest.com/connecttest.txt#expect=200 18 | checkinterval=30 19 | 20 | # DNS SERVER for domains in this rule file 21 | dnsserver=208.67.222.222:53 22 | 23 | # IPSET MANAGEMENT 24 | # ---------------- 25 | # Create and mange ipset on linux based on destinations in rule files 26 | # - add ip/cidrs in rule files on startup 27 | # - add resolved ips for domains in rule files by dns forwarding server 28 | # Usually used in transparent proxy mode on linux 29 | # Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6 30 | ipset=glider 31 | 32 | # DESTINATIONS 33 | # ------------ 34 | # ALL destinations matches the following rules will be forward using forwarders specified above 35 | 36 | # INCLUDE FILE 37 | # we can include a list file with only destinations settings 38 | include=office.list 39 | 40 | # matches example.com and *.example.com 41 | domain=example.com 42 | domain=example1.com 43 | domain=example2.com 44 | domain=example3.com 45 | 46 | # matches ip 47 | ip=1.1.1.1 48 | ip=2.2.2.2 49 | ip=3.3.3.3 50 | 51 | # matches a ip net 52 | cidr=192.168.100.0/24 53 | cidr=172.16.100.0/24 54 | -------------------------------------------------------------------------------- /config/rules.d/reject.rule.example: -------------------------------------------------------------------------------- 1 | 2 | forward=reject:// 3 | 4 | ipset=glider 5 | 6 | domain=pornhub.com 7 | domain=amazon.com 8 | -------------------------------------------------------------------------------- /dns/cache.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // LruCache is the struct of LruCache. 9 | type LruCache struct { 10 | mu sync.Mutex 11 | size int 12 | head *item 13 | tail *item 14 | cache map[string]*item 15 | store map[string][]byte 16 | } 17 | 18 | // item is the struct of cache item. 19 | type item struct { 20 | key string 21 | val []byte 22 | exp int64 23 | prev *item 24 | next *item 25 | } 26 | 27 | // NewLruCache returns a new LruCache. 28 | func NewLruCache(size int) *LruCache { 29 | // init 2 items here, it doesn't matter cuz they will be deleted when the cache is full 30 | head, tail := &item{key: "head"}, &item{key: "tail"} 31 | head.next, tail.prev = tail, head 32 | c := &LruCache{ 33 | size: size, 34 | head: head, 35 | tail: tail, 36 | cache: make(map[string]*item, size), 37 | store: make(map[string][]byte), 38 | } 39 | c.cache[head.key], c.cache[tail.key] = head, tail 40 | return c 41 | } 42 | 43 | // Get gets an item from cache. 44 | func (c *LruCache) Get(k string) (v []byte, expired bool) { 45 | c.mu.Lock() 46 | defer c.mu.Unlock() 47 | 48 | if v, ok := c.store[k]; ok { 49 | return v, false 50 | } 51 | 52 | if it, ok := c.cache[k]; ok { 53 | v = it.val 54 | if it.exp < time.Now().Unix() { 55 | expired = true 56 | } 57 | c.moveToHead(it) 58 | } 59 | return 60 | } 61 | 62 | // Set sets an item with key, value, and ttl(seconds). 63 | // if the ttl is zero, this item will be set and never be deleted. 64 | // if the key exists, update it with value and exp and move it to head. 65 | // if the key does not exist, put a new item to the cache's head. 66 | // finally, remove the tail if the cache is full. 67 | func (c *LruCache) Set(k string, v []byte, ttl int) { 68 | c.mu.Lock() 69 | defer c.mu.Unlock() 70 | 71 | if ttl == 0 { 72 | c.store[k] = v 73 | return 74 | } 75 | 76 | exp := time.Now().Add(time.Second * time.Duration(ttl)).Unix() 77 | if it, ok := c.cache[k]; ok { 78 | it.val = v 79 | it.exp = exp 80 | c.moveToHead(it) 81 | return 82 | } 83 | 84 | c.putToHead(k, v, exp) 85 | 86 | // NOTE: the cache size will always >= 2, 87 | // but it doesn't matter in our environment. 88 | if len(c.cache) > c.size { 89 | c.removeTail() 90 | } 91 | } 92 | 93 | // putToHead puts a new item to cache's head. 94 | func (c *LruCache) putToHead(k string, v []byte, exp int64) { 95 | it := &item{key: k, val: v, exp: exp, prev: nil, next: c.head} 96 | it.prev = nil 97 | it.next = c.head 98 | c.head.prev = it 99 | c.head = it 100 | 101 | c.cache[k] = it 102 | } 103 | 104 | // moveToHead moves an existing item to cache's head. 105 | func (c *LruCache) moveToHead(it *item) { 106 | if it != c.head { 107 | if c.tail == it { 108 | c.tail = it.prev 109 | c.tail.next = nil 110 | } else { 111 | it.prev.next = it.next 112 | it.next.prev = it.prev 113 | } 114 | it.prev = nil 115 | it.next = c.head 116 | c.head.prev = it 117 | c.head = it 118 | } 119 | } 120 | 121 | // removeTail removes the tail from cache. 122 | func (c *LruCache) removeTail() { 123 | delete(c.cache, c.tail.key) 124 | 125 | c.tail.prev.next = nil 126 | c.tail = c.tail.prev 127 | } 128 | -------------------------------------------------------------------------------- /dns/server.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "github.com/meoww-bot/glider/pkg/log" 11 | "github.com/meoww-bot/glider/pkg/pool" 12 | "github.com/meoww-bot/glider/proxy" 13 | ) 14 | 15 | // conn timeout, in seconds. 16 | const timeout = 30 17 | 18 | // Server is a dns server struct. 19 | type Server struct { 20 | addr string 21 | // Client is used to communicate with upstream dns servers 22 | *Client 23 | } 24 | 25 | // NewServer returns a new dns server. 26 | func NewServer(addr string, p proxy.Proxy, config *Config) (*Server, error) { 27 | c, err := NewClient(p, config) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | s := &Server{ 33 | addr: addr, 34 | Client: c, 35 | } 36 | return s, nil 37 | } 38 | 39 | // Start starts the dns forwarding server. 40 | // We use WaitGroup here to ensure both udp and tcp serer are completly running, 41 | // so we can start any other services later, since they may rely on dns service. 42 | func (s *Server) Start() { 43 | var wg sync.WaitGroup 44 | wg.Add(2) 45 | go s.ListenAndServeTCP(&wg) 46 | go s.ListenAndServeUDP(&wg) 47 | wg.Wait() 48 | } 49 | 50 | // ListenAndServeUDP listen and serves on udp port. 51 | func (s *Server) ListenAndServeUDP(wg *sync.WaitGroup) { 52 | pc, err := net.ListenPacket("udp", s.addr) 53 | wg.Done() 54 | if err != nil { 55 | log.F("[dns] failed to listen on %s, error: %v", s.addr, err) 56 | return 57 | } 58 | defer pc.Close() 59 | 60 | log.F("[dns] listening UDP on %s", s.addr) 61 | 62 | for { 63 | reqBytes := pool.GetBuffer(UDPMaxLen) 64 | n, caddr, err := pc.ReadFrom(reqBytes) 65 | if err != nil { 66 | log.F("[dns] local read error: %v", err) 67 | pool.PutBuffer(reqBytes) 68 | continue 69 | } 70 | go s.ServePacket(pc, caddr, reqBytes[:n]) 71 | } 72 | } 73 | 74 | // ServePacket serves dns packet conn. 75 | func (s *Server) ServePacket(pc net.PacketConn, caddr net.Addr, reqBytes []byte) { 76 | respBytes, err := s.Exchange(reqBytes, caddr.String(), false) 77 | defer func() { 78 | pool.PutBuffer(reqBytes) 79 | pool.PutBuffer(respBytes) 80 | }() 81 | 82 | if err != nil { 83 | log.F("[dns] error in exchange for %s: %s", caddr, err) 84 | return 85 | } 86 | 87 | _, err = pc.WriteTo(respBytes, caddr) 88 | if err != nil { 89 | log.F("[dns] error in local write to %s: %s", caddr, err) 90 | return 91 | } 92 | } 93 | 94 | // ListenAndServeTCP listen and serves on tcp port. 95 | func (s *Server) ListenAndServeTCP(wg *sync.WaitGroup) { 96 | l, err := net.Listen("tcp", s.addr) 97 | wg.Done() 98 | if err != nil { 99 | log.F("[dns-tcp] error: %v", err) 100 | return 101 | } 102 | defer l.Close() 103 | 104 | log.F("[dns-tcp] listening TCP on %s", s.addr) 105 | 106 | for { 107 | c, err := l.Accept() 108 | if err != nil { 109 | log.F("[dns-tcp] error: failed to accept: %v", err) 110 | continue 111 | } 112 | go s.ServeTCP(c) 113 | } 114 | } 115 | 116 | // ServeTCP serves a dns tcp connection. 117 | func (s *Server) ServeTCP(c net.Conn) { 118 | defer c.Close() 119 | 120 | c.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second)) 121 | 122 | var reqLen uint16 123 | if err := binary.Read(c, binary.BigEndian, &reqLen); err != nil { 124 | log.F("[dns-tcp] failed to get request length: %v", err) 125 | return 126 | } 127 | 128 | reqBytes := pool.GetBuffer(int(reqLen)) 129 | defer pool.PutBuffer(reqBytes) 130 | 131 | _, err := io.ReadFull(c, reqBytes) 132 | if err != nil { 133 | log.F("[dns-tcp] error in read reqBytes %s", err) 134 | return 135 | } 136 | 137 | respBytes, err := s.Exchange(reqBytes, c.RemoteAddr().String(), true) 138 | defer pool.PutBuffer(respBytes) 139 | if err != nil { 140 | log.F("[dns-tcp] error in exchange: %s", err) 141 | return 142 | } 143 | 144 | lenBuf := pool.GetBuffer(2) 145 | defer pool.PutBuffer(lenBuf) 146 | 147 | binary.BigEndian.PutUint16(lenBuf, uint16(len(respBytes))) 148 | if _, err := (&net.Buffers{lenBuf, respBytes}).WriteTo(c); err != nil { 149 | log.F("[dns-tcp] error in write respBytes: %s", err) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /dns/upstream.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "sync/atomic" 6 | ) 7 | 8 | // UPStream is a dns upstream. 9 | type UPStream struct { 10 | index uint32 11 | servers []string 12 | } 13 | 14 | // NewUPStream returns a new UpStream. 15 | func NewUPStream(servers []string) *UPStream { 16 | // default port for dns upstream servers 17 | for i, server := range servers { 18 | if _, port, _ := net.SplitHostPort(server); port == "" { 19 | servers[i] = net.JoinHostPort(server, "53") 20 | } 21 | } 22 | return &UPStream{servers: servers} 23 | } 24 | 25 | // Server returns a dns server. 26 | func (u *UPStream) Server() string { 27 | return u.servers[atomic.LoadUint32(&u.index)%uint32(len(u.servers))] 28 | } 29 | 30 | // Switch switches to the next dns server. 31 | func (u *UPStream) Switch() string { 32 | return u.servers[atomic.AddUint32(&u.index, 1)%uint32(len(u.servers))] 33 | } 34 | 35 | // SwitchIf switches to the next dns server if needed. 36 | func (u *UPStream) SwitchIf(server string) string { 37 | if u.Server() == server { 38 | return u.Switch() 39 | } 40 | return u.Server() 41 | } 42 | 43 | // Len returns the number of dns servers. 44 | func (u *UPStream) Len() int { 45 | return len(u.servers) 46 | } 47 | -------------------------------------------------------------------------------- /feature.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // comment out the services you don't need to make the compiled binary smaller. 5 | // _ "github.com/meoww-bot/glider/service/xxx" 6 | 7 | // comment out the protocols you don't need to make the compiled binary smaller. 8 | _ "github.com/meoww-bot/glider/proxy/http" 9 | _ "github.com/meoww-bot/glider/proxy/kcp" 10 | _ "github.com/meoww-bot/glider/proxy/mixed" 11 | _ "github.com/meoww-bot/glider/proxy/obfs" 12 | _ "github.com/meoww-bot/glider/proxy/pxyproto" 13 | _ "github.com/meoww-bot/glider/proxy/reject" 14 | _ "github.com/meoww-bot/glider/proxy/smux" 15 | _ "github.com/meoww-bot/glider/proxy/socks4" 16 | _ "github.com/meoww-bot/glider/proxy/socks5" 17 | _ "github.com/meoww-bot/glider/proxy/ss" 18 | _ "github.com/meoww-bot/glider/proxy/ssh" 19 | _ "github.com/meoww-bot/glider/proxy/ssr" 20 | _ "github.com/meoww-bot/glider/proxy/tcp" 21 | _ "github.com/meoww-bot/glider/proxy/tls" 22 | _ "github.com/meoww-bot/glider/proxy/trojan" 23 | _ "github.com/meoww-bot/glider/proxy/udp" 24 | _ "github.com/meoww-bot/glider/proxy/vless" 25 | _ "github.com/meoww-bot/glider/proxy/vmess" 26 | _ "github.com/meoww-bot/glider/proxy/ws" 27 | ) 28 | -------------------------------------------------------------------------------- /feature_linux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // comment out the services you don't need to make the compiled binary smaller. 5 | _ "github.com/meoww-bot/glider/service/dhcpd" 6 | 7 | // comment out the protocols you don't need to make the compiled binary smaller. 8 | _ "github.com/meoww-bot/glider/proxy/redir" 9 | _ "github.com/meoww-bot/glider/proxy/tproxy" 10 | _ "github.com/meoww-bot/glider/proxy/unix" 11 | _ "github.com/meoww-bot/glider/proxy/vsock" 12 | ) 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/meoww-bot/glider 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da 7 | github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d 8 | github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb 9 | github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 10 | github.com/insomniacslk/dhcp v0.0.0-20231126010706-b0416c0f187a 11 | github.com/nadoo/conflag v0.3.1 12 | github.com/nadoo/ipset v0.5.0 13 | github.com/xtaci/kcp-go/v5 v5.6.1 14 | golang.org/x/crypto v0.16.0 15 | golang.org/x/sys v0.15.0 16 | ) 17 | 18 | require ( 19 | github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect 20 | github.com/josharian/native v1.1.0 // indirect 21 | github.com/klauspost/cpuid/v2 v2.2.6 // indirect 22 | github.com/klauspost/reedsolomon v1.11.8 // indirect 23 | github.com/pierrec/lz4/v4 v4.1.18 // indirect 24 | github.com/pkg/errors v0.9.1 // indirect 25 | github.com/templexxx/cpu v0.1.0 // indirect 26 | github.com/templexxx/xorsimd v0.4.2 // indirect 27 | github.com/tjfoc/gmsm v1.4.1 // indirect 28 | github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect 29 | golang.org/x/net v0.19.0 // indirect 30 | ) 31 | 32 | // Replace dependency modules with local developing copy 33 | // use `go list -m all` to confirm the final module used 34 | // replace ( 35 | // github.com/nadoo/conflag => ../conflag 36 | // ) 37 | -------------------------------------------------------------------------------- /ipset/ipset_linux.go: -------------------------------------------------------------------------------- 1 | package ipset 2 | 3 | import ( 4 | "net/netip" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/nadoo/ipset" 9 | 10 | "github.com/meoww-bot/glider/rule" 11 | ) 12 | 13 | // Manager struct. 14 | type Manager struct { 15 | domainSet sync.Map 16 | } 17 | 18 | // NewManager returns a Manager 19 | func NewManager(rules []*rule.Config) (*Manager, error) { 20 | if err := ipset.Init(); err != nil { 21 | return nil, err 22 | } 23 | 24 | m := &Manager{} 25 | sets := make(map[string]struct{}) 26 | 27 | for _, r := range rules { 28 | if r.IPSet == "" { 29 | continue 30 | } 31 | 32 | if _, ok := sets[r.IPSet]; !ok { 33 | sets[r.IPSet] = struct{}{} 34 | ipset.Create(r.IPSet) 35 | ipset.Flush(r.IPSet) 36 | ipset.Create(r.IPSet+"6", ipset.OptIPv6()) 37 | ipset.Flush(r.IPSet + "6") 38 | } 39 | 40 | for _, domain := range r.Domain { 41 | m.domainSet.Store(domain, r.IPSet) 42 | } 43 | for _, ip := range r.IP { 44 | addToSet(r.IPSet, ip) 45 | } 46 | for _, cidr := range r.CIDR { 47 | addToSet(r.IPSet, cidr) 48 | } 49 | } 50 | 51 | return m, nil 52 | } 53 | 54 | // AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule. 55 | func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error { 56 | domain = strings.ToLower(domain) 57 | for i := len(domain); i != -1; { 58 | i = strings.LastIndexByte(domain[:i], '.') 59 | if setName, ok := m.domainSet.Load(domain[i+1:]); ok { 60 | addAddrToSet(setName.(string), ip) 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | func addToSet(s, item string) error { 67 | if strings.IndexByte(item, '.') == -1 { 68 | return ipset.Add(s+"6", item) 69 | } 70 | return ipset.Add(s, item) 71 | } 72 | 73 | func addAddrToSet(s string, ip netip.Addr) error { 74 | if ip.Is4() { 75 | return ipset.AddAddr(s, ip) 76 | } 77 | return ipset.AddAddr(s+"6", ip) 78 | } 79 | -------------------------------------------------------------------------------- /ipset/ipset_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package ipset 4 | 5 | import ( 6 | "errors" 7 | "net/netip" 8 | 9 | "github.com/meoww-bot/glider/rule" 10 | ) 11 | 12 | // Manager struct 13 | type Manager struct{} 14 | 15 | // NewManager returns a Manager 16 | func NewManager(rules []*rule.Config) (*Manager, error) { 17 | return nil, errors.New("ipset not supported on this os") 18 | } 19 | 20 | // AddDomainIP implements the DNSAnswerHandler function 21 | func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error { 22 | return errors.New("ipset not supported on this os") 23 | } 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/meoww-bot/glider/dns" 12 | "github.com/meoww-bot/glider/ipset" 13 | "github.com/meoww-bot/glider/pkg/log" 14 | "github.com/meoww-bot/glider/proxy" 15 | "github.com/meoww-bot/glider/rule" 16 | "github.com/meoww-bot/glider/service" 17 | ) 18 | 19 | var ( 20 | version = "0.16.3" 21 | config = parseConfig() 22 | ) 23 | 24 | func main() { 25 | // global rule proxy 26 | pxy := rule.NewProxy(config.Forwards, &config.Strategy, config.rules, config.ForwardsProvider, config.ForwardsExclude, config.ForwardsInclude) 27 | 28 | // ipset manager 29 | ipsetM, _ := ipset.NewManager(config.rules) 30 | 31 | // check and setup dns server 32 | if config.DNS != "" { 33 | d, err := dns.NewServer(config.DNS, pxy, &config.DNSConfig) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | // rules 39 | for _, r := range config.rules { 40 | if len(r.DNSServers) > 0 { 41 | for _, domain := range r.Domain { 42 | d.SetServers(domain, r.DNSServers) 43 | } 44 | } 45 | } 46 | 47 | // add a handler to update proxy rules when a domain resolved 48 | d.AddHandler(pxy.AddDomainIP) 49 | if ipsetM != nil { 50 | d.AddHandler(ipsetM.AddDomainIP) 51 | } 52 | 53 | d.Start() 54 | 55 | // custom resolver 56 | net.DefaultResolver = &net.Resolver{ 57 | PreferGo: true, 58 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 59 | d := net.Dialer{Timeout: time.Second * 3} 60 | return d.DialContext(ctx, "udp", config.DNS) 61 | }, 62 | } 63 | } 64 | 65 | for _, r := range config.rules { 66 | r.IP, r.CIDR, r.Domain = nil, nil, nil 67 | } 68 | 69 | pxy.Fetch() 70 | // enable checkers 71 | pxy.Check() 72 | 73 | // run proxy servers 74 | for _, listen := range config.Listens { 75 | local, err := proxy.ServerFromURL(listen, pxy) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | go local.ListenAndServe() 80 | } 81 | 82 | // run services 83 | for _, s := range config.Services { 84 | service, err := service.New(s) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | go service.Run() 89 | } 90 | 91 | sigCh := make(chan os.Signal, 1) 92 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 93 | <-sigCh 94 | } 95 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | stdlog "log" 6 | ) 7 | 8 | var enable = false 9 | 10 | // Set sets the logger's verbose mode and output flags. 11 | func Set(verbose bool, flag int) { 12 | enable = verbose 13 | stdlog.SetFlags(flag) 14 | } 15 | 16 | // F prints debug log. 17 | func F(f string, v ...any) { 18 | if enable { 19 | stdlog.Output(2, fmt.Sprintf(f, v...)) 20 | } 21 | } 22 | 23 | // Print prints log. 24 | func Print(v ...any) { 25 | stdlog.Print(v...) 26 | } 27 | 28 | // Printf prints log. 29 | func Printf(f string, v ...any) { 30 | stdlog.Printf(f, v...) 31 | } 32 | 33 | // Fatal log and exit. 34 | func Fatal(v ...any) { 35 | stdlog.Fatal(v...) 36 | } 37 | 38 | // Fatalf log and exit. 39 | func Fatalf(f string, v ...any) { 40 | stdlog.Fatalf(f, v...) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/pool/buffer.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "math/bits" 5 | "sync" 6 | ) 7 | 8 | const ( 9 | // number of pools. 10 | num = 17 11 | maxsize = 1 << (num - 1) 12 | ) 13 | 14 | var ( 15 | sizes [num]int 16 | pools [num]sync.Pool 17 | ) 18 | 19 | func init() { 20 | for i := 0; i < num; i++ { 21 | size := 1 << i 22 | sizes[i] = size 23 | pools[i].New = func() any { 24 | return make([]byte, size) 25 | } 26 | } 27 | } 28 | 29 | // GetBuffer gets a buffer from pool, size should in range: [1, 65536], 30 | // otherwise, this function will call make([]byte, size) directly. 31 | func GetBuffer(size int) []byte { 32 | if size >= 1 && size <= maxsize { 33 | i := bits.Len32(uint32(size)) - 1 34 | if sizes[i] < size { 35 | i += 1 36 | } 37 | return pools[i].Get().([]byte)[:size] 38 | } 39 | return make([]byte, size) 40 | } 41 | 42 | // PutBuffer puts a buffer into pool. 43 | func PutBuffer(buf []byte) { 44 | if size := cap(buf); size >= 1 && size <= maxsize { 45 | i := bits.Len32(uint32(size)) - 1 46 | if sizes[i] == size { 47 | pools[i].Put(buf) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/pool/bufreader.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | var bufReaderPool sync.Pool 10 | 11 | // GetBufReader returns a *bufio.Reader from pool. 12 | func GetBufReader(r io.Reader) *bufio.Reader { 13 | if v := bufReaderPool.Get(); v != nil { 14 | br := v.(*bufio.Reader) 15 | br.Reset(r) 16 | return br 17 | } 18 | return bufio.NewReader(r) 19 | } 20 | 21 | // PutBufReader puts a *bufio.Reader into pool. 22 | func PutBufReader(br *bufio.Reader) { 23 | bufReaderPool.Put(br) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/pool/bytesbuffer.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | ) 7 | 8 | var bytesBufPool = sync.Pool{ 9 | New: func() any { return &bytes.Buffer{} }, 10 | } 11 | 12 | // GetBytesBuffer returns a bytes.buffer from pool. 13 | func GetBytesBuffer() *bytes.Buffer { 14 | return bytesBufPool.Get().(*bytes.Buffer) 15 | } 16 | 17 | // PutBytesBuffer puts a bytes.buffer into pool. 18 | func PutBytesBuffer(buf *bytes.Buffer) { 19 | if buf.Cap() <= 64<<10 { 20 | buf.Reset() 21 | bytesBufPool.Put(buf) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/smux/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2017 Daniel Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/smux/frame.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | const ( // cmds 9 | // protocol version 1: 10 | cmdSYN byte = iota // stream open 11 | cmdFIN // stream close, a.k.a EOF mark 12 | cmdPSH // data push 13 | cmdNOP // no operation 14 | 15 | // protocol version 2 extra commands 16 | // notify bytes consumed by remote peer-end 17 | cmdUPD 18 | ) 19 | 20 | const ( 21 | // data size of cmdUPD, format: 22 | // |4B data consumed(ACK)| 4B window size(WINDOW) | 23 | szCmdUPD = 8 24 | ) 25 | 26 | const ( 27 | // initial peer window guess, a slow-start 28 | initialPeerWindow = 262144 29 | ) 30 | 31 | const ( 32 | sizeOfVer = 1 33 | sizeOfCmd = 1 34 | sizeOfLength = 2 35 | sizeOfSid = 4 36 | headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength 37 | ) 38 | 39 | // Frame defines a packet from or to be multiplexed into a single connection 40 | type Frame struct { 41 | ver byte 42 | cmd byte 43 | sid uint32 44 | data []byte 45 | } 46 | 47 | func newFrame(version byte, cmd byte, sid uint32) Frame { 48 | return Frame{ver: version, cmd: cmd, sid: sid} 49 | } 50 | 51 | type rawHeader [headerSize]byte 52 | 53 | func (h rawHeader) Version() byte { 54 | return h[0] 55 | } 56 | 57 | func (h rawHeader) Cmd() byte { 58 | return h[1] 59 | } 60 | 61 | func (h rawHeader) Length() uint16 { 62 | return binary.LittleEndian.Uint16(h[2:]) 63 | } 64 | 65 | func (h rawHeader) StreamID() uint32 { 66 | return binary.LittleEndian.Uint32(h[4:]) 67 | } 68 | 69 | func (h rawHeader) String() string { 70 | return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d", 71 | h.Version(), h.Cmd(), h.StreamID(), h.Length()) 72 | } 73 | 74 | type updHeader [szCmdUPD]byte 75 | 76 | func (h updHeader) Consumed() uint32 { 77 | return binary.LittleEndian.Uint32(h[:]) 78 | } 79 | func (h updHeader) Window() uint32 { 80 | return binary.LittleEndian.Uint32(h[4:]) 81 | } 82 | -------------------------------------------------------------------------------- /pkg/smux/mux.go: -------------------------------------------------------------------------------- 1 | // Package smux is a multiplexing library for Golang. 2 | // 3 | // It relies on an underlying connection to provide reliability and ordering, such as TCP or KCP, 4 | // and provides stream-oriented multiplexing over a single channel. 5 | package smux 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | "math" 12 | "time" 13 | ) 14 | 15 | // Config is used to tune the Smux session 16 | type Config struct { 17 | // SMUX Protocol version, support 1,2 18 | Version int 19 | 20 | // Disabled keepalive 21 | KeepAliveDisabled bool 22 | 23 | // KeepAliveInterval is how often to send a NOP command to the remote 24 | KeepAliveInterval time.Duration 25 | 26 | // KeepAliveTimeout is how long the session 27 | // will be closed if no data has arrived 28 | KeepAliveTimeout time.Duration 29 | 30 | // MaxFrameSize is used to control the maximum 31 | // frame size to sent to the remote 32 | MaxFrameSize int 33 | 34 | // MaxReceiveBuffer is used to control the maximum 35 | // number of data in the buffer pool 36 | MaxReceiveBuffer int 37 | 38 | // MaxStreamBuffer is used to control the maximum 39 | // number of data per stream 40 | MaxStreamBuffer int 41 | } 42 | 43 | // DefaultConfig is used to return a default configuration 44 | func DefaultConfig() *Config { 45 | return &Config{ 46 | Version: 1, 47 | KeepAliveInterval: 10 * time.Second, 48 | KeepAliveTimeout: 30 * time.Second, 49 | MaxFrameSize: 32768, 50 | MaxReceiveBuffer: 4194304, 51 | MaxStreamBuffer: 65536, 52 | } 53 | } 54 | 55 | // VerifyConfig is used to verify the sanity of configuration 56 | func VerifyConfig(config *Config) error { 57 | if !(config.Version == 1 || config.Version == 2) { 58 | return errors.New("unsupported protocol version") 59 | } 60 | if !config.KeepAliveDisabled { 61 | if config.KeepAliveInterval == 0 { 62 | return errors.New("keep-alive interval must be positive") 63 | } 64 | if config.KeepAliveTimeout < config.KeepAliveInterval { 65 | return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval") 66 | } 67 | } 68 | if config.MaxFrameSize <= 0 { 69 | return errors.New("max frame size must be positive") 70 | } 71 | if config.MaxFrameSize > 65535 { 72 | return errors.New("max frame size must not be larger than 65535") 73 | } 74 | if config.MaxReceiveBuffer <= 0 { 75 | return errors.New("max receive buffer must be positive") 76 | } 77 | if config.MaxStreamBuffer <= 0 { 78 | return errors.New("max stream buffer must be positive") 79 | } 80 | if config.MaxStreamBuffer > config.MaxReceiveBuffer { 81 | return errors.New("max stream buffer must not be larger than max receive buffer") 82 | } 83 | if config.MaxStreamBuffer > math.MaxInt32 { 84 | return errors.New("max stream buffer cannot be larger than 2147483647") 85 | } 86 | return nil 87 | } 88 | 89 | // Server is used to initialize a new server-side connection. 90 | func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) { 91 | if config == nil { 92 | config = DefaultConfig() 93 | } 94 | if err := VerifyConfig(config); err != nil { 95 | return nil, err 96 | } 97 | return newSession(config, conn, false), nil 98 | } 99 | 100 | // Client is used to initialize a new client-side connection. 101 | func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) { 102 | if config == nil { 103 | config = DefaultConfig() 104 | } 105 | 106 | if err := VerifyConfig(config); err != nil { 107 | return nil, err 108 | } 109 | return newSession(config, conn, true), nil 110 | } 111 | -------------------------------------------------------------------------------- /pkg/smux/mux_test.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | type buffer struct { 9 | bytes.Buffer 10 | } 11 | 12 | func (b *buffer) Close() error { 13 | b.Buffer.Reset() 14 | return nil 15 | } 16 | 17 | func TestConfig(t *testing.T) { 18 | VerifyConfig(DefaultConfig()) 19 | 20 | config := DefaultConfig() 21 | config.KeepAliveInterval = 0 22 | err := VerifyConfig(config) 23 | t.Log(err) 24 | if err == nil { 25 | t.Fatal(err) 26 | } 27 | 28 | config = DefaultConfig() 29 | config.KeepAliveInterval = 10 30 | config.KeepAliveTimeout = 5 31 | err = VerifyConfig(config) 32 | t.Log(err) 33 | if err == nil { 34 | t.Fatal(err) 35 | } 36 | 37 | config = DefaultConfig() 38 | config.MaxFrameSize = 0 39 | err = VerifyConfig(config) 40 | t.Log(err) 41 | if err == nil { 42 | t.Fatal(err) 43 | } 44 | 45 | config = DefaultConfig() 46 | config.MaxFrameSize = 65536 47 | err = VerifyConfig(config) 48 | t.Log(err) 49 | if err == nil { 50 | t.Fatal(err) 51 | } 52 | 53 | config = DefaultConfig() 54 | config.MaxReceiveBuffer = 0 55 | err = VerifyConfig(config) 56 | t.Log(err) 57 | if err == nil { 58 | t.Fatal(err) 59 | } 60 | 61 | config = DefaultConfig() 62 | config.MaxStreamBuffer = 0 63 | err = VerifyConfig(config) 64 | t.Log(err) 65 | if err == nil { 66 | t.Fatal(err) 67 | } 68 | 69 | config = DefaultConfig() 70 | config.MaxStreamBuffer = 100 71 | config.MaxReceiveBuffer = 99 72 | err = VerifyConfig(config) 73 | t.Log(err) 74 | if err == nil { 75 | t.Fatal(err) 76 | } 77 | 78 | var bts buffer 79 | if _, err := Server(&bts, config); err == nil { 80 | t.Fatal("server started with wrong config") 81 | } 82 | 83 | if _, err := Client(&bts, config); err == nil { 84 | t.Fatal("client started with wrong config") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/smux/shaper.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | func _itimediff(later, earlier uint32) int32 { 4 | return (int32)(later - earlier) 5 | } 6 | 7 | type shaperHeap []writeRequest 8 | 9 | func (h shaperHeap) Len() int { return len(h) } 10 | func (h shaperHeap) Less(i, j int) bool { 11 | if h[i].class != h[j].class { 12 | return h[i].class < h[j].class 13 | } 14 | return _itimediff(h[j].seq, h[i].seq) > 0 15 | } 16 | 17 | func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 18 | func (h *shaperHeap) Push(x interface{}) { *h = append(*h, x.(writeRequest)) } 19 | 20 | func (h *shaperHeap) Pop() interface{} { 21 | old := *h 22 | n := len(old) 23 | x := old[n-1] 24 | *h = old[0 : n-1] 25 | return x 26 | } 27 | -------------------------------------------------------------------------------- /pkg/smux/shaper_test.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | import ( 4 | "container/heap" 5 | "testing" 6 | ) 7 | 8 | func TestShaper(t *testing.T) { 9 | w1 := writeRequest{seq: 1} 10 | w2 := writeRequest{seq: 2} 11 | w3 := writeRequest{seq: 3} 12 | w4 := writeRequest{seq: 4} 13 | w5 := writeRequest{seq: 5} 14 | 15 | var reqs shaperHeap 16 | heap.Push(&reqs, w5) 17 | heap.Push(&reqs, w4) 18 | heap.Push(&reqs, w3) 19 | heap.Push(&reqs, w2) 20 | heap.Push(&reqs, w1) 21 | 22 | for len(reqs) > 0 { 23 | w := heap.Pop(&reqs).(writeRequest) 24 | t.Log("sid:", w.frame.sid, "seq:", w.seq) 25 | } 26 | } 27 | 28 | func TestShaper2(t *testing.T) { 29 | w1 := writeRequest{class: CLSDATA, seq: 1} // stream 0 30 | w2 := writeRequest{class: CLSDATA, seq: 2} 31 | w3 := writeRequest{class: CLSDATA, seq: 3} 32 | w4 := writeRequest{class: CLSDATA, seq: 4} 33 | w5 := writeRequest{class: CLSDATA, seq: 5} 34 | w6 := writeRequest{class: CLSCTRL, seq: 6, frame: Frame{sid: 10}} // ctrl 1 35 | w7 := writeRequest{class: CLSCTRL, seq: 7, frame: Frame{sid: 11}} // ctrl 2 36 | 37 | var reqs shaperHeap 38 | heap.Push(&reqs, w6) 39 | heap.Push(&reqs, w5) 40 | heap.Push(&reqs, w4) 41 | heap.Push(&reqs, w3) 42 | heap.Push(&reqs, w2) 43 | heap.Push(&reqs, w1) 44 | heap.Push(&reqs, w7) 45 | 46 | for len(reqs) > 0 { 47 | w := heap.Pop(&reqs).(writeRequest) 48 | t.Log("sid:", w.frame.sid, "seq:", w.seq) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/sockopt/sockopt.go: -------------------------------------------------------------------------------- 1 | package sockopt 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | ) 7 | 8 | // Options is the options struct. 9 | type Options struct { 10 | bindIface *net.Interface 11 | reuseAddr bool 12 | } 13 | 14 | // Option is the function paramater. 15 | type Option func(opts *Options) 16 | 17 | // Bind sets the bind interface option. 18 | func Bind(intf *net.Interface) Option { return func(opts *Options) { opts.bindIface = intf } } 19 | 20 | // ReuseAddr sets the reuse addr option. 21 | func ReuseAddr() Option { return func(opts *Options) { opts.reuseAddr = true } } 22 | 23 | // Control returns a control function for the net.Dialer and net.ListenConfig. 24 | func Control(opts ...Option) func(network, address string, c syscall.RawConn) error { 25 | option := &Options{} 26 | for _, opt := range opts { 27 | opt(option) 28 | } 29 | 30 | return control(option) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/sockopt/sockopt_darwin.go: -------------------------------------------------------------------------------- 1 | package sockopt 2 | 3 | import ( 4 | "syscall" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | func control(opt *Options) func(network, address string, c syscall.RawConn) error { 10 | return func(network, address string, c syscall.RawConn) (err error) { 11 | return c.Control(func(fd uintptr) { 12 | 13 | if opt.bindIface != nil { 14 | switch network { 15 | case "tcp4", "udp4": 16 | unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, opt.bindIface.Index) 17 | case "tcp6", "udp6": 18 | unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, opt.bindIface.Index) 19 | } 20 | } 21 | if opt.reuseAddr { 22 | unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) 23 | unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 24 | } 25 | 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/sockopt/sockopt_linux.go: -------------------------------------------------------------------------------- 1 | package sockopt 2 | 3 | import ( 4 | "syscall" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | func control(opt *Options) func(network, address string, c syscall.RawConn) error { 10 | return func(network, address string, c syscall.RawConn) (err error) { 11 | return c.Control(func(fd uintptr) { 12 | 13 | if opt.bindIface != nil { 14 | unix.BindToDevice(int(fd), opt.bindIface.Name) 15 | } 16 | if opt.reuseAddr { 17 | unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) 18 | unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) 19 | } 20 | 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/sockopt/sockopt_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin 2 | 3 | package sockopt 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func control(opt *Options) func(string, string, syscall.RawConn) error { return nil } 10 | -------------------------------------------------------------------------------- /pkg/socks/socks.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net" 7 | "net/netip" 8 | "strconv" 9 | ) 10 | 11 | // SOCKS auth type 12 | const ( 13 | AuthNone = 0 14 | AuthPassword = 2 15 | ) 16 | 17 | // SOCKS request commands as defined in RFC 1928 section 4 18 | const ( 19 | CmdError byte = 0 20 | CmdConnect byte = 1 21 | CmdBind byte = 2 22 | CmdUDPAssociate byte = 3 23 | ) 24 | 25 | // SOCKS address types as defined in RFC 1928 section 5 26 | const ( 27 | ATypIP4 = 1 28 | ATypDomain = 3 29 | ATypIP6 = 4 30 | ) 31 | 32 | // MaxAddrLen is the maximum size of SOCKS address in bytes 33 | const MaxAddrLen = 1 + 1 + 255 + 2 34 | 35 | // Errors are socks5 errors 36 | var Errors = []error{ 37 | errors.New(""), 38 | errors.New("general failure"), 39 | errors.New("connection forbidden"), 40 | errors.New("network unreachable"), 41 | errors.New("host unreachable"), 42 | errors.New("connection refused"), 43 | errors.New("TTL expired"), 44 | errors.New("command not supported"), 45 | errors.New("address type not supported"), 46 | errors.New("socks5UDPAssociate"), 47 | } 48 | 49 | // Addr represents a SOCKS address as defined in RFC 1928 section 5. 50 | type Addr []byte 51 | 52 | // String serializes SOCKS address a to string form. 53 | func (a Addr) String() string { 54 | var host, port string 55 | 56 | switch a[0] { // address type 57 | case ATypDomain: 58 | host = string(a[2 : 2+int(a[1])]) 59 | port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) 60 | case ATypIP4: 61 | host = net.IP(a[1 : 1+net.IPv4len]).String() 62 | port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) 63 | case ATypIP6: 64 | host = net.IP(a[1 : 1+net.IPv6len]).String() 65 | port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1])) 66 | } 67 | 68 | return net.JoinHostPort(host, port) 69 | } 70 | 71 | // Network returns network name. Implements net.Addr interface. 72 | func (a Addr) Network() string { return "socks" } 73 | 74 | // ReadAddr reads just enough bytes from r to get a valid Addr. 75 | func ReadAddr(r io.Reader) (Addr, error) { 76 | b := make([]byte, MaxAddrLen) 77 | _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | switch b[0] { 83 | case ATypDomain: 84 | _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length 85 | if err != nil { 86 | return nil, err 87 | } 88 | _, err = io.ReadFull(r, b[2:2+int(b[1])+2]) 89 | return b[:1+1+int(b[1])+2], err 90 | case ATypIP4: 91 | _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) 92 | return b[:1+net.IPv4len+2], err 93 | case ATypIP6: 94 | _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) 95 | return b[:1+net.IPv6len+2], err 96 | } 97 | 98 | return nil, Errors[8] 99 | } 100 | 101 | // SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. 102 | func SplitAddr(b []byte) Addr { 103 | addrLen := 1 104 | if len(b) < addrLen { 105 | return nil 106 | } 107 | 108 | switch b[0] { 109 | case ATypDomain: 110 | if len(b) < 2 { 111 | return nil 112 | } 113 | addrLen = 1 + 1 + int(b[1]) + 2 114 | case ATypIP4: 115 | addrLen = 1 + net.IPv4len + 2 116 | case ATypIP6: 117 | addrLen = 1 + net.IPv6len + 2 118 | default: 119 | return nil 120 | } 121 | 122 | if len(b) < addrLen { 123 | return nil 124 | } 125 | 126 | return b[:addrLen] 127 | } 128 | 129 | // ParseAddr parses the address in string s. Returns nil if failed. 130 | func ParseAddr(s string) Addr { 131 | var addr Addr 132 | host, port, err := net.SplitHostPort(s) 133 | if err != nil { 134 | return nil 135 | } 136 | 137 | if ip, err := netip.ParseAddr(host); err == nil { 138 | if ip.Is4() { 139 | addr = make([]byte, 1+net.IPv4len+2) 140 | addr[0] = ATypIP4 141 | } else { 142 | addr = make([]byte, 1+net.IPv6len+2) 143 | addr[0] = ATypIP6 144 | } 145 | copy(addr[1:], ip.AsSlice()) 146 | } else { 147 | if len(host) > 255 { 148 | return nil 149 | } 150 | addr = make([]byte, 1+1+len(host)+2) 151 | addr[0] = ATypDomain 152 | addr[1] = byte(len(host)) 153 | copy(addr[2:], host) 154 | } 155 | 156 | portnum, err := strconv.ParseUint(port, 10, 16) 157 | if err != nil { 158 | return nil 159 | } 160 | 161 | addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum) 162 | 163 | return addr 164 | } 165 | -------------------------------------------------------------------------------- /proxy/dialer.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | // ErrNotSupported indicates that the operation is not supported 12 | ErrNotSupported = errors.New("not supported") 13 | ) 14 | 15 | // Dialer is used to create connection. 16 | type Dialer interface { 17 | TCPDialer 18 | UDPDialer 19 | } 20 | 21 | // TCPDialer is used to create tcp connection. 22 | type TCPDialer interface { 23 | // Addr is the dialer's addr 24 | Addr() string 25 | 26 | // Dial connects to the given address 27 | Dial(network, addr string) (c net.Conn, err error) 28 | } 29 | 30 | // UDPDialer is used to create udp PacketConn. 31 | type UDPDialer interface { 32 | // Addr is the dialer's addr 33 | Addr() string 34 | 35 | // DialUDP connects to the given address 36 | DialUDP(network, addr string) (pc net.PacketConn, err error) 37 | } 38 | 39 | // DialerCreator is a function to create dialers. 40 | type DialerCreator func(s string, dialer Dialer) (Dialer, error) 41 | 42 | var ( 43 | dialerCreators = make(map[string]DialerCreator) 44 | ) 45 | 46 | // RegisterDialer is used to register a dialer. 47 | func RegisterDialer(name string, c DialerCreator) { 48 | dialerCreators[strings.ToLower(name)] = c 49 | } 50 | 51 | // DialerFromURL calls the registered creator to create dialers. 52 | // dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function. 53 | func DialerFromURL(s string, dialer Dialer) (Dialer, error) { 54 | if dialer == nil { 55 | return nil, errors.New("DialerFromURL: dialer cannot be nil") 56 | } 57 | 58 | if !strings.Contains(s, "://") { 59 | s = s + "://" 60 | } 61 | 62 | scheme := s[:strings.Index(s, ":")] 63 | c, ok := dialerCreators[strings.ToLower(scheme)] 64 | if ok { 65 | return c(s, dialer) 66 | } 67 | 68 | return nil, errors.New("unknown scheme '" + scheme + "'") 69 | } 70 | 71 | // DialerSchemes returns the registered dialer schemes. 72 | func DialerSchemes() string { 73 | s := make([]string, 0, len(dialerCreators)) 74 | for name := range dialerCreators { 75 | s = append(s, name) 76 | } 77 | sort.Strings(s) 78 | return strings.Join(s, " ") 79 | } 80 | -------------------------------------------------------------------------------- /proxy/direct.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | "net/netip" 8 | "time" 9 | 10 | "github.com/meoww-bot/glider/pkg/sockopt" 11 | ) 12 | 13 | // Direct proxy. 14 | type Direct struct { 15 | iface *net.Interface // interface specified by user 16 | ip net.IP 17 | dialTimeout time.Duration 18 | relayTimeout time.Duration 19 | } 20 | 21 | func init() { 22 | RegisterDialer("direct", NewDirectDialer) 23 | } 24 | 25 | // NewDirect returns a Direct dialer. 26 | func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) { 27 | d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout} 28 | 29 | if intface != "" { 30 | if addr, err := netip.ParseAddr(intface); err == nil { 31 | d.ip = addr.AsSlice() 32 | } else { 33 | iface, err := net.InterfaceByName(intface) 34 | if err != nil { 35 | return nil, errors.New(err.Error() + ": " + intface) 36 | } 37 | d.iface = iface 38 | } 39 | } 40 | 41 | return d, nil 42 | } 43 | 44 | // NewDirectDialer returns a direct dialer. 45 | func NewDirectDialer(s string, d Dialer) (Dialer, error) { 46 | if d == nil { 47 | return NewDirect("", time.Duration(3)*time.Second, time.Duration(3)*time.Second) 48 | } 49 | return d, nil 50 | } 51 | 52 | // Addr returns forwarder's address. 53 | func (d *Direct) Addr() string { return "DIRECT" } 54 | 55 | // Dial connects to the address addr on the network net 56 | func (d *Direct) Dial(network, addr string) (c net.Conn, err error) { 57 | if d.iface == nil || d.ip != nil { 58 | c, err = d.dial(network, addr, d.ip) 59 | if err == nil { 60 | return 61 | } 62 | } 63 | 64 | for _, ip := range d.IFaceIPs() { 65 | c, err = d.dial(network, addr, ip) 66 | if err == nil { 67 | d.ip = ip 68 | break 69 | } 70 | } 71 | 72 | // no ip available (so no dials made), maybe the interface link is down 73 | if c == nil && err == nil { 74 | err = errors.New("dial failed, maybe the interface link is down, please check it") 75 | } 76 | 77 | return c, err 78 | } 79 | 80 | func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) { 81 | var la net.Addr 82 | switch network { 83 | case "tcp": 84 | la = &net.TCPAddr{IP: localIP} 85 | case "udp": 86 | la = &net.UDPAddr{IP: localIP} 87 | } 88 | 89 | dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout} 90 | if d.iface != nil { 91 | dialer.Control = sockopt.Control(sockopt.Bind(d.iface)) 92 | } 93 | 94 | c, err := dialer.Dial(network, addr) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | if c, ok := c.(*net.TCPConn); ok { 100 | c.SetKeepAlive(true) 101 | } 102 | 103 | if d.relayTimeout > 0 { 104 | c.SetDeadline(time.Now().Add(d.relayTimeout)) 105 | } 106 | 107 | return c, err 108 | } 109 | 110 | // DialUDP connects to the given address. 111 | func (d *Direct) DialUDP(network, addr string) (net.PacketConn, error) { 112 | var la string 113 | if d.ip != nil { 114 | la = net.JoinHostPort(d.ip.String(), "0") 115 | } 116 | 117 | lc := &net.ListenConfig{} 118 | if d.iface != nil { 119 | lc.Control = sockopt.Control(sockopt.Bind(d.iface)) 120 | } 121 | 122 | return lc.ListenPacket(context.Background(), network, la) 123 | } 124 | 125 | // IFaceIPs returns ip addresses according to the specified interface. 126 | func (d *Direct) IFaceIPs() (ips []net.IP) { 127 | ipNets, err := d.iface.Addrs() 128 | if err != nil { 129 | return 130 | } 131 | for _, ipNet := range ipNets { 132 | ips = append(ips, ipNet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast() 133 | } 134 | return 135 | } 136 | 137 | func init() { 138 | AddUsage("direct", ` 139 | Direct scheme: 140 | direct:// 141 | 142 | Only needed when you want to load balance multiple interfaces directly: 143 | glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr 144 | 145 | Or you can use the high availability mode: 146 | glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha 147 | `) 148 | } 149 | -------------------------------------------------------------------------------- /proxy/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "net" 7 | "net/textproto" 8 | 9 | "github.com/meoww-bot/glider/pkg/log" 10 | "github.com/meoww-bot/glider/pkg/pool" 11 | "github.com/meoww-bot/glider/proxy" 12 | ) 13 | 14 | // NewHTTPDialer returns a http proxy dialer. 15 | func NewHTTPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 16 | return NewHTTP(s, d, nil) 17 | } 18 | 19 | // Addr returns forwarder's address. 20 | func (s *HTTP) Addr() string { 21 | if s.addr == "" { 22 | return s.dialer.Addr() 23 | } 24 | return s.addr 25 | } 26 | 27 | // Dial connects to the address addr on the network net via the proxy. 28 | func (s *HTTP) Dial(network, addr string) (net.Conn, error) { 29 | rc, err := s.dialer.Dial(network, s.addr) 30 | if err != nil { 31 | log.F("[http] dial to %s error: %s", s.addr, err) 32 | return nil, err 33 | } 34 | 35 | buf := pool.GetBytesBuffer() 36 | buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") 37 | buf.WriteString("Host: " + addr + "\r\n") 38 | buf.WriteString("Proxy-Connection: Keep-Alive\r\n") 39 | 40 | if s.user != "" && s.password != "" { 41 | auth := s.user + ":" + s.password 42 | buf.WriteString("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n") 43 | } 44 | 45 | // header ended 46 | buf.WriteString("\r\n") 47 | _, err = rc.Write(buf.Bytes()) 48 | pool.PutBytesBuffer(buf) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | c := proxy.NewConn(rc) 54 | tpr := textproto.NewReader(c.Reader()) 55 | line, err := tpr.ReadLine() 56 | if err != nil { 57 | return c, err 58 | } 59 | 60 | _, code, _, ok := parseStartLine(line) 61 | if ok && code == "200" { 62 | tpr.ReadMIMEHeader() 63 | return c, err 64 | } 65 | 66 | switch code { 67 | case "403": 68 | log.F("[http] 'CONNECT' to ports other than 443 are not allowed by proxy %s", s.addr) 69 | case "405": 70 | log.F("[http] 'CONNECT' method not allowed by proxy %s", s.addr) 71 | case "407": 72 | log.F("[http] authencation needed by proxy %s", s.addr) 73 | } 74 | 75 | return nil, errors.New("[http] can not connect remote address: " + addr + ". error code: " + code) 76 | } 77 | 78 | // DialUDP connects to the given address via the proxy. 79 | func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, err error) { 80 | return nil, proxy.ErrNotSupported 81 | } 82 | -------------------------------------------------------------------------------- /proxy/http/http.go: -------------------------------------------------------------------------------- 1 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages 2 | // NOTE: never keep-alive so the implementation can be much easier. 3 | 4 | // Package http implements a http proxy. 5 | package http 6 | 7 | import ( 8 | "encoding/base64" 9 | "io" 10 | "net/textproto" 11 | "net/url" 12 | "strings" 13 | 14 | "github.com/meoww-bot/glider/pkg/log" 15 | "github.com/meoww-bot/glider/proxy" 16 | ) 17 | 18 | // HTTP struct. 19 | type HTTP struct { 20 | dialer proxy.Dialer 21 | proxy proxy.Proxy 22 | addr string 23 | user string 24 | password string 25 | pretend bool 26 | } 27 | 28 | func init() { 29 | proxy.RegisterDialer("http", NewHTTPDialer) 30 | proxy.RegisterServer("http", NewHTTPServer) 31 | } 32 | 33 | // NewHTTP returns a http proxy. 34 | func NewHTTP(s string, d proxy.Dialer, p proxy.Proxy) (*HTTP, error) { 35 | u, err := url.Parse(s) 36 | if err != nil { 37 | log.F("parse err: %s", err) 38 | return nil, err 39 | } 40 | 41 | addr := u.Host 42 | user := u.User.Username() 43 | pass, _ := u.User.Password() 44 | 45 | h := &HTTP{ 46 | dialer: d, 47 | proxy: p, 48 | addr: addr, 49 | user: user, 50 | password: pass, 51 | pretend: false, 52 | } 53 | 54 | if u.Query().Get("pretend") == "true" { 55 | h.pretend = true 56 | } 57 | 58 | return h, nil 59 | } 60 | 61 | // parseStartLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. 62 | func parseStartLine(line string) (r1, r2, r3 string, ok bool) { 63 | s1 := strings.Index(line, " ") 64 | s2 := strings.Index(line[s1+1:], " ") 65 | if s1 < 0 || s2 < 0 { 66 | return 67 | } 68 | s2 += s1 + 1 69 | return line[:s1], line[s1+1 : s2], line[s2+1:], true 70 | } 71 | 72 | func cleanHeaders(header textproto.MIMEHeader) { 73 | header.Del("Proxy-Connection") 74 | header.Del("Connection") 75 | header.Del("Keep-Alive") 76 | header.Del("Proxy-Authenticate") 77 | header.Del("Proxy-Authorization") 78 | header.Del("TE") 79 | header.Del("Trailers") 80 | header.Del("Transfer-Encoding") 81 | header.Del("Upgrade") 82 | } 83 | 84 | func writeStartLine(w io.Writer, s1, s2, s3 string) { 85 | io.WriteString(w, s1+" "+s2+" "+s3+"\r\n") 86 | } 87 | 88 | func writeHeaders(w io.Writer, header textproto.MIMEHeader) { 89 | for key, values := range header { 90 | for _, v := range values { 91 | io.WriteString(w, key+": "+v+"\r\n") 92 | } 93 | } 94 | io.WriteString(w, "\r\n") 95 | } 96 | 97 | func extractUserPass(auth string) (username, password string, ok bool) { 98 | if !strings.HasPrefix(auth, "Basic ") { 99 | return 100 | } 101 | 102 | b, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) 103 | if err != nil { 104 | return 105 | } 106 | 107 | s := string(b) 108 | idx := strings.IndexByte(s, ':') 109 | if idx < 0 { 110 | return 111 | } 112 | 113 | return s[:idx], s[idx+1:], true 114 | } 115 | 116 | func init() { 117 | proxy.AddUsage("http", ` 118 | Http scheme: 119 | http://[user:pass@]host:port 120 | `) 121 | } 122 | -------------------------------------------------------------------------------- /proxy/http/request.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "net/textproto" 8 | "net/url" 9 | "strings" 10 | 11 | "github.com/meoww-bot/glider/pkg/log" 12 | ) 13 | 14 | // Methods are http methods from rfc. 15 | // https://www.rfc-editor.org/rfc/rfc2616, http methods must be uppercase 16 | var Methods = [...][]byte{ 17 | []byte("GET"), 18 | []byte("POST"), 19 | []byte("PUT"), 20 | []byte("DELETE"), 21 | []byte("CONNECT"), 22 | []byte("HEAD"), 23 | []byte("OPTIONS"), 24 | []byte("TRACE"), 25 | []byte("PATCH"), 26 | } 27 | 28 | type request struct { 29 | method string 30 | uri string 31 | proto string 32 | auth string 33 | header textproto.MIMEHeader 34 | 35 | target string // target host with port 36 | ruri string // relative uri 37 | absuri string // absolute uri 38 | } 39 | 40 | func parseRequest(r *bufio.Reader) (*request, error) { 41 | tpr := textproto.NewReader(r) 42 | line, err := tpr.ReadLine() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | method, uri, proto, ok := parseStartLine(line) 48 | if !ok { 49 | return nil, fmt.Errorf("error in parseStartLine: %s", line) 50 | } 51 | 52 | header, err := tpr.ReadMIMEHeader() 53 | if err != nil { 54 | log.F("[http] read header error:%s", err) 55 | return nil, err 56 | } 57 | 58 | auth := header.Get("Proxy-Authorization") 59 | 60 | cleanHeaders(header) 61 | header.Set("Connection", "close") 62 | 63 | // https://github.com/golang/go/blob/dcf0929de6a12103a8fd7097abd6e797188c366d/src/net/http/request.go#L1047 64 | justAuthority := method == "CONNECT" && !strings.HasPrefix(uri, "/") 65 | if justAuthority { 66 | uri = "http://" + uri 67 | } 68 | 69 | u, err := url.ParseRequestURI(uri) 70 | if err != nil { 71 | log.F("[http] parse request url error: %s, uri: %s", err, uri) 72 | return nil, err 73 | } 74 | 75 | if justAuthority { 76 | // Strip the bogus "http://" back off. 77 | u.Scheme = "" 78 | uri = uri[7:] 79 | } 80 | 81 | tgt := u.Host 82 | if !strings.Contains(tgt, ":") { 83 | tgt += ":80" 84 | } 85 | 86 | req := &request{ 87 | method: method, 88 | uri: uri, 89 | proto: proto, 90 | auth: auth, 91 | header: header, 92 | target: tgt, 93 | } 94 | 95 | if u.IsAbs() { 96 | req.absuri = u.String() 97 | u.Scheme, u.Host = "", "" 98 | req.ruri = u.String() 99 | } else { 100 | req.ruri = u.String() 101 | base, err := url.Parse("http://" + header.Get("Host")) 102 | if err != nil { 103 | return nil, err 104 | } 105 | u = base.ResolveReference(u) 106 | req.absuri = u.String() 107 | } 108 | 109 | return req, nil 110 | } 111 | 112 | func (r *request) WriteBuf(buf *bytes.Buffer) { 113 | writeStartLine(buf, r.method, r.ruri, r.proto) 114 | writeHeaders(buf, r.header) 115 | } 116 | 117 | func (r *request) WriteAbsBuf(buf *bytes.Buffer) { 118 | writeStartLine(buf, r.method, r.absuri, r.proto) 119 | writeHeaders(buf, r.header) 120 | } 121 | -------------------------------------------------------------------------------- /proxy/mixed/mixed.go: -------------------------------------------------------------------------------- 1 | package mixed 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | 7 | "github.com/meoww-bot/glider/pkg/log" 8 | "github.com/meoww-bot/glider/proxy" 9 | "github.com/meoww-bot/glider/proxy/http" 10 | "github.com/meoww-bot/glider/proxy/socks5" 11 | ) 12 | 13 | // Mixed struct. 14 | type Mixed struct { 15 | proxy proxy.Proxy 16 | addr string 17 | 18 | httpServer *http.HTTP 19 | socks5Server *socks5.Socks5 20 | } 21 | 22 | func init() { 23 | proxy.RegisterServer("mixed", NewMixedServer) 24 | } 25 | 26 | // NewMixed returns a mixed proxy. 27 | func NewMixed(s string, p proxy.Proxy) (*Mixed, error) { 28 | u, err := url.Parse(s) 29 | if err != nil { 30 | log.F("parse err: %s", err) 31 | return nil, err 32 | } 33 | 34 | m := &Mixed{ 35 | proxy: p, 36 | addr: u.Host, 37 | } 38 | 39 | m.httpServer, err = http.NewHTTP(s, nil, p) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | m.socks5Server, err = socks5.NewSocks5(s, nil, p) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return m, nil 50 | } 51 | 52 | // NewMixedServer returns a mixed server. 53 | func NewMixedServer(s string, p proxy.Proxy) (proxy.Server, error) { 54 | return NewMixed(s, p) 55 | } 56 | 57 | // ListenAndServe listens on server's addr and serves connections. 58 | func (m *Mixed) ListenAndServe() { 59 | go m.socks5Server.ListenAndServeUDP() 60 | 61 | l, err := net.Listen("tcp", m.addr) 62 | if err != nil { 63 | log.Fatalf("[mixed] failed to listen on %s: %v", m.addr, err) 64 | return 65 | } 66 | 67 | log.F("[mixed] http & socks5 server listening TCP on %s", m.addr) 68 | 69 | for { 70 | c, err := l.Accept() 71 | if err != nil { 72 | log.F("[mixed] failed to accept: %v", err) 73 | continue 74 | } 75 | 76 | go m.Serve(c) 77 | } 78 | } 79 | 80 | // Serve serves connections. 81 | func (m *Mixed) Serve(c net.Conn) { 82 | conn := proxy.NewConn(c) 83 | if head, err := conn.Peek(1); err == nil { 84 | if head[0] == socks5.Version { 85 | m.socks5Server.Serve(conn) 86 | return 87 | } 88 | } 89 | m.httpServer.Serve(conn) 90 | } 91 | -------------------------------------------------------------------------------- /proxy/obfs/http.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "io" 8 | "net" 9 | 10 | "github.com/meoww-bot/glider/pkg/pool" 11 | ) 12 | 13 | // HTTPObfs struct 14 | type HTTPObfs struct { 15 | obfsHost string 16 | obfsURI string 17 | obfsUA string 18 | } 19 | 20 | // NewHTTPObfs returns a HTTPObfs object 21 | func NewHTTPObfs(obfsHost, obfsURI, obfsUA string) *HTTPObfs { 22 | return &HTTPObfs{obfsHost, obfsURI, obfsUA} 23 | } 24 | 25 | // HTTPObfsConn struct 26 | type HTTPObfsConn struct { 27 | *HTTPObfs 28 | 29 | net.Conn 30 | reader io.Reader 31 | } 32 | 33 | // NewConn returns a new obfs connection 34 | func (p *HTTPObfs) NewConn(c net.Conn) (net.Conn, error) { 35 | cc := &HTTPObfsConn{ 36 | Conn: c, 37 | HTTPObfs: p, 38 | } 39 | 40 | // send http header to remote server 41 | _, err := cc.writeHeader() 42 | return cc, err 43 | } 44 | 45 | func (c *HTTPObfsConn) writeHeader() (int, error) { 46 | buf := pool.GetBytesBuffer() 47 | defer pool.PutBytesBuffer(buf) 48 | 49 | buf.WriteString("GET " + c.obfsURI + " HTTP/1.1\r\n") 50 | buf.WriteString("Host: " + c.obfsHost + "\r\n") 51 | buf.WriteString("User-Agent: " + c.obfsUA + "\r\n") 52 | buf.WriteString("Upgrade: websocket\r\n") 53 | buf.WriteString("Connection: Upgrade\r\n") 54 | 55 | b := pool.GetBuffer(16) 56 | rand.Read(b) 57 | buf.WriteString("Sec-WebSocket-Key: " + base64.StdEncoding.EncodeToString(b) + "\r\n") 58 | pool.PutBuffer(b) 59 | 60 | buf.WriteString("\r\n") 61 | 62 | return c.Conn.Write(buf.Bytes()) 63 | } 64 | 65 | func (c *HTTPObfsConn) Read(b []byte) (n int, err error) { 66 | if c.reader == nil { 67 | r := bufio.NewReader(c.Conn) 68 | c.reader = r 69 | for { 70 | l, _, err := r.ReadLine() 71 | if err != nil { 72 | return 0, err 73 | } 74 | 75 | if len(l) == 0 { 76 | break 77 | } 78 | } 79 | } 80 | 81 | return c.reader.Read(b) 82 | } 83 | -------------------------------------------------------------------------------- /proxy/obfs/obfs.go: -------------------------------------------------------------------------------- 1 | // Package obfs implements simple-obfs of ss 2 | package obfs 3 | 4 | import ( 5 | "errors" 6 | "net" 7 | "net/url" 8 | 9 | "github.com/meoww-bot/glider/pkg/log" 10 | "github.com/meoww-bot/glider/proxy" 11 | ) 12 | 13 | // Obfs struct. 14 | type Obfs struct { 15 | dialer proxy.Dialer 16 | addr string 17 | 18 | obfsType string 19 | obfsHost string 20 | obfsURI string 21 | obfsUA string 22 | 23 | obfsConn func(c net.Conn) (net.Conn, error) 24 | } 25 | 26 | func init() { 27 | proxy.RegisterDialer("simple-obfs", NewObfsDialer) 28 | } 29 | 30 | // NewObfs returns a proxy struct. 31 | func NewObfs(s string, d proxy.Dialer) (*Obfs, error) { 32 | u, err := url.Parse(s) 33 | if err != nil { 34 | log.F("parse url err: %s", err) 35 | return nil, err 36 | } 37 | 38 | addr := u.Host 39 | 40 | query := u.Query() 41 | obfsType := query.Get("type") 42 | if obfsType == "" { 43 | obfsType = "http" 44 | } 45 | 46 | obfsHost := query.Get("host") 47 | if obfsHost == "" { 48 | return nil, errors.New("[obfs] host cannot be null") 49 | } 50 | 51 | obfsURI := query.Get("uri") 52 | if obfsURI == "" { 53 | obfsURI = "/" 54 | } 55 | 56 | obfsUA := query.Get("ua") 57 | if obfsUA == "" { 58 | obfsUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" 59 | } 60 | 61 | p := &Obfs{ 62 | dialer: d, 63 | addr: addr, 64 | obfsType: obfsType, 65 | obfsHost: obfsHost, 66 | obfsURI: obfsURI, 67 | obfsUA: obfsUA, 68 | } 69 | 70 | switch obfsType { 71 | case "http": 72 | httpObfs := NewHTTPObfs(obfsHost, obfsURI, obfsUA) 73 | p.obfsConn = httpObfs.NewConn 74 | case "tls": 75 | tlsObfs := NewTLSObfs(obfsHost) 76 | p.obfsConn = tlsObfs.NewConn 77 | default: 78 | return nil, errors.New("[obfs] unknown obfs type: " + obfsType) 79 | } 80 | 81 | return p, nil 82 | } 83 | 84 | // NewObfsDialer returns a proxy dialer. 85 | func NewObfsDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) { 86 | return NewObfs(s, dialer) 87 | } 88 | 89 | // Addr returns forwarder's address. 90 | func (s *Obfs) Addr() string { 91 | if s.addr == "" { 92 | return s.dialer.Addr() 93 | } 94 | return s.addr 95 | } 96 | 97 | // Dial connects to the address addr on the network net via the proxy. 98 | func (s *Obfs) Dial(network, addr string) (net.Conn, error) { 99 | c, err := s.dialer.Dial("tcp", s.addr) 100 | if err != nil { 101 | log.F("[obfs] dial to %s error: %s", s.addr, err) 102 | return nil, err 103 | } 104 | 105 | return s.obfsConn(c) 106 | } 107 | 108 | // DialUDP connects to the given address via the proxy. 109 | func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, error) { 110 | return nil, proxy.ErrNotSupported 111 | } 112 | 113 | func init() { 114 | proxy.AddUsage("simple-obfs", ` 115 | Simple-Obfs scheme: 116 | simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA] 117 | 118 | Available types for simple-obfs: 119 | http, tls 120 | `) 121 | } 122 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | // Proxy is a dialer manager. 9 | type Proxy interface { 10 | // Dial connects to the given address via the proxy. 11 | Dial(network, addr string) (c net.Conn, dialer Dialer, err error) 12 | 13 | // DialUDP connects to the given address via the proxy. 14 | DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, err error) 15 | 16 | // Get the dialer by dstAddr. 17 | NextDialer(dstAddr string) Dialer 18 | 19 | // Record records result while using the dialer from proxy. 20 | Record(dialer Dialer, success bool) 21 | } 22 | 23 | var ( 24 | msg strings.Builder 25 | usages = make(map[string]string) 26 | ) 27 | 28 | // AddUsage adds help message for the named proxy. 29 | func AddUsage(name, usage string) { 30 | usages[name] = usage 31 | msg.WriteString(usage) 32 | msg.WriteString("\n--") 33 | } 34 | 35 | // Usage returns help message of the named proxy. 36 | func Usage(name string) string { 37 | if name == "all" { 38 | return msg.String() 39 | } 40 | 41 | if usage, ok := usages[name]; ok { 42 | return usage 43 | } 44 | 45 | return "can not find usage for: " + name 46 | } 47 | -------------------------------------------------------------------------------- /proxy/pxyproto/server.go: -------------------------------------------------------------------------------- 1 | package pxyproto 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/meoww-bot/glider/pkg/log" 11 | "github.com/meoww-bot/glider/proxy" 12 | ) 13 | 14 | func init() { 15 | proxy.RegisterServer("pxyproto", NewPxyProtoServer) 16 | } 17 | 18 | // PxyProtoServer struct. 19 | type PxyProtoServer struct { 20 | addr string 21 | proxy proxy.Proxy 22 | server proxy.Server 23 | } 24 | 25 | // NewPxyProtoServer returns a PxyProtoServer struct. 26 | func NewPxyProtoServer(s string, p proxy.Proxy) (proxy.Server, error) { 27 | schemes := strings.SplitN(s, ",", 2) 28 | u, err := url.Parse(schemes[0]) 29 | if err != nil { 30 | log.F("[pxyproto] parse url err: %s", err) 31 | return nil, err 32 | } 33 | 34 | t := &PxyProtoServer{proxy: p, addr: u.Host} 35 | if len(schemes) < 2 { 36 | return nil, errors.New("[pxyproto] you must use pxyproto with a proxy server, e.g: pxyproto://:1234,http://") 37 | } 38 | 39 | t.server, err = proxy.ServerFromURL(schemes[1], p) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return t, nil 45 | } 46 | 47 | // ListenAndServe listens on server's addr and serves connections. 48 | func (s *PxyProtoServer) ListenAndServe() { 49 | l, err := net.Listen("tcp", s.addr) 50 | if err != nil { 51 | log.Fatalf("[pxyproto] failed to listen on %s: %v", s.addr, err) 52 | return 53 | } 54 | defer l.Close() 55 | 56 | log.F("[pxyproto] listening TCP on %s", s.addr) 57 | 58 | for { 59 | c, err := l.Accept() 60 | if err != nil { 61 | log.F("[pxyproto] failed to accept: %v", err) 62 | continue 63 | } 64 | 65 | go s.Serve(c) 66 | } 67 | } 68 | 69 | // Serve serves a connection. 70 | func (s *PxyProtoServer) Serve(cc net.Conn) { 71 | c, err := newServerConn(cc) 72 | if err != nil { 73 | log.F("[pxyproto] parse header failed, error: %v", err) 74 | cc.Close() 75 | return 76 | } 77 | 78 | // log.F("[pxyproto] %s <-> %s <-> %s <-> %s", 79 | // c.RemoteAddr(), c.LocalAddr(), cc.RemoteAddr(), cc.LocalAddr()) 80 | 81 | if s.server != nil { 82 | s.server.Serve(c) 83 | return 84 | } 85 | } 86 | 87 | type serverConn struct { 88 | *proxy.Conn 89 | src, dst net.Addr 90 | } 91 | 92 | func newServerConn(c net.Conn) (*serverConn, error) { 93 | sc := &serverConn{ 94 | Conn: proxy.NewConn(c), 95 | src: c.RemoteAddr(), 96 | dst: c.LocalAddr(), 97 | } 98 | return sc, sc.parseHeader() 99 | } 100 | 101 | // "PROXY TCPx SRC_IP DST_IP SRC_PORT DST_PORT" 102 | func (c *serverConn) parseHeader() error { 103 | line, err := c.Conn.Reader().ReadString('\n') 104 | if err != nil { 105 | return err 106 | } 107 | 108 | line = strings.ReplaceAll(line, "\r\n", "") 109 | // log.F("[pxyproto] req header: %s", line) 110 | 111 | header := strings.Split(line, " ") 112 | if len(header) != 6 { 113 | return fmt.Errorf("invalid header: %s", line) 114 | } 115 | 116 | if header[0] != "PROXY" { 117 | return fmt.Errorf("invalid header: %s", line) 118 | } 119 | 120 | c.src, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[2], header[4])) 121 | if err != nil { 122 | return fmt.Errorf("parse header: %s, error: %v", line, err) 123 | } 124 | 125 | c.dst, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[3], header[5])) 126 | if err != nil { 127 | return fmt.Errorf("parse header: %s, error: %v", line, err) 128 | } 129 | 130 | return nil 131 | } 132 | 133 | func (c *serverConn) LocalAddr() net.Addr { return c.dst } 134 | func (c *serverConn) RemoteAddr() net.Addr { return c.src } 135 | -------------------------------------------------------------------------------- /proxy/redir/redir_linux_386.go: -------------------------------------------------------------------------------- 1 | package redir 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | // https://github.com/golang/go/blob/9e6b79a5dfb2f6fe4301ced956419a0da83bd025/src/syscall/syscall_linux_386.go#L196 9 | const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 10 | 11 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 12 | var a [6]uintptr 13 | a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 14 | if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { 15 | return errno 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /proxy/redir/redir_linux_other.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !386 2 | 3 | package redir 4 | 5 | import "syscall" 6 | 7 | // GETSOCKOPT from syscall 8 | const GETSOCKOPT = syscall.SYS_GETSOCKOPT 9 | 10 | func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { 11 | if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { 12 | return errno 13 | } 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /proxy/reject/reject.go: -------------------------------------------------------------------------------- 1 | // Package reject implements a virtual proxy which always reject requests. 2 | package reject 3 | 4 | import ( 5 | "errors" 6 | "net" 7 | 8 | "github.com/meoww-bot/glider/proxy" 9 | ) 10 | 11 | // A Reject represents the base struct of a reject proxy. 12 | type Reject struct{} 13 | 14 | func init() { 15 | proxy.RegisterDialer("reject", NewRejectDialer) 16 | } 17 | 18 | // NewReject returns a reject proxy, reject://. 19 | func NewReject(s string, d proxy.Dialer) (*Reject, error) { 20 | return &Reject{}, nil 21 | } 22 | 23 | // NewRejectDialer returns a reject proxy dialer. 24 | func NewRejectDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 25 | return NewReject(s, d) 26 | } 27 | 28 | // Addr returns forwarder's address. 29 | func (s *Reject) Addr() string { return "REJECT" } 30 | 31 | // Dial connects to the address addr on the network net via the proxy. 32 | func (s *Reject) Dial(network, addr string) (net.Conn, error) { 33 | return nil, errors.New("REJECT") 34 | } 35 | 36 | // DialUDP connects to the given address via the proxy. 37 | func (s *Reject) DialUDP(network, addr string) (net.PacketConn, error) { 38 | return nil, errors.New("REJECT") 39 | } 40 | 41 | func init() { 42 | proxy.AddUsage("reject", ` 43 | Reject scheme: 44 | reject:// 45 | `) 46 | } 47 | -------------------------------------------------------------------------------- /proxy/server.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | // Server interface. 11 | type Server interface { 12 | // ListenAndServe sets up a listener and serve on it 13 | ListenAndServe() 14 | 15 | // Serve serves a connection 16 | Serve(c net.Conn) 17 | } 18 | 19 | // PacketServer interface. 20 | type PacketServer interface { 21 | ServePacket(pc net.PacketConn) 22 | } 23 | 24 | // ServerCreator is a function to create proxy servers. 25 | type ServerCreator func(s string, proxy Proxy) (Server, error) 26 | 27 | var ( 28 | serverCreators = make(map[string]ServerCreator) 29 | ) 30 | 31 | // RegisterServer is used to register a proxy server. 32 | func RegisterServer(name string, c ServerCreator) { 33 | serverCreators[strings.ToLower(name)] = c 34 | } 35 | 36 | // ServerFromURL calls the registered creator to create proxy servers. 37 | // proxy can not be nil. 38 | func ServerFromURL(s string, proxy Proxy) (Server, error) { 39 | if proxy == nil { 40 | return nil, errors.New("ServerFromURL: dialer cannot be nil") 41 | } 42 | 43 | if !strings.Contains(s, "://") { 44 | s = "mixed://" + s 45 | } 46 | 47 | scheme := s[:strings.Index(s, ":")] 48 | c, ok := serverCreators[strings.ToLower(scheme)] 49 | if ok { 50 | return c(s, proxy) 51 | } 52 | 53 | return nil, errors.New("unknown scheme '" + scheme + "'") 54 | } 55 | 56 | // ServerSchemes returns the registered server schemes. 57 | func ServerSchemes() string { 58 | s := make([]string, 0, len(serverCreators)) 59 | for name := range serverCreators { 60 | s = append(s, name) 61 | } 62 | sort.Strings(s) 63 | return strings.Join(s, " ") 64 | } 65 | -------------------------------------------------------------------------------- /proxy/smux/client.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | "sync" 7 | 8 | "github.com/meoww-bot/glider/pkg/log" 9 | "github.com/meoww-bot/glider/pkg/smux" 10 | "github.com/meoww-bot/glider/proxy" 11 | ) 12 | 13 | // SmuxClient struct. 14 | type SmuxClient struct { 15 | dialer proxy.Dialer 16 | addr string 17 | mu sync.Mutex 18 | session *smux.Session 19 | } 20 | 21 | func init() { 22 | proxy.RegisterDialer("smux", NewSmuxDialer) 23 | } 24 | 25 | // NewSmuxDialer returns a smux dialer. 26 | func NewSmuxDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 27 | u, err := url.Parse(s) 28 | if err != nil { 29 | log.F("[smux] parse url err: %s", err) 30 | return nil, err 31 | } 32 | 33 | c := &SmuxClient{ 34 | dialer: d, 35 | addr: u.Host, 36 | } 37 | 38 | return c, nil 39 | } 40 | 41 | // Addr returns forwarder's address. 42 | func (s *SmuxClient) Addr() string { 43 | if s.addr == "" { 44 | return s.dialer.Addr() 45 | } 46 | return s.addr 47 | } 48 | 49 | // Dial connects to the address addr on the network net via the proxy. 50 | func (s *SmuxClient) Dial(network, addr string) (net.Conn, error) { 51 | s.mu.Lock() 52 | defer s.mu.Unlock() 53 | 54 | if s.session != nil { 55 | if c, err := s.session.OpenStream(); err == nil { 56 | return c, err 57 | } 58 | s.session.Close() 59 | } 60 | if err := s.initConn(); err != nil { 61 | return nil, err 62 | } 63 | return s.session.OpenStream() 64 | } 65 | 66 | // DialUDP connects to the given address via the proxy. 67 | func (s *SmuxClient) DialUDP(network, addr string) (net.PacketConn, error) { 68 | return nil, proxy.ErrNotSupported 69 | } 70 | 71 | func (s *SmuxClient) initConn() error { 72 | conn, err := s.dialer.Dial("tcp", s.addr) 73 | if err != nil { 74 | log.F("[smux] dial to %s error: %s", s.addr, err) 75 | return err 76 | } 77 | s.session, err = smux.Client(conn, nil) 78 | return err 79 | } 80 | -------------------------------------------------------------------------------- /proxy/smux/server.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/meoww-bot/glider/pkg/log" 9 | "github.com/meoww-bot/glider/pkg/smux" 10 | "github.com/meoww-bot/glider/proxy" 11 | ) 12 | 13 | // SmuxServer struct. 14 | type SmuxServer struct { 15 | proxy proxy.Proxy 16 | addr string 17 | server proxy.Server 18 | } 19 | 20 | func init() { 21 | proxy.RegisterServer("smux", NewSmuxServer) 22 | } 23 | 24 | // NewSmuxServer returns a smux transport layer before the real server. 25 | func NewSmuxServer(s string, p proxy.Proxy) (proxy.Server, error) { 26 | schemes := strings.SplitN(s, ",", 2) 27 | u, err := url.Parse(schemes[0]) 28 | if err != nil { 29 | log.F("[smux] parse url err: %s", err) 30 | return nil, err 31 | } 32 | 33 | m := &SmuxServer{ 34 | proxy: p, 35 | addr: u.Host, 36 | } 37 | 38 | if len(schemes) > 1 { 39 | m.server, err = proxy.ServerFromURL(schemes[1], p) 40 | if err != nil { 41 | return nil, err 42 | } 43 | } 44 | 45 | return m, nil 46 | } 47 | 48 | // ListenAndServe listens on server's addr and serves connections. 49 | func (s *SmuxServer) ListenAndServe() { 50 | l, err := net.Listen("tcp", s.addr) 51 | if err != nil { 52 | log.Fatalf("[smux] failed to listen on %s: %v", s.addr, err) 53 | return 54 | } 55 | defer l.Close() 56 | 57 | log.F("[smux] listening mux on %s", s.addr) 58 | 59 | for { 60 | c, err := l.Accept() 61 | if err != nil { 62 | log.F("[smux] failed to accept: %v", err) 63 | continue 64 | } 65 | 66 | go s.Serve(c) 67 | } 68 | } 69 | 70 | // Serve serves a connection. 71 | func (s *SmuxServer) Serve(c net.Conn) { 72 | // we know the internal server will close the connection after serve 73 | // defer c.Close() 74 | 75 | session, err := smux.Server(c, nil) 76 | if err != nil { 77 | log.F("[smux] failed to create session: %v", err) 78 | return 79 | } 80 | 81 | for { 82 | // Accept a stream 83 | stream, err := session.AcceptStream() 84 | if err != nil { 85 | session.Close() 86 | break 87 | } 88 | go s.ServeStream(stream) 89 | } 90 | } 91 | 92 | // ServeStream serves a smux stream. 93 | func (s *SmuxServer) ServeStream(c *smux.Stream) { 94 | if s.server != nil { 95 | s.server.Serve(c) 96 | return 97 | } 98 | 99 | defer c.Close() 100 | 101 | rc, dialer, err := s.proxy.Dial("tcp", "") 102 | if err != nil { 103 | log.F("[smux] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err) 104 | s.proxy.Record(dialer, false) 105 | return 106 | } 107 | defer rc.Close() 108 | 109 | log.F("[smux] %s <-> %s", c.RemoteAddr(), dialer.Addr()) 110 | 111 | if err = proxy.Relay(c, rc); err != nil { 112 | log.F("[smux] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err) 113 | // record remote conn failure only 114 | if !strings.Contains(err.Error(), s.addr) { 115 | s.proxy.Record(dialer, false) 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /proxy/smux/smux.go: -------------------------------------------------------------------------------- 1 | package smux 2 | 3 | import "github.com/meoww-bot/glider/proxy" 4 | 5 | func init() { 6 | proxy.AddUsage("smux", ` 7 | Smux scheme: 8 | smux://host:port 9 | `) 10 | } 11 | -------------------------------------------------------------------------------- /proxy/socks5/packet.go: -------------------------------------------------------------------------------- 1 | package socks5 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/meoww-bot/glider/pkg/pool" 8 | "github.com/meoww-bot/glider/pkg/socks" 9 | ) 10 | 11 | // PktConn . 12 | type PktConn struct { 13 | net.PacketConn 14 | ctrlConn net.Conn // tcp control conn 15 | writeTo net.Addr // write to and read from addr 16 | target socks.Addr 17 | } 18 | 19 | // NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr. 20 | func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctrlConn net.Conn) *PktConn { 21 | pc := &PktConn{ 22 | PacketConn: c, 23 | writeTo: writeAddr, 24 | target: targetAddr, 25 | ctrlConn: ctrlConn, 26 | } 27 | 28 | if ctrlConn != nil { 29 | go func() { 30 | buf := pool.GetBuffer(1) 31 | defer pool.PutBuffer(buf) 32 | for { 33 | _, err := ctrlConn.Read(buf) 34 | if err, ok := err.(net.Error); ok && err.Timeout() { 35 | continue 36 | } 37 | // log.F("[socks5] dialudp udp associate end") 38 | return 39 | } 40 | }() 41 | } 42 | 43 | return pc 44 | } 45 | 46 | // ReadFrom overrides the original function from net.PacketConn. 47 | func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { 48 | n, _, target, err := pc.readFrom(b) 49 | return n, target, err 50 | } 51 | 52 | func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) { 53 | buf := pool.GetBuffer(len(b)) 54 | defer pool.PutBuffer(buf) 55 | 56 | n, raddr, err := pc.PacketConn.ReadFrom(buf) 57 | if err != nil { 58 | return n, raddr, nil, err 59 | } 60 | 61 | if n < 3 { 62 | return n, raddr, nil, errors.New("not enough size to get addr") 63 | } 64 | 65 | // https://www.rfc-editor.org/rfc/rfc1928#section-7 66 | // +----+------+------+----------+----------+----------+ 67 | // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 68 | // +----+------+------+----------+----------+----------+ 69 | // | 2 | 1 | 1 | Variable | 2 | Variable | 70 | // +----+------+------+----------+----------+----------+ 71 | tgtAddr := socks.SplitAddr(buf[3:n]) 72 | if tgtAddr == nil { 73 | return n, raddr, nil, errors.New("can not get target addr") 74 | } 75 | 76 | target, err := net.ResolveUDPAddr("udp", tgtAddr.String()) 77 | if err != nil { 78 | return n, raddr, nil, errors.New("wrong target addr") 79 | } 80 | 81 | if pc.writeTo == nil { 82 | pc.writeTo = raddr 83 | } 84 | 85 | if pc.target == nil { 86 | pc.target = make([]byte, len(tgtAddr)) 87 | copy(pc.target, tgtAddr) 88 | } 89 | 90 | n = copy(b, buf[3+len(tgtAddr):n]) 91 | return n, raddr, target, err 92 | } 93 | 94 | // WriteTo overrides the original function from net.PacketConn. 95 | func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { 96 | target := pc.target 97 | if addr != nil { 98 | target = socks.ParseAddr(addr.String()) 99 | } 100 | 101 | if target == nil { 102 | return 0, errors.New("invalid addr") 103 | } 104 | 105 | buf := pool.GetBytesBuffer() 106 | defer pool.PutBytesBuffer(buf) 107 | 108 | buf.Write([]byte{0, 0, 0}) 109 | tgtLen, _ := buf.Write(target) 110 | buf.Write(b) 111 | 112 | n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo) 113 | if n > tgtLen+3 { 114 | return n - tgtLen - 3, err 115 | } 116 | 117 | return 0, err 118 | } 119 | 120 | // Close . 121 | func (pc *PktConn) Close() error { 122 | if pc.ctrlConn != nil { 123 | pc.ctrlConn.Close() 124 | } 125 | 126 | return pc.PacketConn.Close() 127 | } 128 | -------------------------------------------------------------------------------- /proxy/socks5/socks5.go: -------------------------------------------------------------------------------- 1 | // https://www.rfc-editor.org/rfc/rfc1928 2 | 3 | // socks5 client: 4 | // https://github.com/golang/net/tree/master/proxy 5 | // Copyright 2011 The Go Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | 9 | // Package socks5 implements a socks5 proxy. 10 | package socks5 11 | 12 | import ( 13 | "net/url" 14 | 15 | "github.com/meoww-bot/glider/pkg/log" 16 | "github.com/meoww-bot/glider/proxy" 17 | ) 18 | 19 | // Version is socks5 version number. 20 | const Version = 5 21 | 22 | // Socks5 is a base socks5 struct. 23 | type Socks5 struct { 24 | dialer proxy.Dialer 25 | proxy proxy.Proxy 26 | addr string 27 | user string 28 | password string 29 | } 30 | 31 | // NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address. 32 | // with an optional username and password. (RFC 1928) 33 | func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) { 34 | u, err := url.Parse(s) 35 | if err != nil { 36 | log.F("parse err: %s", err) 37 | return nil, err 38 | } 39 | 40 | addr := u.Host 41 | user := u.User.Username() 42 | pass, _ := u.User.Password() 43 | 44 | h := &Socks5{ 45 | dialer: d, 46 | proxy: p, 47 | addr: addr, 48 | user: user, 49 | password: pass, 50 | } 51 | 52 | return h, nil 53 | } 54 | 55 | func init() { 56 | proxy.AddUsage("socks5", ` 57 | Socks5 scheme: 58 | socks5://[user:pass@]host:port 59 | `) 60 | } 61 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowaead/cipher.go: -------------------------------------------------------------------------------- 1 | package shadowaead 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/sha1" 7 | "io" 8 | "strconv" 9 | 10 | "golang.org/x/crypto/chacha20poly1305" 11 | "golang.org/x/crypto/hkdf" 12 | ) 13 | 14 | // Cipher generates a pair of stream ciphers for encryption and decryption. 15 | type Cipher interface { 16 | KeySize() int 17 | SaltSize() int 18 | Encrypter(salt []byte) (cipher.AEAD, error) 19 | Decrypter(salt []byte) (cipher.AEAD, error) 20 | } 21 | 22 | // KeySizeError is an error about the key size. 23 | type KeySizeError int 24 | 25 | func (e KeySizeError) Error() string { 26 | return "key size error: need " + strconv.Itoa(int(e)) + " bytes" 27 | } 28 | 29 | func hkdfSHA1(secret, salt, info, outkey []byte) { 30 | r := hkdf.New(sha1.New, secret, salt, info) 31 | if _, err := io.ReadFull(r, outkey); err != nil { 32 | panic(err) // should never happen 33 | } 34 | } 35 | 36 | type metaCipher struct { 37 | psk []byte 38 | makeAEAD func(key []byte) (cipher.AEAD, error) 39 | } 40 | 41 | func (a *metaCipher) KeySize() int { return len(a.psk) } 42 | func (a *metaCipher) SaltSize() int { 43 | if ks := a.KeySize(); ks > 16 { 44 | return ks 45 | } 46 | return 16 47 | } 48 | func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) { 49 | subkey := make([]byte, a.KeySize()) 50 | hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey) 51 | return a.makeAEAD(subkey) 52 | } 53 | func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) { 54 | subkey := make([]byte, a.KeySize()) 55 | hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey) 56 | return a.makeAEAD(subkey) 57 | } 58 | 59 | func aesGCM(key []byte) (cipher.AEAD, error) { 60 | blk, err := aes.NewCipher(key) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return cipher.NewGCM(blk) 65 | } 66 | 67 | // AESGCM creates a new Cipher with a pre-shared key. len(psk) must be 68 | // one of 16, 24, or 32 to select AES-128/196/256-GCM. 69 | func AESGCM(psk []byte) (Cipher, error) { 70 | switch l := len(psk); l { 71 | case 16, 24, 32: // AES 128/196/256 72 | default: 73 | return nil, aes.KeySizeError(l) 74 | } 75 | return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil 76 | } 77 | 78 | // Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk) 79 | // must be 32. 80 | func Chacha20Poly1305(psk []byte) (Cipher, error) { 81 | if len(psk) != chacha20poly1305.KeySize { 82 | return nil, KeySizeError(chacha20poly1305.KeySize) 83 | } 84 | return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil 85 | } 86 | 87 | // XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk) 88 | // must be 32. 89 | func XChacha20Poly1305(psk []byte) (Cipher, error) { 90 | if len(psk) != chacha20poly1305.KeySize { 91 | return nil, KeySizeError(chacha20poly1305.KeySize) 92 | } 93 | return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil 94 | } 95 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowaead/conn.go: -------------------------------------------------------------------------------- 1 | package shadowaead 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "net" 7 | ) 8 | 9 | type streamConn struct { 10 | net.Conn 11 | Cipher 12 | r *reader 13 | w *writer 14 | } 15 | 16 | // NewConn wraps a stream-oriented net.Conn with cipher. 17 | func NewConn(c net.Conn, ciph Cipher) net.Conn { return &streamConn{Conn: c, Cipher: ciph} } 18 | 19 | func (c *streamConn) initReader() error { 20 | salt := make([]byte, c.SaltSize()) 21 | if _, err := io.ReadFull(c.Conn, salt); err != nil { 22 | return err 23 | } 24 | 25 | aead, err := c.Decrypter(salt) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | c.r = newReader(c.Conn, aead) 31 | return nil 32 | } 33 | 34 | func (c *streamConn) Read(b []byte) (int, error) { 35 | if c.r == nil { 36 | if err := c.initReader(); err != nil { 37 | return 0, err 38 | } 39 | } 40 | return c.r.Read(b) 41 | } 42 | 43 | func (c *streamConn) initWriter() error { 44 | salt := make([]byte, c.SaltSize()) 45 | if _, err := io.ReadFull(rand.Reader, salt); err != nil { 46 | return err 47 | } 48 | aead, err := c.Encrypter(salt) 49 | if err != nil { 50 | return err 51 | } 52 | _, err = c.Conn.Write(salt) 53 | if err != nil { 54 | return err 55 | } 56 | c.w = newWriter(c.Conn, aead) 57 | return nil 58 | } 59 | 60 | func (c *streamConn) Write(b []byte) (int, error) { 61 | if c.w == nil { 62 | if err := c.initWriter(); err != nil { 63 | return 0, err 64 | } 65 | } 66 | return c.w.Write(b) 67 | } 68 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowaead/packet.go: -------------------------------------------------------------------------------- 1 | package shadowaead 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "io" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | // ErrShortPacket means that the packet is too short for a valid encrypted packet. 12 | var ErrShortPacket = errors.New("short packet") 13 | 14 | var _zerononce [128]byte // read-only. 128 bytes is more than enough. 15 | 16 | // Pack encrypts plaintext using Cipher with a randomly generated salt and 17 | // returns a slice of dst containing the encrypted packet and any error occurred. 18 | // Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead(). 19 | func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) { 20 | saltSize := ciph.SaltSize() 21 | salt := dst[:saltSize] 22 | if _, err := io.ReadFull(rand.Reader, salt); err != nil { 23 | return nil, err 24 | } 25 | 26 | aead, err := ciph.Encrypter(salt) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if len(dst) < saltSize+len(plaintext)+aead.Overhead() { 32 | return nil, io.ErrShortBuffer 33 | } 34 | b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil) 35 | return dst[:saltSize+len(b)], nil 36 | } 37 | 38 | // Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred. 39 | // Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead(). 40 | func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) { 41 | saltSize := ciph.SaltSize() 42 | if len(pkt) < saltSize { 43 | return nil, ErrShortPacket 44 | } 45 | salt := pkt[:saltSize] 46 | aead, err := ciph.Decrypter(salt) 47 | if err != nil { 48 | return nil, err 49 | } 50 | if len(pkt) < saltSize+aead.Overhead() { 51 | return nil, ErrShortPacket 52 | } 53 | if saltSize+len(dst)+aead.Overhead() < len(pkt) { 54 | return nil, io.ErrShortBuffer 55 | } 56 | b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil) 57 | return b, err 58 | } 59 | 60 | type packetConn struct { 61 | net.PacketConn 62 | Cipher 63 | sync.Mutex 64 | buf []byte // write lock 65 | } 66 | 67 | // NewPacketConn wraps a net.PacketConn with cipher 68 | func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn { 69 | const maxPacketSize = 64 * 1024 70 | return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, maxPacketSize)} 71 | } 72 | 73 | // WriteTo encrypts b and write to addr using the embedded PacketConn. 74 | func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) { 75 | c.Lock() 76 | defer c.Unlock() 77 | buf, err := Pack(c.buf, b, c) 78 | if err != nil { 79 | return 0, err 80 | } 81 | _, err = c.PacketConn.WriteTo(buf, addr) 82 | return len(b), err 83 | } 84 | 85 | // ReadFrom reads from the embedded PacketConn and decrypts into b. 86 | func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { 87 | n, addr, err := c.PacketConn.ReadFrom(b) 88 | if err != nil { 89 | return n, addr, err 90 | } 91 | bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c) 92 | if err != nil { 93 | return n, addr, err 94 | } 95 | copy(b, bb) 96 | return len(bb), addr, err 97 | } 98 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowaead/stream.go: -------------------------------------------------------------------------------- 1 | // protocol: 2 | // format: [encrypted payload length] [Overhead] [encrypted payload] [Overhead] 3 | // sizes: 2 bytes, aead.Overhead() bytes, n bytes, aead.Overhead() bytes 4 | // max(n): 0x3FFF 5 | 6 | package shadowaead 7 | 8 | import ( 9 | "crypto/cipher" 10 | "encoding/binary" 11 | "io" 12 | 13 | "github.com/meoww-bot/glider/pkg/pool" 14 | ) 15 | 16 | const ( 17 | lenSize = 2 18 | // max payload size: 16383 from ss-libev aead.c: CHUNK_SIZE_MASK 19 | maxPayload = 0x3FFF 20 | // buf size, shoud enough to save lenSize + aead.Overhead() + maxPayload + aead.Overhead() 21 | bufSize = 17 << 10 22 | ) 23 | 24 | type writer struct { 25 | io.Writer 26 | cipher.AEAD 27 | nonce [32]byte 28 | } 29 | 30 | // NewWriter wraps an io.Writer with AEAD encryption. 31 | func NewWriter(w io.Writer, aead cipher.AEAD) io.Writer { return newWriter(w, aead) } 32 | 33 | func newWriter(w io.Writer, aead cipher.AEAD) *writer { 34 | return &writer{Writer: w, AEAD: aead} 35 | } 36 | 37 | // Write encrypts p and writes to the embedded io.Writer. 38 | func (w *writer) Write(p []byte) (n int, err error) { 39 | buf := pool.GetBuffer(bufSize) 40 | defer pool.PutBuffer(buf) 41 | 42 | nonce := w.nonce[:w.NonceSize()] 43 | encLenSize := lenSize + w.Overhead() 44 | for nw := maxPayload; n < len(p); n += nw { 45 | if left := len(p) - n; left < maxPayload { 46 | nw = left 47 | } 48 | 49 | binary.BigEndian.PutUint16(buf[:lenSize], uint16(nw)) 50 | w.Seal(buf[:0], nonce, buf[:lenSize], nil) 51 | increment(nonce) 52 | 53 | w.Seal(buf[:encLenSize], nonce, p[n:n+nw], nil) 54 | increment(nonce) 55 | 56 | if _, err = w.Writer.Write(buf[:encLenSize+nw+w.Overhead()]); err != nil { 57 | return 58 | } 59 | } 60 | return 61 | } 62 | 63 | type reader struct { 64 | io.Reader 65 | cipher.AEAD 66 | nonce [32]byte 67 | buf []byte 68 | offset int 69 | } 70 | 71 | // NewReader wraps an io.Reader with AEAD decryption. 72 | func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { return newReader(r, aead) } 73 | 74 | func newReader(r io.Reader, aead cipher.AEAD) *reader { 75 | return &reader{Reader: r, AEAD: aead} 76 | } 77 | 78 | // NOTE: len(p) MUST >= max payload size + AEAD overhead. 79 | func (r *reader) read(p []byte) (int, error) { 80 | nonce := r.nonce[:r.NonceSize()] 81 | 82 | // read encrypted lenSize + overhead 83 | p = p[:lenSize+r.Overhead()] 84 | if _, err := io.ReadFull(r.Reader, p); err != nil { 85 | return 0, err 86 | } 87 | 88 | // decrypt lenSize 89 | _, err := r.Open(p[:0], nonce, p, nil) 90 | increment(nonce) 91 | if err != nil { 92 | return 0, err 93 | } 94 | 95 | // get payload size 96 | size := int(binary.BigEndian.Uint16(p[:lenSize])) 97 | 98 | // read encrypted payload + overhead 99 | p = p[:size+r.Overhead()] 100 | if _, err := io.ReadFull(r.Reader, p); err != nil { 101 | return 0, err 102 | } 103 | 104 | // decrypt payload 105 | _, err = r.Open(p[:0], nonce, p, nil) 106 | increment(nonce) 107 | 108 | if err != nil { 109 | return 0, err 110 | } 111 | 112 | return size, nil 113 | } 114 | 115 | func (r *reader) Read(p []byte) (int, error) { 116 | if r.buf == nil { 117 | if len(p) >= maxPayload+r.Overhead() { 118 | return r.read(p) 119 | } 120 | 121 | buf := pool.GetBuffer(bufSize) 122 | n, err := r.read(buf) 123 | if err != nil { 124 | pool.PutBuffer(buf) 125 | return 0, err 126 | } 127 | 128 | r.buf = buf[:n] 129 | r.offset = 0 130 | } 131 | 132 | n := copy(p, r.buf[r.offset:]) 133 | r.offset += n 134 | if r.offset == len(r.buf) { 135 | pool.PutBuffer(r.buf) 136 | r.buf = nil 137 | } 138 | 139 | return n, nil 140 | } 141 | 142 | // increment little-endian encoded unsigned integer b. Wrap around on overflow. 143 | func increment(b []byte) { 144 | for i := range b { 145 | b[i]++ 146 | if b[i] != 0 { 147 | return 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowstream/cipher.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/md5" 7 | "crypto/rc4" 8 | "strconv" 9 | 10 | "github.com/aead/chacha20" 11 | "github.com/aead/chacha20/chacha" 12 | ) 13 | 14 | // Cipher generates a pair of stream ciphers for encryption and decryption. 15 | type Cipher interface { 16 | IVSize() int 17 | Encrypter(iv []byte) cipher.Stream 18 | Decrypter(iv []byte) cipher.Stream 19 | } 20 | 21 | // KeySizeError is an error about the key size. 22 | type KeySizeError int 23 | 24 | func (e KeySizeError) Error() string { 25 | return "key size error: need " + strconv.Itoa(int(e)) + " bytes" 26 | } 27 | 28 | // CTR mode 29 | type ctrStream struct{ cipher.Block } 30 | 31 | func (b *ctrStream) IVSize() int { return b.BlockSize() } 32 | func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) } 33 | func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) } 34 | 35 | // AESCTR returns an aesctr cipher. 36 | func AESCTR(key []byte) (Cipher, error) { 37 | blk, err := aes.NewCipher(key) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return &ctrStream{blk}, nil 42 | } 43 | 44 | // CFB mode 45 | type cfbStream struct{ cipher.Block } 46 | 47 | func (b *cfbStream) IVSize() int { return b.BlockSize() } 48 | func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) } 49 | func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) } 50 | 51 | // AESCFB returns an aescfb cipher. 52 | func AESCFB(key []byte) (Cipher, error) { 53 | blk, err := aes.NewCipher(key) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return &cfbStream{blk}, nil 58 | } 59 | 60 | // IETF-variant of chacha20 61 | type chacha20ietfkey []byte 62 | 63 | func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize } 64 | func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } 65 | func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { 66 | ciph, err := chacha20.NewCipher(iv, k) 67 | if err != nil { 68 | panic(err) // should never happen 69 | } 70 | return ciph 71 | } 72 | 73 | // Chacha20IETF returns a Chacha20IETF cipher. 74 | func Chacha20IETF(key []byte) (Cipher, error) { 75 | if len(key) != chacha.KeySize { 76 | return nil, KeySizeError(chacha.KeySize) 77 | } 78 | return chacha20ietfkey(key), nil 79 | } 80 | 81 | // xchacha20 82 | type xchacha20key []byte 83 | 84 | func (k xchacha20key) IVSize() int { return chacha.XNonceSize } 85 | func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } 86 | func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { 87 | ciph, err := chacha20.NewCipher(iv, k) 88 | if err != nil { 89 | panic(err) // should never happen 90 | } 91 | return ciph 92 | } 93 | 94 | // Xchacha20 returns a Xchacha20 cipher. 95 | func Xchacha20(key []byte) (Cipher, error) { 96 | if len(key) != chacha.KeySize { 97 | return nil, KeySizeError(chacha.KeySize) 98 | } 99 | return xchacha20key(key), nil 100 | } 101 | 102 | // chacah20 103 | type chacha20key []byte 104 | 105 | func (k chacha20key) IVSize() int { return chacha.NonceSize } 106 | func (k chacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } 107 | func (k chacha20key) Encrypter(iv []byte) cipher.Stream { 108 | ciph, err := chacha20.NewCipher(iv, k) 109 | if err != nil { 110 | panic(err) // should never happen 111 | } 112 | return ciph 113 | } 114 | 115 | // ChaCha20 returns a ChaCha20 cipher. 116 | func ChaCha20(key []byte) (Cipher, error) { 117 | if len(key) != chacha.KeySize { 118 | return nil, KeySizeError(chacha.KeySize) 119 | } 120 | return chacha20key(key), nil 121 | } 122 | 123 | // rc4md5 124 | type rc4Md5Key []byte 125 | 126 | func (k rc4Md5Key) IVSize() int { return 16 } 127 | func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } 128 | func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream { 129 | h := md5.New() 130 | h.Write([]byte(k)) 131 | h.Write(iv) 132 | rc4key := h.Sum(nil) 133 | ciph, err := rc4.NewCipher(rc4key) 134 | if err != nil { 135 | panic(err) // should never happen 136 | } 137 | return ciph 138 | } 139 | 140 | // RC4MD5 returns a RC4MD5 cipher. 141 | func RC4MD5(key []byte) (Cipher, error) { 142 | return rc4Md5Key(key), nil 143 | } 144 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowstream/conn.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "net" 7 | ) 8 | 9 | type conn struct { 10 | net.Conn 11 | Cipher 12 | r *reader 13 | w *writer 14 | } 15 | 16 | // NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption. 17 | func NewConn(c net.Conn, ciph Cipher) net.Conn { 18 | return &conn{Conn: c, Cipher: ciph} 19 | } 20 | 21 | func (c *conn) initReader() error { 22 | if c.r == nil { 23 | iv := make([]byte, c.IVSize()) 24 | if _, err := io.ReadFull(c.Conn, iv); err != nil { 25 | return err 26 | } 27 | c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv)} 28 | } 29 | return nil 30 | } 31 | 32 | func (c *conn) Read(b []byte) (int, error) { 33 | if c.r == nil { 34 | if err := c.initReader(); err != nil { 35 | return 0, err 36 | } 37 | } 38 | return c.r.Read(b) 39 | } 40 | 41 | func (c *conn) initWriter() error { 42 | if c.w == nil { 43 | iv := make([]byte, c.IVSize()) 44 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 45 | return err 46 | } 47 | if _, err := c.Conn.Write(iv); err != nil { 48 | return err 49 | } 50 | c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv)} 51 | } 52 | return nil 53 | } 54 | 55 | func (c *conn) Write(b []byte) (int, error) { 56 | if c.w == nil { 57 | if err := c.initWriter(); err != nil { 58 | return 0, err 59 | } 60 | } 61 | return c.w.Write(b) 62 | } 63 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowstream/packet.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "io" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | // ErrShortPacket means the packet is too short to be a valid encrypted packet. 12 | var ErrShortPacket = errors.New("short packet") 13 | 14 | // Pack encrypts plaintext using stream cipher s and a random IV. 15 | // Returns a slice of dst containing random IV and ciphertext. 16 | // Ensure len(dst) >= s.IVSize() + len(plaintext). 17 | func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { 18 | if len(dst) < s.IVSize()+len(plaintext) { 19 | return nil, io.ErrShortBuffer 20 | } 21 | iv := dst[:s.IVSize()] 22 | _, err := io.ReadFull(rand.Reader, iv) 23 | if err != nil { 24 | return nil, err 25 | } 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 | 38 | if len(dst) < len(pkt)-s.IVSize() { 39 | return nil, io.ErrShortBuffer 40 | } 41 | iv := pkt[:s.IVSize()] 42 | s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):]) 43 | return dst[:len(pkt)-len(iv)], nil 44 | } 45 | 46 | type packetConn struct { 47 | net.PacketConn 48 | Cipher 49 | buf []byte 50 | sync.Mutex // write lock 51 | } 52 | 53 | // NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption. 54 | func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn { 55 | return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, 64*1024)} 56 | } 57 | 58 | func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) { 59 | c.Lock() 60 | defer c.Unlock() 61 | buf, err := Pack(c.buf, b, c.Cipher) 62 | if err != nil { 63 | return 0, err 64 | } 65 | _, err = c.PacketConn.WriteTo(buf, addr) 66 | return len(b), err 67 | } 68 | 69 | func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) { 70 | n, addr, err := c.PacketConn.ReadFrom(b) 71 | if err != nil { 72 | return n, addr, err 73 | } 74 | bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher) 75 | if err != nil { 76 | return n, addr, err 77 | } 78 | copy(b, bb) 79 | return len(bb), addr, err 80 | } 81 | -------------------------------------------------------------------------------- /proxy/ss/cipher/shadowstream/stream.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/cipher" 5 | "io" 6 | 7 | "github.com/meoww-bot/glider/pkg/pool" 8 | ) 9 | 10 | const bufSize = 32 * 1024 11 | 12 | type writer struct { 13 | io.Writer 14 | cipher.Stream 15 | } 16 | 17 | // NewWriter wraps an io.Writer with stream cipher encryption. 18 | func NewWriter(w io.Writer, s cipher.Stream) io.Writer { 19 | return &writer{Writer: w, Stream: s} 20 | } 21 | 22 | func (w *writer) Write(p []byte) (n int, err error) { 23 | buf := pool.GetBuffer(bufSize) 24 | defer pool.PutBuffer(buf) 25 | 26 | for nw := 0; n < len(p) && err == nil; n += nw { 27 | end := n + len(buf) 28 | if end > len(p) { 29 | end = len(p) 30 | } 31 | w.XORKeyStream(buf, p[n:end]) 32 | nw, err = w.Writer.Write(buf[:end-n]) 33 | } 34 | return 35 | } 36 | 37 | type reader struct { 38 | io.Reader 39 | cipher.Stream 40 | } 41 | 42 | // NewReader wraps an io.Reader with stream cipher decryption. 43 | func NewReader(r io.Reader, s cipher.Stream) io.Reader { 44 | return &reader{Reader: r, Stream: s} 45 | } 46 | 47 | func (r *reader) Read(p []byte) (int, error) { 48 | n, err := r.Reader.Read(p) 49 | if err != nil { 50 | return 0, err 51 | } 52 | p = p[:n] 53 | r.XORKeyStream(p, p) 54 | return n, nil 55 | } 56 | -------------------------------------------------------------------------------- /proxy/ss/client.go: -------------------------------------------------------------------------------- 1 | package ss 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/meoww-bot/glider/pkg/log" 8 | "github.com/meoww-bot/glider/pkg/socks" 9 | "github.com/meoww-bot/glider/proxy" 10 | ) 11 | 12 | // NewSSDialer returns a ss proxy dialer. 13 | func NewSSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 14 | return NewSS(s, d, nil) 15 | } 16 | 17 | // Addr returns forwarder's address. 18 | func (s *SS) Addr() string { 19 | if s.addr == "" { 20 | return s.dialer.Addr() 21 | } 22 | return s.addr 23 | } 24 | 25 | // Dial connects to the address addr on the network net via the proxy. 26 | func (s *SS) Dial(network, addr string) (net.Conn, error) { 27 | target := socks.ParseAddr(addr) 28 | if target == nil { 29 | return nil, errors.New("[ss] unable to parse address: " + addr) 30 | } 31 | 32 | c, err := s.dialer.Dial("tcp", s.addr) 33 | if err != nil { 34 | log.F("[ss] dial to %s error: %s", s.addr, err) 35 | return nil, err 36 | } 37 | 38 | c = s.StreamConn(c) 39 | if _, err = c.Write(target); err != nil { 40 | c.Close() 41 | return nil, err 42 | } 43 | 44 | return c, err 45 | } 46 | 47 | // DialUDP connects to the given address via the proxy. 48 | func (s *SS) DialUDP(network, addr string) (net.PacketConn, error) { 49 | pc, err := s.dialer.DialUDP(network, s.addr) 50 | if err != nil { 51 | log.F("[ss] dialudp to %s error: %s", s.addr, err) 52 | return nil, err 53 | } 54 | 55 | writeTo, err := net.ResolveUDPAddr("udp", s.addr) 56 | if err != nil { 57 | log.F("[ss] resolve addr error: %s", err) 58 | return nil, err 59 | } 60 | 61 | pkc := NewPktConn(s.PacketConn(pc), writeTo, socks.ParseAddr(addr)) 62 | return pkc, nil 63 | } 64 | -------------------------------------------------------------------------------- /proxy/ss/packet.go: -------------------------------------------------------------------------------- 1 | package ss 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/meoww-bot/glider/pkg/pool" 8 | "github.com/meoww-bot/glider/pkg/socks" 9 | ) 10 | 11 | // PktConn . 12 | type PktConn struct { 13 | net.PacketConn 14 | writeTo net.Addr 15 | target socks.Addr // if target is not nil, it may be a tunnel 16 | } 17 | 18 | // NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr. 19 | func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr) *PktConn { 20 | return &PktConn{PacketConn: c, writeTo: writeAddr, target: targetAddr} 21 | } 22 | 23 | // ReadFrom overrides the original function from net.PacketConn. 24 | func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { 25 | n, _, target, err := pc.readFrom(b) 26 | return n, target, err 27 | } 28 | 29 | func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) { 30 | buf := pool.GetBuffer(len(b)) 31 | defer pool.PutBuffer(buf) 32 | 33 | n, raddr, err := pc.PacketConn.ReadFrom(buf) 34 | if err != nil { 35 | return n, raddr, nil, err 36 | } 37 | 38 | tgtAddr := socks.SplitAddr(buf[:n]) 39 | if tgtAddr == nil { 40 | return n, raddr, nil, errors.New("can not get target addr") 41 | } 42 | 43 | target, err := net.ResolveUDPAddr("udp", tgtAddr.String()) 44 | if err != nil { 45 | return n, raddr, nil, errors.New("wrong target addr") 46 | } 47 | 48 | if pc.writeTo == nil { 49 | pc.writeTo = raddr 50 | } 51 | 52 | if pc.target == nil { 53 | pc.target = make([]byte, len(tgtAddr)) 54 | copy(pc.target, tgtAddr) 55 | } 56 | 57 | n = copy(b, buf[len(tgtAddr):n]) 58 | return n, raddr, target, err 59 | } 60 | 61 | // WriteTo overrides the original function from net.PacketConn 62 | func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { 63 | target := pc.target 64 | if addr != nil { 65 | target = socks.ParseAddr(addr.String()) 66 | } 67 | 68 | if target == nil { 69 | return 0, errors.New("invalid addr") 70 | } 71 | 72 | buf := pool.GetBytesBuffer() 73 | defer pool.PutBytesBuffer(buf) 74 | 75 | tgtLen, _ := buf.Write(target) 76 | buf.Write(b) 77 | 78 | n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo) 79 | if n > tgtLen { 80 | return n - tgtLen, err 81 | } 82 | 83 | return 0, err 84 | } 85 | -------------------------------------------------------------------------------- /proxy/ss/ss.go: -------------------------------------------------------------------------------- 1 | package ss 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/meoww-bot/glider/pkg/log" 9 | "github.com/meoww-bot/glider/proxy" 10 | "github.com/meoww-bot/glider/proxy/ss/cipher" 11 | ) 12 | 13 | // SS is a base ss struct. 14 | type SS struct { 15 | dialer proxy.Dialer 16 | proxy proxy.Proxy 17 | addr string 18 | 19 | cipher.Cipher 20 | } 21 | 22 | func init() { 23 | proxy.RegisterDialer("ss", NewSSDialer) 24 | proxy.RegisterServer("ss", NewSSServer) 25 | } 26 | 27 | func addPaddingIfNeeded(base64String string) string { 28 | // 计算需要添加的填充字符数量 29 | padding := strings.Repeat("=", (4-len(base64String)%4)%4) 30 | return base64String + padding 31 | } 32 | 33 | // NewSS returns a ss proxy. 34 | func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) { 35 | 36 | u, err := url.Parse(s) 37 | if err != nil { 38 | log.F("[ss] parse err: %s", err) 39 | return nil, err 40 | } 41 | 42 | var method, pass string 43 | 44 | addr := u.Host 45 | if u.User == nil { 46 | paddedBase64String := addPaddingIfNeeded(addr) 47 | ss, err := base64.StdEncoding.DecodeString(paddedBase64String) 48 | if err != nil { 49 | log.F("[ss] parse err: %s", err) 50 | return nil, err 51 | } 52 | u, err := url.Parse(string(ss)) 53 | method = u.Scheme 54 | pass = strings.Split(u.Opaque, "@")[0] 55 | addr = strings.Split(u.Opaque, "@")[1] 56 | 57 | } else if !strings.Contains(u.User.String(), "-") { 58 | 59 | paddedBase64String := addPaddingIfNeeded(u.User.String()) 60 | ss, err := base64.StdEncoding.DecodeString(paddedBase64String) 61 | if err != nil { 62 | log.F("base64 decode err: %s %s", err, u.User.String()) 63 | return nil, err 64 | } 65 | 66 | method = strings.Split(string(ss), ":")[0] 67 | pass = strings.Split(string(ss), ":")[1] 68 | 69 | } else { 70 | method = u.User.Username() 71 | pass, _ = u.User.Password() 72 | } 73 | 74 | ciph, err := cipher.PickCipher(method, nil, pass) 75 | if err != nil { 76 | log.Fatalf("[ss] PickCipher for '%s', error: %s", method, err) 77 | } 78 | 79 | ss := &SS{ 80 | dialer: d, 81 | proxy: p, 82 | addr: addr, 83 | Cipher: ciph, 84 | } 85 | 86 | return ss, nil 87 | } 88 | 89 | func init() { 90 | proxy.AddUsage("ss", ` 91 | SS scheme: 92 | ss://method:pass@host:port 93 | 94 | Available methods for ss: 95 | AEAD Ciphers: 96 | AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305 97 | Stream Ciphers: 98 | AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5 99 | Alias: 100 | chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305 101 | Plain: NONE 102 | `) 103 | } 104 | -------------------------------------------------------------------------------- /proxy/ssr/internal/obfs/base.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/meoww-bot/glider/proxy/ssr/internal/ssr" 7 | ) 8 | 9 | type creator func() IObfs 10 | 11 | var ( 12 | creatorMap = make(map[string]creator) 13 | ) 14 | 15 | type IObfs interface { 16 | SetServerInfo(s *ssr.ServerInfo) 17 | GetServerInfo() (s *ssr.ServerInfo) 18 | Encode(data []byte) (encodedData []byte, err error) 19 | Decode(data []byte) (decodedData []byte, needSendBack bool, err error) 20 | SetData(data any) 21 | GetData() any 22 | GetOverhead() int 23 | } 24 | 25 | func register(name string, c creator) { 26 | creatorMap[name] = c 27 | } 28 | 29 | // NewObfs create an obfs object by name and return as an IObfs interface 30 | func NewObfs(name string) IObfs { 31 | c, ok := creatorMap[strings.ToLower(name)] 32 | if ok { 33 | return c() 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /proxy/ssr/internal/obfs/http_post.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func init() { 8 | register("http_post", newHttpPost) 9 | } 10 | 11 | // newHttpPost create a http_post object 12 | func newHttpPost() IObfs { 13 | // newHttpSimple create a http_simple object 14 | 15 | t := &httpSimplePost{ 16 | userAgentIndex: rand.Intn(len(requestUserAgent)), 17 | methodGet: false, 18 | } 19 | return t 20 | } 21 | -------------------------------------------------------------------------------- /proxy/ssr/internal/obfs/plain.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "github.com/meoww-bot/glider/proxy/ssr/internal/ssr" 5 | ) 6 | 7 | func init() { 8 | register("plain", newPlainObfs) 9 | } 10 | 11 | type plain struct { 12 | ssr.ServerInfo 13 | } 14 | 15 | func newPlainObfs() IObfs { 16 | p := &plain{} 17 | return p 18 | } 19 | 20 | func (p *plain) SetServerInfo(s *ssr.ServerInfo) { 21 | p.ServerInfo = *s 22 | } 23 | 24 | func (p *plain) GetServerInfo() (s *ssr.ServerInfo) { 25 | return &p.ServerInfo 26 | } 27 | 28 | func (p *plain) Encode(data []byte) (encodedData []byte, err error) { 29 | return data, nil 30 | } 31 | 32 | func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) { 33 | return data, false, nil 34 | } 35 | 36 | func (p *plain) SetData(data any) { 37 | 38 | } 39 | 40 | func (p *plain) GetData() any { 41 | return nil 42 | } 43 | 44 | func (p *plain) GetOverhead() int { 45 | return 0 46 | } 47 | -------------------------------------------------------------------------------- /proxy/ssr/internal/obfs/random_head.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/meoww-bot/glider/proxy/ssr/internal/ssr" 7 | ) 8 | 9 | type randomHead struct { 10 | ssr.ServerInfo 11 | rawTransSent bool 12 | rawTransReceived bool 13 | hasSentHeader bool 14 | dataBuffer []byte 15 | } 16 | 17 | func init() { 18 | register("random_head", newRandomHead) 19 | } 20 | 21 | func newRandomHead() IObfs { 22 | p := &randomHead{} 23 | return p 24 | } 25 | 26 | func (r *randomHead) SetServerInfo(s *ssr.ServerInfo) { 27 | r.ServerInfo = *s 28 | } 29 | 30 | func (r *randomHead) GetServerInfo() (s *ssr.ServerInfo) { 31 | return &r.ServerInfo 32 | } 33 | 34 | func (r *randomHead) SetData(data any) { 35 | 36 | } 37 | 38 | func (r *randomHead) GetData() any { 39 | return nil 40 | } 41 | 42 | func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) { 43 | if r.rawTransSent { 44 | return data, nil 45 | } 46 | 47 | dataLength := len(data) 48 | if r.hasSentHeader { 49 | if dataLength > 0 { 50 | d := make([]byte, len(r.dataBuffer)+dataLength) 51 | copy(d, r.dataBuffer) 52 | copy(d[len(r.dataBuffer):], data) 53 | r.dataBuffer = d 54 | } else { 55 | encodedData = r.dataBuffer 56 | r.dataBuffer = nil 57 | r.rawTransSent = true 58 | } 59 | } else { 60 | size := rand.Intn(96) + 8 61 | encodedData = make([]byte, size) 62 | rand.Read(encodedData) 63 | ssr.SetCRC32(encodedData, size) 64 | 65 | d := make([]byte, dataLength) 66 | copy(d, data) 67 | r.dataBuffer = d 68 | } 69 | r.hasSentHeader = true 70 | return 71 | } 72 | 73 | func (r *randomHead) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) { 74 | if r.rawTransReceived { 75 | return data, false, nil 76 | } 77 | r.rawTransReceived = true 78 | return data, true, nil 79 | } 80 | 81 | func (r *randomHead) GetOverhead() int { 82 | return 0 83 | } 84 | -------------------------------------------------------------------------------- /proxy/ssr/internal/protocol/auth_aes128_sha1.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/meoww-bot/glider/proxy/ssr/internal/tools" 7 | ) 8 | 9 | func init() { 10 | register("auth_aes128_sha1", NewAuthAES128SHA1) 11 | } 12 | 13 | func NewAuthAES128SHA1() IProtocol { 14 | a := &authAES128{ 15 | salt: "auth_aes128_sha1", 16 | hmac: tools.HmacSHA1, 17 | hashDigest: tools.SHA1Sum, 18 | packID: 1, 19 | recvInfo: recvInfo{ 20 | recvID: 1, 21 | buffer: bytes.NewBuffer(nil), 22 | }, 23 | } 24 | return a 25 | } 26 | -------------------------------------------------------------------------------- /proxy/ssr/internal/protocol/auth_chain_b.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | 7 | "github.com/meoww-bot/glider/proxy/ssr/internal/tools" 8 | ) 9 | 10 | func init() { 11 | register("auth_chain_b", NewAuthChainB) 12 | } 13 | 14 | func NewAuthChainB() IProtocol { 15 | a := &authChainA{ 16 | salt: "auth_chain_b", 17 | hmac: tools.HmacMD5, 18 | hashDigest: tools.SHA1Sum, 19 | rnd: authChainBGetRandLen, 20 | recvInfo: recvInfo{ 21 | recvID: 1, 22 | buffer: new(bytes.Buffer), 23 | }, 24 | } 25 | return a 26 | } 27 | 28 | func (a *authChainA) authChainBInitDataSize() { 29 | if len(a.Key) == 0 { 30 | return 31 | } 32 | // libev version 33 | random := &a.randomServer 34 | random.InitFromBin(a.Key) 35 | length := random.Next()%8 + 4 36 | a.dataSizeList = make([]int, length) 37 | for i := 0; i < int(length); i++ { 38 | a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) 39 | } 40 | sort.Ints(a.dataSizeList) 41 | 42 | length = random.Next()%16 + 8 43 | a.dataSizeList2 = make([]int, length) 44 | for i := 0; i < int(length); i++ { 45 | a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) 46 | } 47 | sort.Ints(a.dataSizeList2) 48 | } 49 | 50 | func authChainBGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { 51 | if dataLength > 1440 { 52 | return 0 53 | } 54 | random.InitFromBinDatalen(lastHash[:16], dataLength) 55 | // libev version, upper_bound 56 | pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead }) 57 | finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList)) 58 | if finalPos < uint64(len(dataSizeList)) { 59 | return dataSizeList[finalPos] - dataLength - overhead 60 | } 61 | // libev version, upper_bound 62 | pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead }) 63 | finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2)) 64 | if finalPos < uint64(len(dataSizeList2)) { 65 | return dataSizeList2[finalPos] - dataLength - overhead 66 | } 67 | if finalPos < uint64(pos+len(dataSizeList2)-1) { 68 | return 0 69 | } 70 | 71 | if dataLength > 1300 { 72 | return int(random.Next() % 31) 73 | } 74 | if dataLength > 900 { 75 | return int(random.Next() % 127) 76 | } 77 | if dataLength > 400 { 78 | return int(random.Next() % 521) 79 | } 80 | return int(random.Next() % 1021) 81 | } 82 | -------------------------------------------------------------------------------- /proxy/ssr/internal/protocol/base.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | 7 | "github.com/meoww-bot/glider/proxy/ssr/internal/ssr" 8 | "github.com/meoww-bot/glider/proxy/ssr/internal/tools" 9 | ) 10 | 11 | type creator func() IProtocol 12 | 13 | var ( 14 | creatorMap = make(map[string]creator) 15 | ) 16 | 17 | type hmacMethod func(key []byte, data []byte) []byte 18 | type hashDigestMethod func(data []byte) []byte 19 | type rndMethod func(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int 20 | 21 | type IProtocol interface { 22 | SetServerInfo(s *ssr.ServerInfo) 23 | GetServerInfo() *ssr.ServerInfo 24 | PreEncrypt(data []byte) ([]byte, error) 25 | PostDecrypt(data []byte) ([]byte, int, error) 26 | SetData(data any) 27 | GetData() any 28 | GetOverhead() int 29 | } 30 | 31 | type AuthData struct { 32 | clientID []byte 33 | connectionID uint32 34 | mutex sync.Mutex 35 | } 36 | 37 | func register(name string, c creator) { 38 | creatorMap[name] = c 39 | } 40 | 41 | func NewProtocol(name string) IProtocol { 42 | c, ok := creatorMap[strings.ToLower(name)] 43 | if ok { 44 | return c() 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /proxy/ssr/internal/protocol/origin.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "github.com/meoww-bot/glider/proxy/ssr/internal/ssr" 5 | ) 6 | 7 | func init() { 8 | register("origin", NewOrigin) 9 | } 10 | 11 | type origin struct { 12 | ssr.ServerInfo 13 | } 14 | 15 | func NewOrigin() IProtocol { 16 | a := &origin{} 17 | return a 18 | } 19 | 20 | func (o *origin) SetServerInfo(s *ssr.ServerInfo) { 21 | o.ServerInfo = *s 22 | } 23 | 24 | func (o *origin) GetServerInfo() (s *ssr.ServerInfo) { 25 | return &o.ServerInfo 26 | } 27 | 28 | func (o *origin) PreEncrypt(data []byte) (encryptedData []byte, err error) { 29 | return data, nil 30 | } 31 | 32 | func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) { 33 | return data, len(data), nil 34 | } 35 | 36 | func (o *origin) SetData(data any) { 37 | 38 | } 39 | 40 | func (o *origin) GetData() any { 41 | return nil 42 | } 43 | 44 | func (o *origin) GetOverhead() int { 45 | return 0 46 | } 47 | -------------------------------------------------------------------------------- /proxy/ssr/internal/protocol/verify_sha1.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | 7 | "github.com/meoww-bot/glider/proxy/ssr/internal/ssr" 8 | "github.com/meoww-bot/glider/proxy/ssr/internal/tools" 9 | ) 10 | 11 | func init() { 12 | register("verify_sha1", NewVerifySHA1) 13 | register("ota", NewVerifySHA1) 14 | } 15 | 16 | type verifySHA1 struct { 17 | ssr.ServerInfo 18 | hasSentHeader bool 19 | buffer bytes.Buffer 20 | chunkId uint32 21 | } 22 | 23 | const ( 24 | oneTimeAuthMask byte = 0x10 25 | ) 26 | 27 | func NewVerifySHA1() IProtocol { 28 | a := &verifySHA1{} 29 | return a 30 | } 31 | 32 | func (v *verifySHA1) otaConnectAuth(data []byte) []byte { 33 | return append(data, tools.HmacSHA1(append(v.IV, v.Key...), data)...) 34 | } 35 | 36 | func (v *verifySHA1) otaReqChunkAuth(chunkId uint32, data []byte) []byte { 37 | nb := make([]byte, 2) 38 | binary.BigEndian.PutUint16(nb, uint16(len(data))) 39 | chunkIdBytes := make([]byte, 4) 40 | binary.BigEndian.PutUint32(chunkIdBytes, chunkId) 41 | header := append(nb, tools.HmacSHA1(append(v.IV, chunkIdBytes...), data)...) 42 | return append(header, data...) 43 | } 44 | 45 | func (v *verifySHA1) otaVerifyAuth(iv []byte, chunkId uint32, data []byte, expectedHmacSha1 []byte) bool { 46 | chunkIdBytes := make([]byte, 4) 47 | binary.BigEndian.PutUint32(chunkIdBytes, chunkId) 48 | actualHmacSha1 := tools.HmacSHA1(append(iv, chunkIdBytes...), data) 49 | return bytes.Equal(expectedHmacSha1, actualHmacSha1) 50 | } 51 | 52 | func (v *verifySHA1) getAndIncreaseChunkId() (chunkId uint32) { 53 | chunkId = v.chunkId 54 | v.chunkId += 1 55 | return 56 | } 57 | 58 | func (v *verifySHA1) SetServerInfo(s *ssr.ServerInfo) { 59 | v.ServerInfo = *s 60 | } 61 | 62 | func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) { 63 | return &v.ServerInfo 64 | } 65 | 66 | func (v *verifySHA1) SetData(data any) { 67 | 68 | } 69 | 70 | func (v *verifySHA1) GetData() any { 71 | return nil 72 | } 73 | 74 | func (v *verifySHA1) PreEncrypt(data []byte) (encryptedData []byte, err error) { 75 | v.buffer.Reset() 76 | dataLength := len(data) 77 | offset := 0 78 | if !v.hasSentHeader { 79 | data[0] |= oneTimeAuthMask 80 | v.buffer.Write(v.otaConnectAuth(data[:v.HeadLen])) 81 | v.hasSentHeader = true 82 | dataLength -= v.HeadLen 83 | offset += v.HeadLen 84 | } 85 | const blockSize = 4096 86 | for dataLength > blockSize { 87 | chunkId := v.getAndIncreaseChunkId() 88 | v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:offset+blockSize])) 89 | dataLength -= blockSize 90 | offset += blockSize 91 | } 92 | if dataLength > 0 { 93 | chunkId := v.getAndIncreaseChunkId() 94 | v.buffer.Write(v.otaReqChunkAuth(chunkId, data[offset:])) 95 | } 96 | return v.buffer.Bytes(), nil 97 | } 98 | 99 | func (v *verifySHA1) PostDecrypt(data []byte) ([]byte, int, error) { 100 | return data, len(data), nil 101 | } 102 | 103 | func (v *verifySHA1) GetOverhead() int { 104 | return 0 105 | } 106 | -------------------------------------------------------------------------------- /proxy/ssr/internal/ssr/adler32.go: -------------------------------------------------------------------------------- 1 | package ssr 2 | 3 | import "encoding/binary" 4 | 5 | func calcShortAdler32(input []byte, a, b uint32) (uint32, uint32) { 6 | for _, i := range input { 7 | a += uint32(i) 8 | b += a 9 | } 10 | a %= 65521 11 | b %= 65521 12 | return a, b 13 | } 14 | 15 | func CalcAdler32(input []byte) uint32 { 16 | var a uint32 = 1 17 | var b uint32 = 0 18 | const nMax = 5552 19 | for length := len(input); length > nMax; length -= nMax { 20 | a, b = calcShortAdler32(input[:nMax], a, b) 21 | input = input[nMax:] 22 | } 23 | a, b = calcShortAdler32(input, a, b) 24 | return (b << 16) + a 25 | } 26 | 27 | func CheckAdler32(input []byte, l int) bool { 28 | adler32 := CalcAdler32(input[:l-4]) 29 | checksum := binary.LittleEndian.Uint32(input[l-4:]) 30 | return adler32 == checksum 31 | } 32 | -------------------------------------------------------------------------------- /proxy/ssr/internal/ssr/crc32.go: -------------------------------------------------------------------------------- 1 | package ssr 2 | 3 | import "encoding/binary" 4 | 5 | var ( 6 | crc32Table = make([]uint32, 256) 7 | ) 8 | 9 | func init() { 10 | createCRC32Table() 11 | } 12 | 13 | func createCRC32Table() { 14 | for i := 0; i < 256; i++ { 15 | crc := uint32(i) 16 | for j := 8; j > 0; j-- { 17 | if crc&1 == 1 { 18 | crc = (crc >> 1) ^ 0xEDB88320 19 | } else { 20 | crc >>= 1 21 | } 22 | } 23 | crc32Table[i] = crc 24 | } 25 | } 26 | 27 | func CalcCRC32(input []byte, length int, value uint32) uint32 { 28 | value = 0xFFFFFFFF 29 | return DoCalcCRC32(input, 0, length, value) 30 | } 31 | 32 | func DoCalcCRC32(input []byte, index int, length int, value uint32) uint32 { 33 | buffer := input 34 | for i := index; i < length; i++ { 35 | value = (value >> 8) ^ crc32Table[byte(value&0xFF)^buffer[i]] 36 | } 37 | return value ^ 0xFFFFFFFF 38 | } 39 | 40 | func DoSetCRC32(buffer []byte, index int, length int) { 41 | crc := CalcCRC32(buffer[:length-4], length-4, 0xFFFFFFFF) 42 | binary.LittleEndian.PutUint32(buffer[length-4:], crc^0xFFFFFFFF) 43 | } 44 | 45 | func SetCRC32(buffer []byte, length int) { 46 | DoSetCRC32(buffer, 0, length) 47 | } 48 | 49 | func CheckCRC32(buffer []byte, length int) bool { 50 | crc := CalcCRC32(buffer, length, 0xFFFFFFFF) 51 | return crc == 0xFFFFFFFF 52 | } 53 | -------------------------------------------------------------------------------- /proxy/ssr/internal/ssr/obfs.go: -------------------------------------------------------------------------------- 1 | package ssr 2 | 3 | import "errors" 4 | 5 | const ObfsHMACSHA1Len = 10 6 | 7 | var ( 8 | ErrAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") 9 | ErrAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") 10 | ErrAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") 11 | ErrAuthAES128IncorrectHMAC = errors.New("auth_aes128_* post decrypt incorrect hmac") 12 | ErrAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") 13 | ErrAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch") 14 | ErrAuthChainIncorrectHMAC = errors.New("auth_chain_* post decrypt incorrect hmac") 15 | ErrAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") 16 | ErrAuthAES128PosOutOfRange = errors.New("auth_aes128_* post decrypt pos out of range") 17 | ErrTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") 18 | ErrTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") 19 | ErrTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") 20 | ) 21 | 22 | type ServerInfo struct { 23 | Host string 24 | Port uint16 25 | Param string 26 | IV []byte 27 | IVLen int 28 | RecvIV []byte 29 | RecvIVLen int 30 | Key []byte 31 | KeyLen int 32 | HeadLen int 33 | TcpMss int 34 | Overhead int 35 | } 36 | 37 | func GetHeadSize(data []byte, defaultValue int) int { 38 | if data == nil || len(data) < 2 { 39 | return defaultValue 40 | } 41 | headType := data[0] & 0x07 42 | switch headType { 43 | case 1: 44 | // IPv4 1+4+2 45 | return 7 46 | case 4: 47 | // IPv6 1+16+2 48 | return 19 49 | case 3: 50 | // domain name, variant length 51 | return 4 + int(data[1]) 52 | } 53 | 54 | return defaultValue 55 | } 56 | 57 | func (s *ServerInfo) SetHeadLen(data []byte, defaultValue int) { 58 | s.HeadLen = GetHeadSize(data, defaultValue) 59 | } 60 | -------------------------------------------------------------------------------- /proxy/ssr/internal/tools/encrypt.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | ) 8 | 9 | func HmacMD5(key []byte, data []byte) []byte { 10 | hmacMD5 := hmac.New(md5.New, key) 11 | hmacMD5.Write(data) 12 | return hmacMD5.Sum(nil)[:16] 13 | } 14 | 15 | func HmacSHA1(key []byte, data []byte) []byte { 16 | hmacSHA1 := hmac.New(sha1.New, key) 17 | hmacSHA1.Write(data) 18 | return hmacSHA1.Sum(nil)[:20] 19 | } 20 | 21 | func MD5Sum(d []byte) []byte { 22 | h := md5.New() 23 | h.Write(d) 24 | return h.Sum(nil) 25 | } 26 | 27 | func SHA1Sum(d []byte) []byte { 28 | h := sha1.New() 29 | h.Write(d) 30 | return h.Sum(nil) 31 | } 32 | 33 | func EVPBytesToKey(password string, keyLen int) (key []byte) { 34 | const md5Len = 16 35 | 36 | cnt := (keyLen-1)/md5Len + 1 37 | m := make([]byte, cnt*md5Len) 38 | copy(m, MD5Sum([]byte(password))) 39 | 40 | // Repeatedly call md5 until bytes generated is enough. 41 | // Each call to md5 uses data: prev md5 sum + password. 42 | d := make([]byte, md5Len+len(password)) 43 | start := 0 44 | for i := 1; i < cnt; i++ { 45 | start += md5Len 46 | copy(d, m[start-md5Len:start]) 47 | copy(d[md5Len:], password) 48 | copy(m[start:], MD5Sum(d)) 49 | } 50 | return m[:keyLen] 51 | } 52 | -------------------------------------------------------------------------------- /proxy/ssr/internal/tools/obfsutil.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/binary" 5 | "unsafe" 6 | ) 7 | 8 | func IsLittleEndian() bool { 9 | const N int = int(unsafe.Sizeof(0)) 10 | x := 0x1234 11 | p := unsafe.Pointer(&x) 12 | p2 := (*[N]byte)(p) 13 | if p2[0] == 0 { 14 | return false 15 | } else { 16 | return true 17 | } 18 | } 19 | 20 | type Shift128plusContext struct { 21 | v [2]uint64 22 | } 23 | 24 | func (ctx *Shift128plusContext) InitFromBin(bin []byte) { 25 | var fillBin [16]byte 26 | copy(fillBin[:], bin) 27 | 28 | ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) 29 | ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) 30 | } 31 | 32 | func (ctx *Shift128plusContext) InitFromBinDatalen(bin []byte, datalen int) { 33 | var fillBin [16]byte 34 | copy(fillBin[:], bin) 35 | binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen)) 36 | 37 | ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) 38 | ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) 39 | 40 | for i := 0; i < 4; i++ { 41 | ctx.Next() 42 | } 43 | } 44 | 45 | func (ctx *Shift128plusContext) Next() uint64 { 46 | x := ctx.v[0] 47 | y := ctx.v[1] 48 | ctx.v[0] = y 49 | x ^= x << 23 50 | x ^= y ^ (x >> 17) ^ (y >> 26) 51 | ctx.v[1] = x 52 | return x + y 53 | } 54 | -------------------------------------------------------------------------------- /proxy/tcp/tcp.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/meoww-bot/glider/pkg/log" 9 | "github.com/meoww-bot/glider/proxy" 10 | ) 11 | 12 | // TCP struct. 13 | type TCP struct { 14 | addr string 15 | dialer proxy.Dialer 16 | proxy proxy.Proxy 17 | } 18 | 19 | func init() { 20 | proxy.RegisterDialer("tcp", NewTCPDialer) 21 | proxy.RegisterServer("tcp", NewTCPServer) 22 | } 23 | 24 | // NewTCP returns a tcp struct. 25 | func NewTCP(s string, d proxy.Dialer, p proxy.Proxy) (*TCP, error) { 26 | u, err := url.Parse(s) 27 | if err != nil { 28 | log.F("[tcp] parse url err: %s", err) 29 | return nil, err 30 | } 31 | 32 | t := &TCP{ 33 | dialer: d, 34 | proxy: p, 35 | addr: u.Host, 36 | } 37 | 38 | return t, nil 39 | } 40 | 41 | // NewTCPDialer returns a tcp dialer. 42 | func NewTCPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 43 | return NewTCP(s, d, nil) 44 | } 45 | 46 | // NewTCPServer returns a tcp transport layer before the real server. 47 | func NewTCPServer(s string, p proxy.Proxy) (proxy.Server, error) { 48 | return NewTCP(s, nil, p) 49 | } 50 | 51 | // ListenAndServe listens on server's addr and serves connections. 52 | func (s *TCP) ListenAndServe() { 53 | l, err := net.Listen("tcp", s.addr) 54 | if err != nil { 55 | log.Fatalf("[tcp] failed to listen on %s: %v", s.addr, err) 56 | return 57 | } 58 | defer l.Close() 59 | 60 | log.F("[tcp] listening TCP on %s", s.addr) 61 | 62 | for { 63 | c, err := l.Accept() 64 | if err != nil { 65 | log.F("[tcp] failed to accept: %v", err) 66 | continue 67 | } 68 | 69 | go s.Serve(c) 70 | } 71 | } 72 | 73 | // Serve serves a connection. 74 | func (s *TCP) Serve(c net.Conn) { 75 | defer c.Close() 76 | 77 | if c, ok := c.(*net.TCPConn); ok { 78 | c.SetKeepAlive(true) 79 | } 80 | 81 | rc, dialer, err := s.proxy.Dial("tcp", "") 82 | if err != nil { 83 | log.F("[tcp] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err) 84 | s.proxy.Record(dialer, false) 85 | return 86 | } 87 | defer rc.Close() 88 | 89 | log.F("[tcp] %s <-> %s", c.RemoteAddr(), dialer.Addr()) 90 | 91 | if err = proxy.Relay(c, rc); err != nil { 92 | log.F("[tcp] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err) 93 | // record remote conn failure only 94 | if !strings.Contains(err.Error(), s.addr) { 95 | s.proxy.Record(dialer, false) 96 | } 97 | } 98 | } 99 | 100 | // Addr returns forwarder's address. 101 | func (s *TCP) Addr() string { 102 | if s.addr == "" { 103 | return s.dialer.Addr() 104 | } 105 | return s.addr 106 | } 107 | 108 | // Dial connects to the address addr on the network net via the proxy. 109 | func (s *TCP) Dial(network, addr string) (net.Conn, error) { 110 | return s.dialer.Dial("tcp", s.addr) 111 | } 112 | 113 | // DialUDP connects to the given address via the proxy. 114 | func (s *TCP) DialUDP(network, addr string) (net.PacketConn, error) { 115 | return nil, proxy.ErrNotSupported 116 | } 117 | -------------------------------------------------------------------------------- /proxy/trojan/client.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "net" 8 | "os" 9 | 10 | "github.com/meoww-bot/glider/pkg/log" 11 | "github.com/meoww-bot/glider/pkg/pool" 12 | "github.com/meoww-bot/glider/pkg/socks" 13 | "github.com/meoww-bot/glider/proxy" 14 | ) 15 | 16 | func init() { 17 | proxy.RegisterDialer("trojan", NewTrojanDialer) 18 | proxy.RegisterDialer("trojanc", NewClearTextDialer) // cleartext 19 | } 20 | 21 | // NewClearTextDialer returns a trojan cleartext proxy dialer. 22 | func NewClearTextDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 23 | t, err := NewTrojan(s, d, nil) 24 | if err != nil { 25 | return nil, fmt.Errorf("[trojanc] create instance error: %s", err) 26 | } 27 | t.withTLS = false 28 | return t, err 29 | } 30 | 31 | // NewTrojanDialer returns a trojan proxy dialer. 32 | func NewTrojanDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 33 | t, err := NewTrojan(s, d, nil) 34 | if err != nil { 35 | return nil, fmt.Errorf("[trojan] create instance error: %s", err) 36 | } 37 | 38 | t.tlsConfig = &tls.Config{ 39 | ServerName: t.serverName, 40 | InsecureSkipVerify: t.skipVerify, 41 | MinVersion: tls.VersionTLS12, 42 | } 43 | 44 | if t.certFile != "" { 45 | certData, err := os.ReadFile(t.certFile) 46 | if err != nil { 47 | return nil, fmt.Errorf("[trojan] read cert file error: %s", err) 48 | } 49 | 50 | certPool := x509.NewCertPool() 51 | if !certPool.AppendCertsFromPEM(certData) { 52 | return nil, fmt.Errorf("[trojan] can not append cert file: %s", t.certFile) 53 | } 54 | t.tlsConfig.RootCAs = certPool 55 | } 56 | 57 | return t, err 58 | } 59 | 60 | // Addr returns forwarder's address. 61 | func (s *Trojan) Addr() string { 62 | if s.addr == "" { 63 | return s.dialer.Addr() 64 | } 65 | return s.addr 66 | } 67 | 68 | // Dial connects to the address addr on the network net via the proxy. 69 | func (s *Trojan) Dial(network, addr string) (net.Conn, error) { 70 | return s.dial(network, addr) 71 | } 72 | 73 | func (s *Trojan) dial(network, addr string) (net.Conn, error) { 74 | rc, err := s.dialer.Dial("tcp", s.addr) 75 | if err != nil { 76 | log.F("[trojan]: dial to %s error: %s", s.addr, err) 77 | return nil, err 78 | } 79 | 80 | if s.withTLS { 81 | tlsConn := tls.Client(rc, s.tlsConfig) 82 | if err := tlsConn.Handshake(); err != nil { 83 | return nil, err 84 | } 85 | rc = tlsConn 86 | } 87 | 88 | buf := pool.GetBytesBuffer() 89 | defer pool.PutBytesBuffer(buf) 90 | 91 | buf.Write(s.pass[:]) 92 | buf.WriteString("\r\n") 93 | 94 | cmd := socks.CmdConnect 95 | if network == "udp" { 96 | cmd = socks.CmdUDPAssociate 97 | } 98 | buf.WriteByte(cmd) 99 | 100 | buf.Write(socks.ParseAddr(addr)) 101 | buf.WriteString("\r\n") 102 | _, err = rc.Write(buf.Bytes()) 103 | 104 | return rc, err 105 | } 106 | 107 | // DialUDP connects to the given address via the proxy. 108 | func (s *Trojan) DialUDP(network, addr string) (net.PacketConn, error) { 109 | c, err := s.dial("udp", addr) 110 | return NewPktConn(c, socks.ParseAddr(addr)), err 111 | } 112 | -------------------------------------------------------------------------------- /proxy/trojan/packet.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "net" 8 | 9 | "github.com/meoww-bot/glider/pkg/pool" 10 | "github.com/meoww-bot/glider/pkg/socks" 11 | ) 12 | 13 | // PktConn is a udp Packet.Conn. 14 | type PktConn struct { 15 | net.Conn 16 | target socks.Addr 17 | } 18 | 19 | // NewPktConn returns a PktConn. 20 | func NewPktConn(c net.Conn, target socks.Addr) *PktConn { 21 | return &PktConn{Conn: c, target: target} 22 | } 23 | 24 | // ReadFrom implements the necessary function of net.PacketConn. 25 | func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { 26 | // ATYP, DST.ADDR, DST.PORT 27 | tgtAddr, err := socks.ReadAddr(pc.Conn) 28 | if err != nil { 29 | return 0, nil, err 30 | } 31 | 32 | target, err := net.ResolveUDPAddr("udp", tgtAddr.String()) 33 | if err != nil { 34 | return 0, nil, err 35 | } 36 | 37 | // TODO: we know that we use it in proxy.CopyUDP and the length of b is enough, check it later. 38 | if len(b) < 2 { 39 | return 0, nil, errors.New("buf size is not enough") 40 | } 41 | 42 | // Length 43 | if _, err = io.ReadFull(pc.Conn, b[:2]); err != nil { 44 | return 0, nil, err 45 | } 46 | 47 | length := int(binary.BigEndian.Uint16(b[:2])) 48 | 49 | if len(b) < length { 50 | return 0, nil, errors.New("buf size is not enough") 51 | } 52 | 53 | // CRLF 54 | if _, err = io.ReadFull(pc.Conn, b[:2]); err != nil { 55 | return 0, nil, err 56 | } 57 | 58 | // Payload 59 | n, err := io.ReadFull(pc.Conn, b[:length]) 60 | if err != nil { 61 | return n, nil, err 62 | } 63 | 64 | return n, target, err 65 | } 66 | 67 | // WriteTo implements the necessary function of net.PacketConn. 68 | func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { 69 | target := pc.target 70 | if addr != nil { 71 | target = socks.ParseAddr(addr.String()) 72 | } 73 | 74 | if target == nil { 75 | return 0, errors.New("invalid addr") 76 | } 77 | 78 | buf := pool.GetBytesBuffer() 79 | defer pool.PutBytesBuffer(buf) 80 | 81 | tgtLen, _ := buf.Write(target) 82 | binary.Write(buf, binary.BigEndian, uint16(len(b))) 83 | buf.WriteString("\r\n") 84 | buf.Write(b) 85 | 86 | n, err := pc.Write(buf.Bytes()) 87 | if n > tgtLen+4 { 88 | return n - tgtLen - 4, nil 89 | } 90 | 91 | return 0, err 92 | } 93 | -------------------------------------------------------------------------------- /proxy/trojan/trojan.go: -------------------------------------------------------------------------------- 1 | // protocol spec: 2 | // https://trojan-gfw.github.io/trojan/protocol 3 | 4 | package trojan 5 | 6 | import ( 7 | "crypto/sha256" 8 | "crypto/tls" 9 | "encoding/hex" 10 | "errors" 11 | "fmt" 12 | "net" 13 | "net/url" 14 | "strings" 15 | 16 | "github.com/meoww-bot/glider/proxy" 17 | ) 18 | 19 | // Trojan is a base trojan struct. 20 | type Trojan struct { 21 | dialer proxy.Dialer 22 | proxy proxy.Proxy 23 | addr string 24 | pass [56]byte 25 | withTLS bool 26 | tlsConfig *tls.Config 27 | serverName string 28 | skipVerify bool 29 | certFile string 30 | keyFile string 31 | fallback string 32 | } 33 | 34 | // NewTrojan returns a trojan proxy. 35 | func NewTrojan(s string, d proxy.Dialer, p proxy.Proxy) (*Trojan, error) { 36 | u, err := url.Parse(s) 37 | if err != nil { 38 | return nil, fmt.Errorf("parse url err: %s", err) 39 | } 40 | 41 | query := u.Query() 42 | t := &Trojan{ 43 | dialer: d, 44 | proxy: p, 45 | addr: u.Host, 46 | withTLS: true, 47 | skipVerify: true, 48 | serverName: query.Get("sni"), 49 | certFile: query.Get("cert"), 50 | keyFile: query.Get("key"), 51 | fallback: query.Get("fallback"), 52 | } 53 | 54 | if t.addr != "" { 55 | if _, port, _ := net.SplitHostPort(t.addr); port == "" { 56 | t.addr = net.JoinHostPort(t.addr, "443") 57 | } 58 | if t.serverName == "" { 59 | t.serverName = t.addr[:strings.LastIndex(t.addr, ":")] 60 | } 61 | } 62 | 63 | // pass 64 | pass := u.User.Username() 65 | if pass == "" { 66 | return nil, errors.New("[trojan] password must be specified") 67 | } 68 | 69 | hash := sha256.New224() 70 | hash.Write([]byte(pass)) 71 | hex.Encode(t.pass[:], hash.Sum(nil)) 72 | 73 | return t, nil 74 | } 75 | 76 | func init() { 77 | proxy.AddUsage("trojan", ` 78 | Trojan client scheme: 79 | trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH] 80 | trojanc://pass@host:port (cleartext, without TLS) 81 | 82 | Trojan server scheme: 83 | trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1] 84 | trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS) 85 | `) 86 | } 87 | -------------------------------------------------------------------------------- /proxy/unix/client.go: -------------------------------------------------------------------------------- 1 | package unix 2 | 3 | import ( 4 | "net" 5 | "os" 6 | 7 | "github.com/meoww-bot/glider/proxy" 8 | ) 9 | 10 | func init() { 11 | proxy.RegisterDialer("unix", NewUnixDialer) 12 | } 13 | 14 | // NewUnixDialer returns a unix domain socket dialer. 15 | func NewUnixDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 16 | return NewUnix(s, d, nil) 17 | } 18 | 19 | // Addr returns forwarder's address. 20 | func (s *Unix) Addr() string { 21 | if s.addr == "" { 22 | return s.dialer.Addr() 23 | } 24 | return s.addr 25 | } 26 | 27 | // Dial connects to the address addr on the network net via the proxy. 28 | // NOTE: must be the first dialer in a chain 29 | func (s *Unix) Dial(network, addr string) (net.Conn, error) { 30 | return net.Dial("unix", s.addr) 31 | } 32 | 33 | // DialUDP connects to the given address via the proxy. 34 | // NOTE: must be the first dialer in a chain 35 | func (s *Unix) DialUDP(network, addr string) (net.PacketConn, error) { 36 | laddru := s.addru + "_" + addr 37 | os.Remove(laddru) 38 | 39 | luaddru, err := net.ResolveUnixAddr("unixgram", laddru) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | pc, err := net.ListenUnixgram("unixgram", luaddru) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return &PktConn{pc, laddru, luaddru, s.uaddru}, nil 50 | } 51 | 52 | // PktConn . 53 | type PktConn struct { 54 | *net.UnixConn 55 | addr string 56 | uaddr *net.UnixAddr 57 | writeAddr *net.UnixAddr 58 | } 59 | 60 | // ReadFrom overrides the original function from net.PacketConn. 61 | func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { 62 | n, _, err := pc.UnixConn.ReadFrom(b) 63 | return n, pc.uaddr, err 64 | } 65 | 66 | // WriteTo overrides the original function from net.PacketConn. 67 | func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { 68 | return pc.UnixConn.WriteTo(b, pc.writeAddr) 69 | } 70 | 71 | // Close overrides the original function from net.PacketConn. 72 | func (pc *PktConn) Close() error { 73 | pc.UnixConn.Close() 74 | os.Remove(pc.addr) 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /proxy/unix/unix.go: -------------------------------------------------------------------------------- 1 | package unix 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | 7 | "github.com/meoww-bot/glider/pkg/log" 8 | "github.com/meoww-bot/glider/proxy" 9 | ) 10 | 11 | // Unix domain socket struct. 12 | type Unix struct { 13 | dialer proxy.Dialer 14 | proxy proxy.Proxy 15 | server proxy.Server 16 | 17 | addr string // addr for tcp 18 | uaddr *net.UnixAddr 19 | 20 | addru string // addr for udp (datagram) 21 | uaddru *net.UnixAddr 22 | } 23 | 24 | // NewUnix returns unix domain socket proxy. 25 | func NewUnix(s string, d proxy.Dialer, p proxy.Proxy) (*Unix, error) { 26 | u, err := url.Parse(s) 27 | if err != nil { 28 | log.F("[unix] parse url err: %s", err) 29 | return nil, err 30 | } 31 | 32 | unix := &Unix{ 33 | dialer: d, 34 | proxy: p, 35 | addr: u.Path, 36 | addru: u.Path + "u", 37 | } 38 | 39 | unix.uaddr, err = net.ResolveUnixAddr("unixgram", unix.addr) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | unix.uaddru, err = net.ResolveUnixAddr("unixgram", unix.addru) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return unix, nil 50 | } 51 | 52 | func init() { 53 | proxy.AddUsage("unix", ` 54 | Unix domain socket scheme: 55 | unix://path 56 | `) 57 | } 58 | -------------------------------------------------------------------------------- /proxy/vless/addr.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | "net/netip" 8 | "strconv" 9 | 10 | "github.com/meoww-bot/glider/pkg/pool" 11 | ) 12 | 13 | // Atyp is vless addr type. 14 | type Atyp byte 15 | 16 | // Atyp 17 | const ( 18 | AtypErr Atyp = 0 19 | AtypIP4 Atyp = 1 20 | AtypDomain Atyp = 2 21 | AtypIP6 Atyp = 3 22 | ) 23 | 24 | // Addr is vless addr. 25 | type Addr []byte 26 | 27 | // MaxHostLen is the maximum size of host in bytes. 28 | const MaxHostLen = 255 29 | 30 | // Port is vless addr port. 31 | type Port uint16 32 | 33 | // ParseAddr parses the address in string s. 34 | func ParseAddr(s string) (Atyp, Addr, Port, error) { 35 | host, port, err := net.SplitHostPort(s) 36 | if err != nil { 37 | return 0, nil, 0, err 38 | } 39 | 40 | var addr Addr 41 | var atyp Atyp = AtypIP4 42 | if ip, err := netip.ParseAddr(host); err == nil { 43 | if ip.Is6() { 44 | atyp = AtypIP6 45 | } 46 | addr = ip.AsSlice() 47 | } else { 48 | if len(host) > MaxHostLen { 49 | return 0, nil, 0, err 50 | } 51 | addr = make([]byte, 1+len(host)) 52 | atyp = AtypDomain 53 | addr[0] = byte(len(host)) 54 | copy(addr[1:], host) 55 | } 56 | 57 | portnum, err := strconv.ParseUint(port, 10, 16) 58 | if err != nil { 59 | return 0, nil, 0, err 60 | } 61 | 62 | return atyp, addr, Port(portnum), err 63 | } 64 | 65 | // ReadAddr reads just enough bytes from r to get addr. 66 | func ReadAddr(r io.Reader) (atyp Atyp, host Addr, port Port, err error) { 67 | buf := pool.GetBuffer(2) 68 | defer pool.PutBuffer(buf) 69 | 70 | // port 71 | _, err = io.ReadFull(r, buf[:2]) 72 | if err != nil { 73 | return 74 | } 75 | port = Port(binary.BigEndian.Uint16(buf[:2])) 76 | 77 | // atyp 78 | _, err = io.ReadFull(r, buf[:1]) 79 | if err != nil { 80 | return 81 | } 82 | atyp = Atyp(buf[0]) 83 | 84 | switch atyp { 85 | case AtypIP4: 86 | host = make([]byte, net.IPv4len) 87 | _, err = io.ReadFull(r, host) 88 | return 89 | case AtypIP6: 90 | host = make([]byte, net.IPv6len) 91 | _, err = io.ReadFull(r, host) 92 | return 93 | case AtypDomain: 94 | _, err = io.ReadFull(r, buf[:1]) 95 | if err != nil { 96 | return 97 | } 98 | host = make([]byte, int(buf[0])) 99 | _, err = io.ReadFull(r, host) 100 | return 101 | } 102 | 103 | return 104 | } 105 | 106 | // ReadAddrString reads just enough bytes from r to get addr string. 107 | func ReadAddrString(r io.Reader) (string, error) { 108 | atyp, host, port, err := ReadAddr(r) 109 | if err != nil { 110 | return "", err 111 | } 112 | return AddrString(atyp, host, port), nil 113 | } 114 | 115 | // AddrString returns a addr string in format of "host:port". 116 | func AddrString(atyp Atyp, addr Addr, port Port) string { 117 | var host string 118 | 119 | switch atyp { 120 | case AtypIP4, AtypIP6: 121 | host = net.IP(addr).String() 122 | case AtypDomain: 123 | host = string(addr) 124 | } 125 | 126 | return net.JoinHostPort(host, strconv.Itoa(int(port))) 127 | } 128 | -------------------------------------------------------------------------------- /proxy/vless/client.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "net" 8 | 9 | "github.com/meoww-bot/glider/pkg/log" 10 | "github.com/meoww-bot/glider/pkg/pool" 11 | "github.com/meoww-bot/glider/proxy" 12 | ) 13 | 14 | // NewVLessDialer returns a vless proxy dialer. 15 | func NewVLessDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) { 16 | return NewVLess(s, dialer, nil) 17 | } 18 | 19 | // Addr returns forwarder's address. 20 | func (s *VLess) Addr() string { 21 | if s.addr == "" { 22 | return s.dialer.Addr() 23 | } 24 | return s.addr 25 | } 26 | 27 | // Dial connects to the address addr on the network net via the proxy. 28 | func (s *VLess) Dial(network, addr string) (net.Conn, error) { 29 | return s.dial(network, addr) 30 | } 31 | 32 | func (s *VLess) dial(network, addr string) (net.Conn, error) { 33 | rc, err := s.dialer.Dial("tcp", s.addr) 34 | if err != nil { 35 | log.F("[vless]: dial to %s error: %s", s.addr, err) 36 | return nil, err 37 | } 38 | return NewClientConn(rc, s.uuid, network, addr) 39 | } 40 | 41 | // DialUDP connects to the given address via the proxy. 42 | func (s *VLess) DialUDP(network, addr string) (net.PacketConn, error) { 43 | c, err := s.dial("udp", addr) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | tgtAddr, err := net.ResolveUDPAddr("udp", addr) 49 | if err != nil { 50 | log.F("[vless] error in ResolveUDPAddr: %v", err) 51 | return nil, err 52 | } 53 | 54 | return NewPktConn(c, tgtAddr), err 55 | } 56 | 57 | // ClientConn is a vless client connection. 58 | type ClientConn struct { 59 | net.Conn 60 | rcved bool 61 | } 62 | 63 | // NewClientConn returns a new vless client conn. 64 | func NewClientConn(c net.Conn, uuid [16]byte, network, target string) (*ClientConn, error) { 65 | atyp, addr, port, err := ParseAddr(target) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | buf := pool.GetBytesBuffer() 71 | defer pool.PutBytesBuffer(buf) 72 | 73 | buf.WriteByte(Version) // ver 74 | buf.Write(uuid[:]) // uuid 75 | buf.WriteByte(0) // addonLen 76 | 77 | cmd := CmdTCP 78 | if network == "udp" { 79 | cmd = CmdUDP 80 | } 81 | buf.WriteByte(byte(cmd)) // cmd 82 | 83 | // target 84 | err = binary.Write(buf, binary.BigEndian, uint16(port)) // port 85 | if err != nil { 86 | return nil, err 87 | } 88 | buf.WriteByte(byte(atyp)) // atyp 89 | buf.Write(addr) //addr 90 | 91 | _, err = c.Write(buf.Bytes()) 92 | return &ClientConn{Conn: c}, err 93 | } 94 | 95 | func (c *ClientConn) Read(b []byte) (n int, err error) { 96 | if !c.rcved { 97 | buf := pool.GetBuffer(2) 98 | defer pool.PutBuffer(buf) 99 | 100 | n, err = io.ReadFull(c.Conn, buf) 101 | if err != nil { 102 | return 103 | } 104 | 105 | if buf[0] != Version { 106 | return n, errors.New("version not supported") 107 | } 108 | 109 | if addLen := int64(buf[1]); addLen > 0 { 110 | proxy.CopyN(io.Discard, c.Conn, addLen) 111 | } 112 | c.rcved = true 113 | } 114 | 115 | return c.Conn.Read(b) 116 | } 117 | -------------------------------------------------------------------------------- /proxy/vless/packet.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "net" 8 | 9 | "github.com/meoww-bot/glider/pkg/pool" 10 | ) 11 | 12 | // PktConn is a udp Packet.Conn. 13 | type PktConn struct { 14 | net.Conn 15 | target *net.UDPAddr 16 | } 17 | 18 | // NewPktConn returns a PktConn. 19 | func NewPktConn(c net.Conn, target *net.UDPAddr) *PktConn { 20 | return &PktConn{Conn: c, target: target} 21 | } 22 | 23 | // ReadFrom implements the necessary function of net.PacketConn. 24 | func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { 25 | if len(b) < 2 { 26 | return 0, pc.target, errors.New("buf size is not enough") 27 | } 28 | 29 | // Length 30 | if _, err := io.ReadFull(pc.Conn, b[:2]); err != nil { 31 | return 0, pc.target, err 32 | } 33 | length := int(binary.BigEndian.Uint16(b[:2])) 34 | 35 | if len(b) < length { 36 | return 0, pc.target, errors.New("buf size is not enough") 37 | } 38 | 39 | // Payload 40 | n, err := io.ReadFull(pc.Conn, b[:length]) 41 | return n, pc.target, err 42 | } 43 | 44 | // WriteTo implements the necessary function of net.PacketConn. 45 | func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { 46 | buf := pool.GetBytesBuffer() 47 | defer pool.PutBytesBuffer(buf) 48 | 49 | binary.Write(buf, binary.BigEndian, uint16(len(b))) 50 | buf.Write(b) 51 | 52 | n, err := pc.Write(buf.Bytes()) 53 | if n > 2 { 54 | return n - 2, err 55 | } 56 | return 0, err 57 | } 58 | -------------------------------------------------------------------------------- /proxy/vless/vless.go: -------------------------------------------------------------------------------- 1 | package vless 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "errors" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/meoww-bot/glider/proxy" 11 | ) 12 | 13 | // Version of vless protocol. 14 | const Version byte = 0 15 | 16 | // CmdType is vless cmd type. 17 | type CmdType byte 18 | 19 | // CMD types. 20 | const ( 21 | CmdErr CmdType = 0 22 | CmdTCP CmdType = 1 23 | CmdUDP CmdType = 2 24 | ) 25 | 26 | // VLess struct. 27 | type VLess struct { 28 | dialer proxy.Dialer 29 | proxy proxy.Proxy 30 | addr string 31 | uuid [16]byte 32 | fallback string 33 | } 34 | 35 | func init() { 36 | proxy.RegisterDialer("vless", NewVLessDialer) 37 | proxy.RegisterServer("vless", NewVLessServer) 38 | } 39 | 40 | // NewVLess returns a vless proxy. 41 | func NewVLess(s string, d proxy.Dialer, p proxy.Proxy) (*VLess, error) { 42 | u, err := url.Parse(s) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | addr := u.Host 48 | uuid, err := StrToUUID(u.User.Username()) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | query := u.Query() 54 | v := &VLess{ 55 | dialer: d, 56 | proxy: p, 57 | addr: addr, 58 | uuid: uuid, 59 | fallback: query.Get("fallback"), 60 | } 61 | 62 | return v, nil 63 | } 64 | 65 | // StrToUUID converts string to uuid. 66 | func StrToUUID(s string) (uuid [16]byte, err error) { 67 | if len(s) >= 1 && len(s) <= 30 { 68 | h := sha1.New() 69 | h.Write(uuid[:]) 70 | h.Write([]byte(s)) 71 | u := h.Sum(nil)[:16] 72 | u[6] = (u[6] & 0x0f) | (5 << 4) 73 | u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) 74 | copy(uuid[:], u) 75 | return 76 | } 77 | b := []byte(strings.Replace(s, "-", "", -1)) 78 | if len(b) != 32 { 79 | return uuid, errors.New("invalid UUID: " + s) 80 | } 81 | _, err = hex.Decode(uuid[:], b) 82 | return 83 | } 84 | 85 | func init() { 86 | proxy.AddUsage("vless", ` 87 | VLESS scheme: 88 | vless://uuid@host:port[?fallback=127.0.0.1:80] 89 | `) 90 | } 91 | -------------------------------------------------------------------------------- /proxy/vmess/addr.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | "strconv" 7 | ) 8 | 9 | // Atyp is vmess addr type. 10 | type Atyp byte 11 | 12 | // Atyp 13 | const ( 14 | AtypErr Atyp = 0 15 | AtypIP4 Atyp = 1 16 | AtypDomain Atyp = 2 17 | AtypIP6 Atyp = 3 18 | ) 19 | 20 | // Addr is vmess addr. 21 | type Addr []byte 22 | 23 | // MaxHostLen is the maximum size of host in bytes. 24 | const MaxHostLen = 255 25 | 26 | // Port is vmess addr port. 27 | type Port uint16 28 | 29 | // ParseAddr parses the address in string s. 30 | func ParseAddr(s string) (Atyp, Addr, Port, error) { 31 | host, port, err := net.SplitHostPort(s) 32 | if err != nil { 33 | return 0, nil, 0, err 34 | } 35 | 36 | var addr Addr 37 | var atyp Atyp = AtypIP4 38 | if ip, err := netip.ParseAddr(host); err == nil { 39 | if ip.Is6() { 40 | atyp = AtypIP6 41 | } 42 | addr = ip.AsSlice() 43 | } else { 44 | if len(host) > MaxHostLen { 45 | return 0, nil, 0, err 46 | } 47 | addr = make([]byte, 1+len(host)) 48 | atyp = AtypDomain 49 | addr[0] = byte(len(host)) 50 | copy(addr[1:], host) 51 | } 52 | 53 | portnum, err := strconv.ParseUint(port, 10, 16) 54 | if err != nil { 55 | return 0, nil, 0, err 56 | } 57 | 58 | return atyp, addr, Port(portnum), err 59 | } 60 | -------------------------------------------------------------------------------- /proxy/vmess/aead.go: -------------------------------------------------------------------------------- 1 | // protocol: 2 | // format: [data length] [data] 3 | // sizes: 2 bytes, n bytes 4 | // max(n): 2^14 bytes 5 | // [data]: [encrypted payload] + [Overhead] 6 | 7 | package vmess 8 | 9 | import ( 10 | "crypto/cipher" 11 | "encoding/binary" 12 | "io" 13 | "net" 14 | 15 | "github.com/meoww-bot/glider/pkg/pool" 16 | ) 17 | 18 | type aeadWriter struct { 19 | io.Writer 20 | chunkSizeEncoder ChunkSizeEncoder 21 | cipher.AEAD 22 | nonce [32]byte 23 | count uint16 24 | } 25 | 26 | // AEADWriter returns a aead writer. 27 | func AEADWriter(w io.Writer, aead cipher.AEAD, iv []byte, chunkSizeEncoder ChunkSizeEncoder) io.Writer { 28 | aw := &aeadWriter{Writer: w, AEAD: aead, chunkSizeEncoder: chunkSizeEncoder} 29 | copy(aw.nonce[2:], iv[2:aead.NonceSize()]) 30 | return aw 31 | } 32 | 33 | func (w *aeadWriter) Write(b []byte) (n int, err error) { 34 | buf := pool.GetBuffer(chunkSize) 35 | defer pool.PutBuffer(buf) 36 | 37 | lenBuf := make([]byte, w.chunkSizeEncoder.SizeBytes()) 38 | var writeLen, dataLen int 39 | 40 | nonce := w.nonce[:w.NonceSize()] 41 | for left := len(b); left != 0; { 42 | writeLen = left + w.Overhead() 43 | if writeLen > chunkSize { 44 | writeLen = chunkSize 45 | } 46 | dataLen = writeLen - w.Overhead() 47 | 48 | w.chunkSizeEncoder.Encode(uint16(writeLen), lenBuf) 49 | binary.BigEndian.PutUint16(nonce[:2], w.count) 50 | 51 | w.Seal(buf[:0], nonce, b[n:n+dataLen], nil) 52 | w.count++ 53 | 54 | if _, err = (&net.Buffers{lenBuf[:], buf[:writeLen]}).WriteTo(w.Writer); err != nil { 55 | break 56 | } 57 | 58 | n += dataLen 59 | left -= dataLen 60 | } 61 | 62 | return 63 | } 64 | 65 | type aeadReader struct { 66 | io.Reader 67 | chunkSizeDecoder ChunkSizeDecoder 68 | cipher.AEAD 69 | nonce [32]byte 70 | count uint16 71 | buf []byte 72 | offset int 73 | } 74 | 75 | // AEADReader returns a aead reader. 76 | func AEADReader(r io.Reader, aead cipher.AEAD, iv []byte, chunkSizeDecoder ChunkSizeDecoder) io.Reader { 77 | ar := &aeadReader{Reader: r, AEAD: aead, chunkSizeDecoder: chunkSizeDecoder} 78 | copy(ar.nonce[2:], iv[2:aead.NonceSize()]) 79 | return ar 80 | } 81 | 82 | func (r *aeadReader) read(p []byte) (int, error) { 83 | if _, err := io.ReadFull(r.Reader, p[:r.chunkSizeDecoder.SizeBytes()]); err != nil { 84 | return 0, err 85 | } 86 | 87 | size, err := r.chunkSizeDecoder.Decode(p[:r.chunkSizeDecoder.SizeBytes()]) 88 | if err != nil { 89 | return 0, err 90 | } 91 | 92 | if int(size) <= r.Overhead() || int(size) > len(p) { 93 | return 0, io.EOF 94 | } 95 | 96 | p = p[:size] 97 | if _, err := io.ReadFull(r.Reader, p); err != nil { 98 | return 0, err 99 | } 100 | 101 | binary.BigEndian.PutUint16(r.nonce[:2], r.count) 102 | _, err = r.Open(p[:0], r.nonce[:r.NonceSize()], p, nil) 103 | r.count++ 104 | 105 | if err != nil { 106 | return 0, err 107 | } 108 | 109 | return int(size) - r.Overhead(), nil 110 | } 111 | 112 | func (r *aeadReader) Read(p []byte) (int, error) { 113 | if r.buf == nil { 114 | if len(p) >= chunkSize { 115 | return r.read(p) 116 | } 117 | 118 | buf := pool.GetBuffer(chunkSize) 119 | n, err := r.read(buf) 120 | if err != nil || n == 0 { 121 | pool.PutBuffer(buf) 122 | return 0, err 123 | } 124 | 125 | r.buf = buf[:n] 126 | r.offset = 0 127 | } 128 | 129 | n := copy(p, r.buf[r.offset:]) 130 | r.offset += n 131 | if r.offset == len(r.buf) { 132 | pool.PutBuffer(r.buf) 133 | r.buf = nil 134 | } 135 | 136 | return n, nil 137 | } 138 | -------------------------------------------------------------------------------- /proxy/vmess/chunk.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "io" 5 | "net" 6 | ) 7 | 8 | const ( 9 | chunkSize = 16 << 10 10 | ) 11 | 12 | type chunkedWriter struct { 13 | io.Writer 14 | chunkSizeEncoder ChunkSizeEncoder 15 | buf []byte 16 | } 17 | 18 | // ChunkedWriter returns a chunked writer. 19 | func ChunkedWriter(w io.Writer, chunkSizeEncoder ChunkSizeEncoder) io.Writer { 20 | return &chunkedWriter{Writer: w, chunkSizeEncoder: chunkSizeEncoder, buf: make([]byte, chunkSizeEncoder.SizeBytes())} 21 | } 22 | 23 | func (w *chunkedWriter) Write(p []byte) (n int, err error) { 24 | var dataLen int 25 | for left := len(p); left != 0; { 26 | dataLen = left 27 | if dataLen > chunkSize { 28 | dataLen = chunkSize 29 | } 30 | w.chunkSizeEncoder.Encode(uint16(dataLen), w.buf) 31 | if _, err = (&net.Buffers{w.buf[:], p[n : n+dataLen]}).WriteTo(w.Writer); err != nil { 32 | break 33 | } 34 | 35 | n += dataLen 36 | left -= dataLen 37 | } 38 | return 39 | } 40 | 41 | type chunkedReader struct { 42 | io.Reader 43 | chunkSizeDecoder ChunkSizeDecoder 44 | buf []byte 45 | left int 46 | } 47 | 48 | // ChunkedReader returns a chunked reader. 49 | func ChunkedReader(r io.Reader, chunkSizeDecoder ChunkSizeDecoder) io.Reader { 50 | return &chunkedReader{Reader: r, chunkSizeDecoder: chunkSizeDecoder, buf: make([]byte, chunkSizeDecoder.SizeBytes())} 51 | } 52 | 53 | func (r *chunkedReader) Read(p []byte) (int, error) { 54 | if r.left == 0 { 55 | // get length 56 | _, err := io.ReadFull(r.Reader, r.buf[:r.chunkSizeDecoder.SizeBytes()]) 57 | if err != nil { 58 | return 0, err 59 | } 60 | n, err := r.chunkSizeDecoder.Decode(r.buf[:]) 61 | if err != nil { 62 | return 0, err 63 | } 64 | r.left = int(n) 65 | 66 | // if left == 0, then this is the end 67 | if r.left == 0 { 68 | return 0, nil 69 | } 70 | } 71 | 72 | readLen := len(p) 73 | if readLen > r.left { 74 | readLen = r.left 75 | } 76 | 77 | n, err := r.Reader.Read(p[:readLen]) 78 | if err != nil { 79 | return 0, err 80 | } 81 | 82 | r.left -= n 83 | 84 | return n, err 85 | } 86 | -------------------------------------------------------------------------------- /proxy/vmess/packet.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // PktConn is a udp Packet.Conn. 8 | type PktConn struct { 9 | net.Conn 10 | target *net.UDPAddr 11 | } 12 | 13 | // NewPktConn returns a PktConn. 14 | func NewPktConn(c net.Conn, target *net.UDPAddr) *PktConn { 15 | return &PktConn{Conn: c, target: target} 16 | } 17 | 18 | // ReadFrom implements the necessary function of net.PacketConn. 19 | func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { 20 | n, err := pc.Read(b) 21 | return n, pc.target, err 22 | } 23 | 24 | // WriteTo implements the necessary function of net.PacketConn. 25 | func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { 26 | return pc.Write(b) 27 | } 28 | -------------------------------------------------------------------------------- /proxy/vmess/size.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "golang.org/x/crypto/sha3" 7 | ) 8 | 9 | // ChunkSizeEncoder is a utility class to encode size value into bytes. 10 | type ChunkSizeEncoder interface { 11 | SizeBytes() int32 12 | Encode(uint16, []byte) []byte 13 | } 14 | 15 | // ChunkSizeDecoder is a utility class to decode size value from bytes. 16 | type ChunkSizeDecoder interface { 17 | SizeBytes() int32 18 | Decode([]byte) (uint16, error) 19 | } 20 | 21 | // ShakeSizeParser implements ChunkSizeEncoder & ChunkSizeDecoder. 22 | type ShakeSizeParser struct { 23 | shake sha3.ShakeHash 24 | buffer [2]byte 25 | } 26 | 27 | // NewShakeSizeParser returns a new ShakeSizeParser. 28 | func NewShakeSizeParser(nonce []byte) *ShakeSizeParser { 29 | shake := sha3.NewShake128() 30 | shake.Write(nonce) 31 | return &ShakeSizeParser{ 32 | shake: shake, 33 | } 34 | } 35 | 36 | // SizeBytes implements ChunkSizeEncoder method. 37 | func (*ShakeSizeParser) SizeBytes() int32 { 38 | return 2 39 | } 40 | 41 | func (s *ShakeSizeParser) next() uint16 { 42 | s.shake.Read(s.buffer[:]) 43 | return binary.BigEndian.Uint16(s.buffer[:]) 44 | } 45 | 46 | // Decode implements ChunkSizeDecoder method. 47 | func (s *ShakeSizeParser) Decode(b []byte) (uint16, error) { 48 | mask := s.next() 49 | size := binary.BigEndian.Uint16(b) 50 | return mask ^ size, nil 51 | } 52 | 53 | // Encode implements ChunkSizeEncoder method. 54 | func (s *ShakeSizeParser) Encode(size uint16, b []byte) []byte { 55 | mask := s.next() 56 | binary.BigEndian.PutUint16(b, mask^size) 57 | return b[:2] 58 | } 59 | -------------------------------------------------------------------------------- /proxy/vmess/user.go: -------------------------------------------------------------------------------- 1 | package vmess 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "errors" 10 | "strings" 11 | "time" 12 | 13 | "github.com/meoww-bot/glider/pkg/pool" 14 | ) 15 | 16 | // User of vmess client. 17 | type User struct { 18 | UUID [16]byte 19 | CmdKey [16]byte 20 | } 21 | 22 | // NewUser returns a new user. 23 | func NewUser(uuid [16]byte) *User { 24 | u := &User{UUID: uuid} 25 | copy(u.CmdKey[:], GetKey(uuid)) 26 | return u 27 | } 28 | 29 | func nextID(oldID [16]byte) (newID [16]byte) { 30 | md5hash := md5.New() 31 | md5hash.Write(oldID[:]) 32 | md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) 33 | for { 34 | md5hash.Sum(newID[:0]) 35 | if !bytes.Equal(oldID[:], newID[:]) { 36 | return 37 | } 38 | md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) 39 | } 40 | } 41 | 42 | // GenAlterIDUsers generates users according to primary user's id and alterID. 43 | func (u *User) GenAlterIDUsers(alterID int) []*User { 44 | users := make([]*User, alterID) 45 | preID := u.UUID 46 | for i := 0; i < alterID; i++ { 47 | newID := nextID(preID) 48 | // NOTE: alterID user is a user which have a different uuid but a same cmdkey with the primary user. 49 | users[i] = &User{UUID: newID, CmdKey: u.CmdKey} 50 | preID = newID 51 | } 52 | 53 | return users 54 | } 55 | 56 | // StrToUUID converts string to uuid. 57 | func StrToUUID(s string) (uuid [16]byte, err error) { 58 | if len(s) >= 1 && len(s) <= 30 { 59 | h := sha1.New() 60 | h.Write(uuid[:]) 61 | h.Write([]byte(s)) 62 | u := h.Sum(nil)[:16] 63 | u[6] = (u[6] & 0x0f) | (5 << 4) 64 | u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) 65 | copy(uuid[:], u) 66 | return 67 | } 68 | b := []byte(strings.Replace(s, "-", "", -1)) 69 | if len(b) != 32 { 70 | return uuid, errors.New("invalid UUID: " + s) 71 | } 72 | _, err = hex.Decode(uuid[:], b) 73 | return 74 | } 75 | 76 | // GetKey returns the key of AES-128-CFB encrypter. 77 | // Key:MD5(UUID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21')) 78 | func GetKey(uuid [16]byte) []byte { 79 | md5hash := md5.New() 80 | md5hash.Write(uuid[:]) 81 | md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) 82 | return md5hash.Sum(nil) 83 | } 84 | 85 | // TimestampHash returns the iv of AES-128-CFB encrypter. 86 | // IV:MD5(X + X + X + X),X = []byte(timestamp.now) (8 bytes, Big Endian) 87 | func TimestampHash(t time.Time) []byte { 88 | ts := pool.GetBuffer(8) 89 | defer pool.PutBuffer(ts) 90 | 91 | binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix())) 92 | md5hash := md5.New() 93 | md5hash.Write(ts) 94 | md5hash.Write(ts) 95 | md5hash.Write(ts) 96 | md5hash.Write(ts) 97 | return md5hash.Sum(nil) 98 | } 99 | -------------------------------------------------------------------------------- /proxy/vsock/client.go: -------------------------------------------------------------------------------- 1 | package vsock 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/meoww-bot/glider/proxy" 7 | ) 8 | 9 | func init() { 10 | proxy.RegisterDialer("vsock", NewVSockDialer) 11 | } 12 | 13 | // NewVSockDialer returns a vm socket dialer. 14 | func NewVSockDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 15 | return NewVSock(s, d, nil) 16 | } 17 | 18 | // Dial connects to the address addr on the network net via the proxy. 19 | // NOTE: must be the first dialer in a chain 20 | func (s *vsock) Dial(network, addr string) (net.Conn, error) { 21 | return Dial(s.cid, s.port) 22 | } 23 | 24 | // DialUDP connects to the given address via the proxy. 25 | func (s *vsock) DialUDP(network, addr string) (net.PacketConn, error) { 26 | return nil, proxy.ErrNotSupported 27 | } 28 | -------------------------------------------------------------------------------- /proxy/vsock/server.go: -------------------------------------------------------------------------------- 1 | package vsock 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/meoww-bot/glider/pkg/log" 8 | "github.com/meoww-bot/glider/proxy" 9 | ) 10 | 11 | func init() { 12 | proxy.RegisterServer("vsock", NewVSockServer) 13 | } 14 | 15 | // NewVSockServer returns a vm socket server. 16 | func NewVSockServer(s string, p proxy.Proxy) (proxy.Server, error) { 17 | schemes := strings.SplitN(s, ",", 2) 18 | vsock, err := NewVSock(schemes[0], nil, p) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | if len(schemes) > 1 { 24 | vsock.server, err = proxy.ServerFromURL(schemes[1], p) 25 | if err != nil { 26 | return nil, err 27 | } 28 | } 29 | 30 | if vsock.cid == 0 { 31 | cid, err := ContextID() 32 | if err != nil { 33 | return nil, err 34 | } 35 | vsock.cid = cid 36 | } 37 | 38 | return vsock, nil 39 | } 40 | 41 | // ListenAndServe serves requests. 42 | func (s *vsock) ListenAndServe() { 43 | l, err := Listen(s.cid, s.port) 44 | if err != nil { 45 | log.Fatalf("[vsock] failed to listen: %v", err) 46 | return 47 | } 48 | defer l.Close() 49 | 50 | log.F("[vsock] Listening on %s", l.Addr()) 51 | 52 | for { 53 | c, err := l.Accept() 54 | if err != nil { 55 | log.F("[vsock] failed to accept: %v", err) 56 | continue 57 | } 58 | 59 | go s.Serve(c) 60 | } 61 | } 62 | 63 | // Serve serves requests. 64 | func (s *vsock) Serve(c net.Conn) { 65 | if s.server != nil { 66 | s.server.Serve(c) 67 | return 68 | } 69 | 70 | defer c.Close() 71 | 72 | rc, dialer, err := s.proxy.Dial("tcp", "") 73 | if err != nil { 74 | log.F("[vsock] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err) 75 | s.proxy.Record(dialer, false) 76 | return 77 | } 78 | defer rc.Close() 79 | 80 | log.F("[vsock] %s <-> %s", c.RemoteAddr(), dialer.Addr()) 81 | 82 | if err = proxy.Relay(c, rc); err != nil { 83 | log.F("[vsock] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err) 84 | // record remote conn failure only 85 | if !strings.Contains(err.Error(), s.addr) { 86 | s.proxy.Record(dialer, false) 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /proxy/vsock/vsock.go: -------------------------------------------------------------------------------- 1 | package vsock 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/meoww-bot/glider/pkg/log" 9 | "github.com/meoww-bot/glider/proxy" 10 | ) 11 | 12 | type vsock struct { 13 | dialer proxy.Dialer 14 | proxy proxy.Proxy 15 | server proxy.Server 16 | addr string 17 | cid, port uint32 18 | } 19 | 20 | // NewVSock returns vm socket proxy. 21 | func NewVSock(s string, d proxy.Dialer, p proxy.Proxy) (*vsock, error) { 22 | u, err := url.Parse(s) 23 | if err != nil { 24 | log.F("[vsock] parse url err: %s", err) 25 | return nil, err 26 | } 27 | 28 | v := &vsock{dialer: d, proxy: p, addr: u.Host} 29 | if hostStr, portStr, _ := net.SplitHostPort(v.addr); portStr != "" { 30 | if hostStr != "" { 31 | host, err := strconv.ParseUint(hostStr, 10, 32) 32 | if err != nil { 33 | log.F("[vsock] parse cid err: %s", err) 34 | return nil, err 35 | } 36 | v.cid = uint32(host) 37 | } 38 | 39 | port, err := strconv.ParseUint(portStr, 10, 32) 40 | if err != nil { 41 | log.F("[vsock] parse port err: %s", err) 42 | return nil, err 43 | } 44 | v.port = uint32(port) 45 | } 46 | 47 | return v, nil 48 | } 49 | 50 | // Addr returns forwarder's address. 51 | func (s *vsock) Addr() string { 52 | if s.addr == "" { 53 | return s.dialer.Addr() 54 | } 55 | return s.addr 56 | } 57 | 58 | func init() { 59 | proxy.AddUsage("vsock", ` 60 | VM socket scheme(linux only): 61 | vsock://[CID]:port 62 | 63 | if you want to listen on any address, just set CID to 4294967295. 64 | `) 65 | } 66 | -------------------------------------------------------------------------------- /proxy/ws/client.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/textproto" 11 | "os" 12 | 13 | "github.com/meoww-bot/glider/pkg/pool" 14 | "github.com/meoww-bot/glider/proxy" 15 | ) 16 | 17 | // NewWSDialer returns a ws proxy dialer. 18 | func NewWSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 19 | w, err := NewWS(s, d, nil, false) 20 | if err != nil { 21 | return nil, fmt.Errorf("[ws] create instance error: %s", err) 22 | } 23 | return w, err 24 | } 25 | 26 | // NewWSSDialer returns a wss proxy dialer. 27 | func NewWSSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { 28 | w, err := NewWS(s, d, nil, true) 29 | if err != nil { 30 | return nil, fmt.Errorf("[wss] create instance error: %s", err) 31 | } 32 | 33 | w.tlsConfig = &tls.Config{ 34 | ServerName: w.serverName, 35 | InsecureSkipVerify: w.skipVerify, 36 | MinVersion: tls.VersionTLS12, 37 | } 38 | 39 | if w.certFile != "" { 40 | certData, err := os.ReadFile(w.certFile) 41 | if err != nil { 42 | return nil, fmt.Errorf("[wss] read cert file error: %s", err) 43 | } 44 | 45 | certPool := x509.NewCertPool() 46 | if !certPool.AppendCertsFromPEM(certData) { 47 | return nil, fmt.Errorf("[wss] can not append cert file: %s", w.certFile) 48 | } 49 | w.tlsConfig.RootCAs = certPool 50 | } 51 | 52 | return w, err 53 | } 54 | 55 | // Addr returns forwarder's address. 56 | func (s *WS) Addr() string { 57 | if s.addr == "" { 58 | return s.dialer.Addr() 59 | } 60 | return s.addr 61 | } 62 | 63 | // Dial connects to the address addr on the network net via the proxy. 64 | func (s *WS) Dial(network, addr string) (net.Conn, error) { 65 | rc, err := s.dialer.Dial("tcp", s.addr) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if s.withTLS { 71 | tlsConn := tls.Client(rc, s.tlsConfig) 72 | if err := tlsConn.Handshake(); err != nil { 73 | return nil, err 74 | } 75 | rc = tlsConn 76 | } 77 | 78 | return s.NewClientConn(rc) 79 | } 80 | 81 | // DialUDP connects to the given address via the proxy. 82 | func (s *WS) DialUDP(network, addr string) (net.PacketConn, error) { 83 | return nil, proxy.ErrNotSupported 84 | } 85 | 86 | // ClientConn is a connection to ws server. 87 | type ClientConn struct { 88 | net.Conn 89 | reader io.Reader 90 | writer io.Writer 91 | } 92 | 93 | // NewClientConn creates a new ws client connection. 94 | func (s *WS) NewClientConn(rc net.Conn) (*ClientConn, error) { 95 | conn := &ClientConn{Conn: rc} 96 | return conn, conn.Handshake(s.host, s.path, s.origin) 97 | } 98 | 99 | // Handshake handshakes with the server using HTTP to request a protocol upgrade. 100 | func (c *ClientConn) Handshake(host, path, origin string) error { 101 | clientKey := generateClientKey() 102 | 103 | buf := pool.GetBytesBuffer() 104 | defer pool.PutBytesBuffer(buf) 105 | 106 | buf.WriteString("GET " + path + " HTTP/1.1\r\n") 107 | buf.WriteString("Host: " + host + "\r\n") 108 | buf.WriteString("Upgrade: websocket\r\n") 109 | buf.WriteString("Connection: Upgrade\r\n") 110 | if origin != "" { 111 | buf.WriteString("Origin: http://" + origin + "\r\n") 112 | } 113 | buf.WriteString("Sec-WebSocket-Key: " + clientKey + "\r\n") 114 | buf.WriteString("Sec-WebSocket-Version: 13\r\n") 115 | buf.WriteString(("\r\n")) 116 | 117 | if _, err := c.Conn.Write(buf.Bytes()); err != nil { 118 | return err 119 | } 120 | 121 | br := pool.GetBufReader(c.Conn) 122 | defer pool.PutBufReader(br) 123 | 124 | tpr := textproto.NewReader(br) 125 | line, err := tpr.ReadLine() 126 | if err != nil { 127 | return err 128 | } 129 | 130 | _, code, _, ok := parseFirstLine(line) 131 | if !ok || code != "101" { 132 | return errors.New("[ws] error in ws handshake, got wrong response: " + line) 133 | } 134 | 135 | respHeader, err := tpr.ReadMIMEHeader() 136 | if err != nil { 137 | return err 138 | } 139 | 140 | serverKey := respHeader.Get("Sec-WebSocket-Accept") 141 | if serverKey != computeServerKey(clientKey) { 142 | return errors.New("[ws] error in ws handshake, got wrong Sec-Websocket-Key") 143 | } 144 | 145 | return nil 146 | } 147 | 148 | func (c *ClientConn) Write(b []byte) (n int, err error) { 149 | if c.writer == nil { 150 | c.writer = FrameWriter(c.Conn, false) 151 | } 152 | return c.writer.Write(b) 153 | } 154 | 155 | func (c *ClientConn) Read(b []byte) (n int, err error) { 156 | if c.reader == nil { 157 | c.reader = FrameReader(c.Conn, false) 158 | } 159 | return c.reader.Read(b) 160 | } 161 | -------------------------------------------------------------------------------- /proxy/ws/ws.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha1" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "fmt" 9 | "net" 10 | "net/url" 11 | "strings" 12 | 13 | "github.com/meoww-bot/glider/pkg/pool" 14 | "github.com/meoww-bot/glider/proxy" 15 | ) 16 | 17 | var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 18 | 19 | func init() { 20 | proxy.RegisterDialer("ws", NewWSDialer) 21 | proxy.RegisterServer("ws", NewWSServer) 22 | proxy.RegisterDialer("wss", NewWSSDialer) 23 | proxy.RegisterServer("wss", NewWSSServer) 24 | } 25 | 26 | // WS is the base ws proxy struct. 27 | type WS struct { 28 | dialer proxy.Dialer 29 | proxy proxy.Proxy 30 | addr string 31 | host string 32 | path string 33 | origin string 34 | withTLS bool 35 | tlsConfig *tls.Config 36 | serverName string 37 | skipVerify bool 38 | certFile string 39 | keyFile string 40 | server proxy.Server 41 | } 42 | 43 | // NewWS returns a websocket proxy. 44 | func NewWS(s string, d proxy.Dialer, p proxy.Proxy, withTLS bool) (*WS, error) { 45 | u, err := url.Parse(s) 46 | if err != nil { 47 | return nil, fmt.Errorf("parse url err: %s", err) 48 | } 49 | 50 | addr := u.Host 51 | if addr == "" && d != nil { 52 | addr = d.Addr() 53 | } 54 | 55 | if _, p, _ := net.SplitHostPort(addr); p == "" { 56 | if withTLS { 57 | addr = net.JoinHostPort(addr, "443") 58 | } else { 59 | addr = net.JoinHostPort(addr, "80") 60 | } 61 | } 62 | 63 | query := u.Query() 64 | w := &WS{ 65 | dialer: d, 66 | proxy: p, 67 | addr: addr, 68 | path: u.Path, 69 | host: query.Get("host"), 70 | origin: query.Get("origin"), 71 | withTLS: withTLS, 72 | skipVerify: query.Get("skipVerify") == "true", 73 | serverName: query.Get("serverName"), 74 | certFile: query.Get("cert"), 75 | keyFile: query.Get("key"), 76 | } 77 | 78 | if w.host == "" { 79 | w.host = w.addr 80 | } 81 | 82 | if w.path == "" { 83 | w.path = "/" 84 | } 85 | 86 | if w.serverName == "" { 87 | w.serverName = w.addr[:strings.LastIndex(w.addr, ":")] 88 | } 89 | 90 | return w, nil 91 | } 92 | 93 | // parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. 94 | // TODO: move to separate http lib package for reuse(also for http proxy module) 95 | func parseFirstLine(line string) (r1, r2, r3 string, ok bool) { 96 | s1 := strings.Index(line, " ") 97 | s2 := strings.Index(line[s1+1:], " ") 98 | if s1 < 0 || s2 < 0 { 99 | return 100 | } 101 | s2 += s1 + 1 102 | return line[:s1], line[s1+1 : s2], line[s2+1:], true 103 | } 104 | 105 | func generateClientKey() string { 106 | p := pool.GetBuffer(16) 107 | defer pool.PutBuffer(p) 108 | rand.Read(p) 109 | return base64.StdEncoding.EncodeToString(p) 110 | } 111 | 112 | func computeServerKey(clientKey string) string { 113 | h := sha1.New() 114 | h.Write([]byte(clientKey)) 115 | h.Write(keyGUID) 116 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 117 | } 118 | 119 | func init() { 120 | proxy.AddUsage("ws", ` 121 | Websocket client scheme: 122 | ws://host:port[/path][?host=HOST][&origin=ORIGIN] 123 | wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&host=HOST][&origin=ORIGIN] 124 | 125 | Websocket server scheme: 126 | ws://:port[/path][?host=HOST] 127 | wss://:port[/path]?cert=PATH&key=PATH[?host=HOST] 128 | 129 | Websocket with a specified proxy protocol: 130 | ws://host:port[/path][?host=HOST],scheme:// 131 | ws://host:port[/path][?host=HOST],http://[user:pass@] 132 | ws://host:port[/path][?host=HOST],socks5://[user:pass@] 133 | 134 | TLS and Websocket with a specified proxy protocol: 135 | tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme:// 136 | tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@] 137 | tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@] 138 | tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num 139 | `) 140 | } 141 | -------------------------------------------------------------------------------- /rule/check.go: -------------------------------------------------------------------------------- 1 | package rule 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net" 9 | "os" 10 | "os/exec" 11 | "regexp" 12 | "strings" 13 | "time" 14 | 15 | "github.com/meoww-bot/glider/pkg/pool" 16 | ) 17 | 18 | // Checker is a forwarder health checker. 19 | type Checker interface { 20 | Check(fwdr *Forwarder) (elap time.Duration, err error) 21 | } 22 | 23 | type tcpChecker struct { 24 | addr string 25 | timeout time.Duration 26 | } 27 | 28 | func newTcpChecker(addr string, timeout time.Duration) *tcpChecker { 29 | if _, port, _ := net.SplitHostPort(addr); port == "" { 30 | addr = net.JoinHostPort(addr, "80") 31 | } 32 | return &tcpChecker{addr, timeout} 33 | } 34 | 35 | // Check implements the Checker interface. 36 | func (c *tcpChecker) Check(fwdr *Forwarder) (time.Duration, error) { 37 | startTime := time.Now() 38 | rc, err := fwdr.Dial("tcp", c.addr) 39 | if err != nil { 40 | return 0, err 41 | } 42 | rc.Close() 43 | return time.Since(startTime), nil 44 | } 45 | 46 | type httpChecker struct { 47 | addr string 48 | uri string 49 | expect string 50 | timeout time.Duration 51 | 52 | tlsConfig *tls.Config 53 | serverName string 54 | 55 | regex *regexp.Regexp 56 | } 57 | 58 | func newHttpChecker(addr, uri, expect string, timeout time.Duration, withTLS bool) *httpChecker { 59 | c := &httpChecker{ 60 | addr: addr, 61 | uri: uri, 62 | expect: expect, 63 | timeout: timeout, 64 | regex: regexp.MustCompile(expect), 65 | } 66 | 67 | if _, p, _ := net.SplitHostPort(addr); p == "" { 68 | if withTLS { 69 | c.addr = net.JoinHostPort(addr, "443") 70 | } else { 71 | c.addr = net.JoinHostPort(addr, "80") 72 | } 73 | } 74 | c.serverName = c.addr[:strings.LastIndex(c.addr, ":")] 75 | if withTLS { 76 | c.tlsConfig = &tls.Config{ServerName: c.serverName} 77 | } 78 | return c 79 | } 80 | 81 | // Check implements the Checker interface. 82 | func (c *httpChecker) Check(fwdr *Forwarder) (time.Duration, error) { 83 | startTime := time.Now() 84 | rc, err := fwdr.Dial("tcp", c.addr) 85 | if err != nil { 86 | return 0, err 87 | } 88 | 89 | if c.tlsConfig != nil { 90 | tlsConn := tls.Client(rc, c.tlsConfig) 91 | if err := tlsConn.Handshake(); err != nil { 92 | tlsConn.Close() 93 | return 0, err 94 | } 95 | rc = tlsConn 96 | } 97 | defer rc.Close() 98 | 99 | if c.timeout > 0 { 100 | rc.SetDeadline(time.Now().Add(c.timeout)) 101 | } 102 | 103 | if _, err = io.WriteString(rc, 104 | "GET "+c.uri+" HTTP/1.1\r\nHost:"+c.serverName+"\r\n\r\n"); err != nil { 105 | return 0, err 106 | } 107 | 108 | r := pool.GetBufReader(rc) 109 | defer pool.PutBufReader(r) 110 | 111 | line, err := r.ReadString('\n') 112 | if err != nil { 113 | return 0, err 114 | } 115 | 116 | if !c.regex.MatchString(line) { 117 | return 0, fmt.Errorf("expect: %s, got: %s", c.expect, line) 118 | } 119 | 120 | elapsed := time.Since(startTime) 121 | if elapsed > c.timeout { 122 | return elapsed, errors.New("timeout") 123 | } 124 | 125 | return elapsed, nil 126 | } 127 | 128 | type fileChecker struct{ path string } 129 | 130 | func newFileChecker(path string) *fileChecker { return &fileChecker{path} } 131 | 132 | // Check implements the Checker interface. 133 | func (c *fileChecker) Check(fwdr *Forwarder) (time.Duration, error) { 134 | cmd := exec.Command(c.path) 135 | cmd.Stdout = os.Stdout 136 | cmd.Env = os.Environ() 137 | cmd.Env = append(cmd.Env, "FORWARDER_ADDR="+fwdr.Addr()) 138 | cmd.Env = append(cmd.Env, "FORWARDER_URL="+fwdr.URL()) 139 | return 0, cmd.Run() 140 | } 141 | -------------------------------------------------------------------------------- /rule/config.go: -------------------------------------------------------------------------------- 1 | package rule 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/nadoo/conflag" 8 | ) 9 | 10 | // Config is config of rule. 11 | type Config struct { 12 | RulePath string 13 | 14 | Forward []string 15 | Strategy Strategy 16 | 17 | DNSServers []string 18 | IPSet string 19 | 20 | Domain []string 21 | IP []string 22 | CIDR []string 23 | } 24 | 25 | // Strategy configurations. 26 | type Strategy struct { 27 | Strategy string 28 | Check string 29 | CheckInterval int 30 | CheckTimeout int 31 | CheckTolerance int 32 | CheckLatencySamples int 33 | CheckDisabledOnly bool 34 | MaxFailures int 35 | DialTimeout int 36 | RelayTimeout int 37 | IntFace string 38 | } 39 | 40 | // NewConfFromFile returns a new config from file. 41 | func NewConfFromFile(ruleFile string) (*Config, error) { 42 | p := &Config{RulePath: ruleFile} 43 | 44 | f := conflag.NewFromFile("rule", ruleFile) 45 | f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]") 46 | f.StringVar(&p.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr") 47 | f.StringVar(&p.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]\ncheck=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR\ncheck=disable: disable health check") 48 | f.IntVar(&p.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)") 49 | f.IntVar(&p.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)") 50 | f.IntVar(&p.Strategy.CheckLatencySamples, "checklatencysamples", 10, "use the average latency of the latest N checks") 51 | f.IntVar(&p.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode") 52 | f.BoolVar(&p.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only") 53 | f.IntVar(&p.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled") 54 | f.IntVar(&p.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)") 55 | f.IntVar(&p.Strategy.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)") 56 | f.StringVar(&p.Strategy.IntFace, "interface", "", "source ip or source interface") 57 | 58 | f.StringSliceUniqVar(&p.DNSServers, "dnsserver", nil, "remote dns server") 59 | f.StringVar(&p.IPSet, "ipset", "", "ipset NAME, will create 2 sets: NAME for ipv4 and NAME6 for ipv6") 60 | 61 | f.StringSliceVar(&p.Domain, "domain", nil, "domain") 62 | f.StringSliceVar(&p.IP, "ip", nil, "ip") 63 | f.StringSliceVar(&p.CIDR, "cidr", nil, "cidr") 64 | 65 | err := f.Parse() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | return p, err 71 | } 72 | 73 | // ListDir returns file list named with suffix in dirPth. 74 | func ListDir(dirPth string, suffix string) (files []string, err error) { 75 | files = make([]string, 0, 10) 76 | dir, err := os.ReadDir(dirPth) 77 | if err != nil { 78 | return nil, err 79 | } 80 | PthSep := string(os.PathSeparator) 81 | suffix = strings.ToLower(suffix) 82 | for _, fi := range dir { 83 | if fi.IsDir() { 84 | continue 85 | } 86 | if strings.HasSuffix(strings.ToLower(fi.Name()), suffix) { 87 | files = append(files, dirPth+PthSep+fi.Name()) 88 | } 89 | } 90 | return files, nil 91 | } 92 | -------------------------------------------------------------------------------- /service/dhcpd/cilent.go: -------------------------------------------------------------------------------- 1 | package dhcpd 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/insomniacslk/dhcp/dhcpv4" 9 | 10 | "github.com/meoww-bot/glider/pkg/sockopt" 11 | ) 12 | 13 | func discovery(intf *net.Interface) (found bool) { 14 | lc := &net.ListenConfig{Control: sockopt.Control(sockopt.Bind(intf), sockopt.ReuseAddr())} 15 | 16 | pc, err := lc.ListenPacket(context.Background(), "udp4", ":68") 17 | if err != nil { 18 | return 19 | } 20 | defer pc.Close() 21 | 22 | discovery, err := dhcpv4.NewDiscovery(intf.HardwareAddr, dhcpv4.WithBroadcast(true)) 23 | if err != nil { 24 | return 25 | } 26 | 27 | _, err = pc.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67}) 28 | if err != nil { 29 | return 30 | } 31 | 32 | var buf [dhcpv4.MaxMessageSize]byte 33 | pc.SetReadDeadline(time.Now().Add(time.Second * 3)) 34 | n, _, err := pc.ReadFrom(buf[:]) 35 | if err != nil { 36 | return 37 | } 38 | 39 | _, err = dhcpv4.FromBytes(buf[:n]) 40 | return err == nil 41 | } 42 | -------------------------------------------------------------------------------- /service/dhcpd/pool.go: -------------------------------------------------------------------------------- 1 | package dhcpd 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "math/rand" 7 | "net" 8 | "net/netip" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // Pool is a dhcp pool. 14 | type Pool struct { 15 | items []*item 16 | mutex sync.RWMutex 17 | lease time.Duration 18 | } 19 | 20 | type item struct { 21 | ip netip.Addr 22 | mac net.HardwareAddr 23 | expire time.Time 24 | } 25 | 26 | // NewPool returns a new dhcp ip pool. 27 | func NewPool(lease time.Duration, start, end netip.Addr) (*Pool, error) { 28 | if start.IsUnspecified() || end.IsUnspecified() || start.Is6() || end.Is6() { 29 | return nil, errors.New("start ip or end ip is wrong/nil, please check your config, note only ipv4 is supported") 30 | } 31 | 32 | s, e := ipv4ToNum(start), ipv4ToNum(end) 33 | if e < s { 34 | return nil, errors.New("start ip larger than end ip") 35 | } 36 | 37 | items := make([]*item, 0, e-s+1) 38 | for n := s; n <= e; n++ { 39 | items = append(items, &item{ip: numToIPv4(n)}) 40 | } 41 | rand.Seed(time.Now().Unix()) 42 | 43 | p := &Pool{items: items, lease: lease} 44 | go func() { 45 | for now := range time.Tick(time.Second) { 46 | p.mutex.Lock() 47 | for i := 0; i < len(items); i++ { 48 | if !items[i].expire.IsZero() && now.After(items[i].expire) { 49 | items[i].mac = nil 50 | items[i].expire = time.Time{} 51 | } 52 | } 53 | p.mutex.Unlock() 54 | } 55 | }() 56 | 57 | return p, nil 58 | } 59 | 60 | // LeaseIP leases an ip to mac from dhcp pool. 61 | func (p *Pool) LeaseIP(mac net.HardwareAddr) (netip.Addr, error) { 62 | p.mutex.Lock() 63 | defer p.mutex.Unlock() 64 | 65 | for _, item := range p.items { 66 | if bytes.Equal(mac, item.mac) { 67 | return item.ip, nil 68 | } 69 | } 70 | 71 | idx := rand.Intn(len(p.items)) 72 | for _, item := range p.items[idx:] { 73 | if item.mac == nil { 74 | item.mac = mac 75 | item.expire = time.Now().Add(p.lease) 76 | return item.ip, nil 77 | } 78 | } 79 | 80 | for _, item := range p.items { 81 | if item.mac == nil { 82 | item.mac = mac 83 | item.expire = time.Now().Add(p.lease) 84 | return item.ip, nil 85 | } 86 | } 87 | 88 | return netip.Addr{}, errors.New("no more ip can be leased") 89 | } 90 | 91 | // LeaseStaticIP leases static ip from pool according to the given mac. 92 | func (p *Pool) LeaseStaticIP(mac net.HardwareAddr, ip netip.Addr) { 93 | p.mutex.Lock() 94 | defer p.mutex.Unlock() 95 | 96 | for _, item := range p.items { 97 | if item.ip == ip { 98 | item.mac = mac 99 | item.expire = time.Time{} 100 | } 101 | } 102 | } 103 | 104 | // ReleaseIP releases ip from pool according to the given mac. 105 | func (p *Pool) ReleaseIP(mac net.HardwareAddr) { 106 | p.mutex.Lock() 107 | defer p.mutex.Unlock() 108 | 109 | for _, item := range p.items { 110 | // not static ip 111 | if !item.expire.IsZero() && bytes.Equal(mac, item.mac) { 112 | item.mac = nil 113 | item.expire = time.Time{} 114 | } 115 | } 116 | } 117 | 118 | func ipv4ToNum(addr netip.Addr) uint32 { 119 | ip := addr.AsSlice() 120 | n := uint32(ip[0])<<24 + uint32(ip[1])<<16 121 | return n + uint32(ip[2])<<8 + uint32(ip[3]) 122 | } 123 | 124 | func numToIPv4(n uint32) netip.Addr { 125 | ip := [4]byte{byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)} 126 | return netip.AddrFrom4(ip) 127 | } 128 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var creators = make(map[string]Creator) 9 | 10 | // Service is a server that can be run. 11 | type Service interface{ Run() } 12 | 13 | // Creator is a function to create services. 14 | type Creator func(args ...string) (Service, error) 15 | 16 | // Register is used to register a service. 17 | func Register(name string, c Creator) { 18 | creators[strings.ToLower(name)] = c 19 | } 20 | 21 | // New calls the registered creator to create services. 22 | func New(s string) (Service, error) { 23 | args := strings.Split(s, ",") 24 | c, ok := creators[strings.ToLower(args[0])] 25 | if ok { 26 | return c(args[1:]...) 27 | } 28 | return nil, errors.New("unknown service name: '" + args[0] + "'") 29 | } 30 | -------------------------------------------------------------------------------- /systemd/README.md: -------------------------------------------------------------------------------- 1 | ## Service 2 | 3 | ### Install 4 | 5 | #### 1. copy binary file 6 | 7 | ```bash 8 | cp glider /usr/bin/ 9 | ``` 10 | 11 | #### 2. add service file 12 | 13 | ```bash 14 | # copy service file to systemd 15 | cp systemd/glider@.service /etc/systemd/system/ 16 | ``` 17 | 18 | #### 3. add config file: ***glider***.conf 19 | 20 | ```bash 21 | # copy config file to /etc/glider/ 22 | mkdir /etc/glider/ 23 | cp ./config/glider.conf.example /etc/glider/glider.conf 24 | ``` 25 | 26 | #### 4. enable and start service: glider@***glider*** 27 | 28 | ```bash 29 | # enable and start service 30 | systemctl enable glider@glider 31 | systemctl start glider@glider 32 | ``` 33 | 34 | See [glider@.service](glider%40.service) 35 | -------------------------------------------------------------------------------- /systemd/glider@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Glider Service (%i) 3 | After=network.target iptables.service ip6tables.service 4 | 5 | [Service] 6 | Type=simple 7 | DynamicUser=yes 8 | Restart=always 9 | LimitNOFILE=102400 10 | 11 | # NOTE: CHANGE to your glider path 12 | ExecStart=/usr/bin/glider -config /etc/glider/%i.conf 13 | 14 | # NOTE: 15 | # work with systemd v229 or later, so glider can listen on port below 1024 with none-root user 16 | # CAP_NET_ADMIN: ipset, setsockopt: IP_TRANSPARENT 17 | # CAP_NET_BIND_SERVICE: bind ports under 1024 18 | # CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd) 19 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW 20 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW 21 | NoNewPrivileges=true 22 | 23 | [Install] 24 | WantedBy=multi-user.target 25 | -------------------------------------------------------------------------------- /systemd/postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if test ! -f "/etc/glider/glider.conf"; then 6 | cp /etc/glider/glider.conf.example /etc/glider/glider.conf 7 | fi 8 | 9 | /bin/systemctl daemon-reload 10 | 11 | if /bin/systemctl is-active --quiet glider@glider; then 12 | /bin/systemctl restart glider@glider 13 | fi 14 | 15 | if ! /bin/systemctl is-enabled --quiet glider@glider; then 16 | /bin/systemctl enable --now glider@glider; 17 | fi 18 | -------------------------------------------------------------------------------- /systemd/postremove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | /bin/systemctl daemon-reload -------------------------------------------------------------------------------- /systemd/preremove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if /bin/systemctl is-active --quiet glider@glider; then 6 | /bin/systemctl stop glider@glider 7 | fi 8 | 9 | if /bin/systemctl is-enabled --quiet glider@glider; then 10 | /bin/systemctl disable --now glider@glider; 11 | fi --------------------------------------------------------------------------------