├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── args.go ├── build-release.sh ├── cmd └── build │ ├── arch.go │ ├── osversion.go │ └── osversion_windows.go ├── errors.generated.go ├── go.mod ├── go.sum ├── log.go ├── log_android.go ├── main.go ├── utils.go └── utils_android.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.13 6 | working_directory: ~/code 7 | steps: 8 | - checkout 9 | - run: sudo apt-get update && sudo apt-get install -y upx 10 | - restore_cache: 11 | key: go-pkg-{{ checksum "go.sum" }} 12 | - run: ./build-release.sh 13 | - save_cache: 14 | paths: 15 | - /go/pkg 16 | key: go-pkg-{{ checksum "go.sum" }} 17 | - store_artifacts: 18 | path: bin 19 | destination: bin 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | v2ray-plugin* 2 | /bin/ 3 | /.idea/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 by Max Lv 4 | Copyright (C) 2019 by Mygod Studio 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Yet another SIP003 plugin for shadowsocks, based on [V2Ray](https://github.com/v2fly/v2ray-core) 2 | 3 | [![CircleCI](https://circleci.com/gh/shadowsocks/v2ray-plugin.svg?style=shield)](https://circleci.com/gh/shadowsocks/v2ray-plugin) 4 | [![Releases](https://img.shields.io/github/downloads/shadowsocks/v2ray-plugin/total.svg)](https://github.com/shadowsocks/v2ray-plugin/releases) 5 | [![Language: Go](https://img.shields.io/badge/go-1.20+-blue.svg)](https://github.com/shadowsocks/v2ray-plugin/search?l=go) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/shadowsocks/v2ray-plugin)](https://goreportcard.com/report/github.com/shadowsocks/v2ray-plugin) 7 | [![License](https://img.shields.io/github/license/shadowsocks/v2ray-plugin.svg)](LICENSE) 8 | 9 | ## Build 10 | 11 | * `go build` 12 | * Alternatively, you can grab the latest nightly from Circle CI by logging into Circle CI or adding `#artifacts` at the end of URL like such: https://circleci.com/gh/shadowsocks/v2ray-plugin/20#artifacts 13 | 14 | ## Usage 15 | 16 | See command line args for advanced usages. 17 | 18 | ### Shadowsocks over websocket (HTTP) 19 | 20 | On your server 21 | 22 | ```sh 23 | ss-server -c config.json -p 80 --plugin v2ray-plugin --plugin-opts "server" 24 | ``` 25 | 26 | On your client 27 | 28 | ```sh 29 | ss-local -c config.json -p 80 --plugin v2ray-plugin 30 | ``` 31 | 32 | ### Shadowsocks over websocket with TLS (HTTPS) 33 | 34 | On your server 35 | 36 | ```sh 37 | ss-server -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "server;tls;host=mydomain.me" 38 | ``` 39 | 40 | On your client 41 | 42 | ```sh 43 | ss-local -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "tls;host=mydomain.me" 44 | ``` 45 | 46 | ### Shadowsocks over QUIC 47 | 48 | On your server 49 | 50 | ```sh 51 | ss-server -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "server;mode=quic;host=mydomain.me" 52 | ``` 53 | 54 | On your client 55 | 56 | ```sh 57 | ss-local -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "mode=quic;host=mydomain.me" 58 | ``` 59 | 60 | ### Shadowsocks over gRPC 61 | 62 | On your server 63 | 64 | ```sh 65 | ss-server -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "server;mode=grpc" 66 | ``` 67 | 68 | On your client 69 | 70 | ```sh 71 | ss-local -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "mode=grpc" 72 | ``` 73 | 74 | ### Shadowsocks over gRPC with TLS 75 | 76 | On your server 77 | 78 | ```sh 79 | ss-server -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "server;mode=grpc;tls;host=mydomain.me" 80 | ``` 81 | 82 | On your client 83 | 84 | ```sh 85 | ss-local -c config.json -p 443 --plugin v2ray-plugin --plugin-opts "tls;mode=grpc;host=mydomain.me" 86 | ``` 87 | 88 | ### Issue a cert for TLS and QUIC 89 | 90 | `v2ray-plugin` will look for TLS certificates signed by [acme.sh](https://github.com/acmesh-official/acme.sh) by default. 91 | Here's some sample commands for issuing a certificate using CloudFlare. 92 | You can find commands for issuing certificates for other DNS providers at acme.sh. 93 | 94 | ```sh 95 | curl https://get.acme.sh | sh 96 | ~/.acme.sh/acme.sh --issue --dns dns_cf -d mydomain.me 97 | ``` 98 | 99 | Alternatively, you can specify path to your certificates using option `cert` and `key`. 100 | 101 | ### Use `certRaw` to pass certificate 102 | 103 | Instead of using `cert` to pass the certificate file, `certRaw` could be used to pass in PEM format certificate, that is the content between `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` without the line breaks. 104 | -------------------------------------------------------------------------------- /args.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "os" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | func isIPv6(str string) bool { 13 | ip := net.ParseIP(str) 14 | return ip != nil && strings.Contains(str, ":") 15 | } 16 | 17 | // Key–value mappings for the representation of client and server options. 18 | 19 | // Args maps a string key to a list of values. It is similar to url.Values. 20 | type Args map[string][]string 21 | 22 | // Get the first value associated with the given key. If there are any values 23 | // associated with the key, the value return has the value and ok is set to 24 | // true. If there are no values for the given key, value is "" and ok is false. 25 | // If you need access to multiple values, use the map directly. 26 | func (args Args) Get(key string) (value string, ok bool) { 27 | if args == nil { 28 | return "", false 29 | } 30 | vals, ok := args[key] 31 | if !ok || len(vals) == 0 { 32 | return "", false 33 | } 34 | return vals[0], true 35 | } 36 | 37 | // Append value to the list of values for key. 38 | func (args Args) Add(key, value string) { 39 | args[key] = append(args[key], value) 40 | } 41 | 42 | // Return the index of the next unescaped byte in s that is in the term set, or 43 | // else the length of the string if no terminators appear. Additionally return 44 | // the unescaped string up to the returned index. 45 | func indexUnescaped(s string, term []byte) (int, string, error) { 46 | var i int 47 | unesc := make([]byte, 0) 48 | for i = 0; i < len(s); i++ { 49 | b := s[i] 50 | // A terminator byte? 51 | if bytes.IndexByte(term, b) != -1 { 52 | break 53 | } 54 | if b == '\\' { 55 | i++ 56 | if i >= len(s) { 57 | return 0, "", fmt.Errorf("nothing following final escape in %q", s) 58 | } 59 | b = s[i] 60 | } 61 | unesc = append(unesc, b) 62 | } 63 | return i, string(unesc), nil 64 | } 65 | 66 | // Parse SS_PLUGIN options from environment variables 67 | func parseEnv() (opts Args, err error) { 68 | opts = make(Args) 69 | ss_remote_host := os.Getenv("SS_REMOTE_HOST") 70 | ss_remote_port := os.Getenv("SS_REMOTE_PORT") 71 | ss_local_host := os.Getenv("SS_LOCAL_HOST") 72 | ss_local_port := os.Getenv("SS_LOCAL_PORT") 73 | if len(ss_remote_host) == 0 { 74 | return 75 | } 76 | if len(ss_remote_port) == 0 { 77 | return 78 | } 79 | if len(ss_local_host) == 0 { 80 | return 81 | } 82 | if len(ss_local_port) == 0 { 83 | return 84 | } 85 | 86 | opts.Add("remoteAddr", ss_remote_host) 87 | opts.Add("remotePort", ss_remote_port) 88 | opts.Add("localAddr", ss_local_host) 89 | opts.Add("localPort", ss_local_port) 90 | 91 | ss_plugin_options := os.Getenv("SS_PLUGIN_OPTIONS") 92 | if len(ss_plugin_options) > 0 { 93 | other_opts, err := parsePluginOptions(ss_plugin_options) 94 | if err != nil { 95 | return nil, err 96 | } 97 | for k, v := range other_opts { 98 | opts[k] = v 99 | } 100 | } 101 | return opts, nil 102 | } 103 | 104 | // Parse a name–value mapping as from SS_PLUGIN_OPTIONS. 105 | // 106 | // " is a k=v string value with options that are to be passed to the 107 | // transport. semicolons, equal signs and backslashes must be escaped 108 | // with a backslash." 109 | // Example: secret=nou;cache=/tmp/cache;secret=yes 110 | func parsePluginOptions(s string) (opts Args, err error) { 111 | opts = make(Args) 112 | if len(s) == 0 { 113 | return 114 | } 115 | i := 0 116 | for { 117 | var key, value string 118 | var offset, begin int 119 | 120 | if i >= len(s) { 121 | break 122 | } 123 | begin = i 124 | // Read the key. 125 | offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) 126 | if err != nil { 127 | return 128 | } 129 | if len(key) == 0 { 130 | err = fmt.Errorf("empty key in %q", s[begin:i]) 131 | return 132 | } 133 | i += offset 134 | // End of string or no equals sign? 135 | if i >= len(s) || s[i] != '=' { 136 | opts.Add(key, "1") 137 | // Skip the semicolon. 138 | i++ 139 | continue 140 | } 141 | // Skip the equals sign. 142 | i++ 143 | // Read the value. 144 | offset, value, err = indexUnescaped(s[i:], []byte{';'}) 145 | if err != nil { 146 | return 147 | } 148 | i += offset 149 | opts.Add(key, value) 150 | // Skip the semicolon. 151 | i++ 152 | } 153 | return opts, nil 154 | } 155 | 156 | // Escape backslashes and all the bytes that are in set. 157 | func backslashEscape(s string, set []byte) string { 158 | var buf bytes.Buffer 159 | for _, b := range []byte(s) { 160 | if b == '\\' || bytes.IndexByte(set, b) != -1 { 161 | buf.WriteByte('\\') 162 | } 163 | buf.WriteByte(b) 164 | } 165 | return buf.String() 166 | } 167 | 168 | // Encode a name–value mapping so that it is suitable to go in the ARGS option 169 | // of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not 170 | // added. 171 | // 172 | // "Equal signs and commas [and backslashes] must be escaped with a backslash." 173 | func encodeSmethodArgs(args Args) string { 174 | if args == nil { 175 | return "" 176 | } 177 | 178 | keys := make([]string, 0, len(args)) 179 | for key := range args { 180 | keys = append(keys, key) 181 | } 182 | sort.Strings(keys) 183 | 184 | escape := func(s string) string { 185 | return backslashEscape(s, []byte{'=', ','}) 186 | } 187 | 188 | var pairs []string 189 | for _, key := range keys { 190 | for _, value := range args[key] { 191 | pairs = append(pairs, escape(key)+"="+escape(value)) 192 | } 193 | } 194 | 195 | return strings.Join(pairs, ",") 196 | } 197 | -------------------------------------------------------------------------------- /build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sum="sha1sum" 3 | 4 | if ! hash sha1sum 2>/dev/null; then 5 | if ! hash shasum 2>/dev/null; then 6 | echo "I can't see 'sha1sum' or 'shasum'" 7 | echo "Please install one of them!" 8 | exit 9 | fi 10 | sum="shasum" 11 | fi 12 | 13 | [[ -z $upx ]] && upx="echo pending" 14 | if [[ $upx == "echo pending" ]] && hash upx 2>/dev/null; then 15 | upx="upx -9" 16 | fi 17 | 18 | VERSION=$(git describe --tags) 19 | LDFLAGS="-X main.VERSION=${VERSION} -s -w -buildid=" 20 | 21 | OSES=(linux darwin windows freebsd) 22 | ARCHS=(amd64 386) 23 | 24 | mkdir bin 25 | 26 | for os in "${OSES[@]}"; do 27 | for arch in "${ARCHS[@]}"; do 28 | # Go 1.15 drops support for 32-bit binaries on macOS, iOS, iPadOS, watchOS, and tvOS (the darwin/386 and darwin/arm ports) 29 | # Reference URL: https://tip.golang.org/doc/go1.15#darwin 30 | if [ "${os}" == "darwin" ] && [ "${arch}" == "386" ]; then 31 | continue 32 | fi 33 | suffix="" 34 | if [ "${os}" == "windows" ]; then 35 | suffix=".exe" 36 | fi 37 | env CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_${os}_${arch}${suffix} 38 | $upx v2ray-plugin_${os}_${arch}${suffix} >/dev/null 39 | tar -zcf bin/v2ray-plugin-${os}-${arch}-${VERSION}.tar.gz v2ray-plugin_${os}_${arch}${suffix} 40 | $sum bin/v2ray-plugin-${os}-${arch}-${VERSION}.tar.gz 41 | done 42 | done 43 | 44 | # ARM 45 | ARMS=(5 6 7) 46 | for v in "${ARMS[@]}"; do 47 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=${v} go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_arm${v} 48 | done 49 | $upx v2ray-plugin_linux_arm* >/dev/null 50 | tar -zcf bin/v2ray-plugin-linux-arm-${VERSION}.tar.gz v2ray-plugin_linux_arm* 51 | $sum bin/v2ray-plugin-linux-arm-${VERSION}.tar.gz 52 | 53 | # ARM64 (ARMv8 or aarch64) 54 | env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_arm64 55 | $upx v2ray-plugin_linux_arm64 >/dev/null 56 | tar -zcf bin/v2ray-plugin-linux-arm64-${VERSION}.tar.gz v2ray-plugin_linux_arm64 57 | $sum bin/v2ray-plugin-linux-arm64-${VERSION}.tar.gz 58 | 59 | # Darwin ARM64 60 | env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_darwin_arm64 61 | $upx v2ray-plugin_darwin_arm64 >/dev/null 62 | tar -zcf bin/v2ray-plugin-darwin-arm64-${VERSION}.tar.gz v2ray-plugin_darwin_arm64 63 | $sum bin/v2ray-plugin-darwin-arm64-${VERSION}.tar.gz 64 | 65 | # Windows ARM 66 | env CGO_ENABLED=0 GOOS=windows GOARCH=arm go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_windows_arm.exe 67 | $upx v2ray-plugin_windows_arm.exe >/dev/null 68 | tar -zcf bin/v2ray-plugin-windows-arm-${VERSION}.tar.gz v2ray-plugin_windows_arm.exe 69 | $sum bin/v2ray-plugin-windows-arm-${VERSION}.tar.gz 70 | 71 | # Windows ARM64 72 | env CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_windows_arm64.exe 73 | $upx v2ray-plugin_windows_arm64.exe >/dev/null 74 | tar -zcf bin/v2ray-plugin-windows-arm64-${VERSION}.tar.gz v2ray-plugin_windows_arm64.exe 75 | $sum bin/v2ray-plugin-windows-arm64-${VERSION}.tar.gz 76 | 77 | # MIPS 78 | MIPSS=(mips mipsle) 79 | for v in "${MIPSS[@]}"; do 80 | env CGO_ENABLED=0 GOOS=linux GOARCH=${v} go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_${v} 81 | env CGO_ENABLED=0 GOOS=linux GOARCH=${v} GOMIPS=softfloat go build -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_${v}_sf 82 | done 83 | $upx v2ray-plugin_linux_mips* >/dev/null 84 | tar -zcf bin/v2ray-plugin-linux-mips-${VERSION}.tar.gz v2ray-plugin_linux_mips* 85 | $sum bin/v2ray-plugin-linux-mips-${VERSION}.tar.gz 86 | 87 | # MIPS64 88 | MIPS64S=(mips64 mips64le) 89 | for v in "${MIPS64S[@]}"; do 90 | env CGO_ENABLED=0 GOOS=linux GOARCH=${v} go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_${v} 91 | done 92 | tar -zcf bin/v2ray-plugin-linux-mips64-${VERSION}.tar.gz v2ray-plugin_linux_mips64* 93 | $sum bin/v2ray-plugin-linux-mips64-${VERSION}.tar.gz 94 | 95 | # ppc64le 96 | env CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_ppc64le 97 | $upx v2ray-plugin_linux_ppc64le >/dev/null 98 | tar -zcf bin/v2ray-plugin-linux-ppc64le-${VERSION}.tar.gz v2ray-plugin_linux_ppc64le 99 | $sum bin/v2ray-plugin-linux-ppc64le-${VERSION}.tar.gz 100 | 101 | # s390x 102 | env CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_s390x 103 | $upx v2ray-plugin_linux_s390x >/dev/null 104 | tar -zcf bin/v2ray-plugin-linux-s390x-${VERSION}.tar.gz v2ray-plugin_linux_s390x 105 | $sum bin/v2ray-plugin-linux-s390x-${VERSION}.tar.gz 106 | 107 | # riscv64 108 | env CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -v -trimpath -ldflags "${LDFLAGS}" -o v2ray-plugin_linux_riscv64 109 | $upx v2ray-plugin_linux_riscv64 >/dev/null 110 | tar -zcf bin/v2ray-plugin-linux-riscv64-${VERSION}.tar.gz v2ray-plugin_linux_riscv64 111 | $sum bin/v2ray-plugin-linux-riscv64-${VERSION}.tar.gz 112 | -------------------------------------------------------------------------------- /cmd/build/arch.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import ( 4 | "runtime" 5 | 6 | "golang.org/x/sys/cpu" 7 | ) 8 | 9 | // GetSupportedGOARM returns the ARM compatibility level of the current CPU. 10 | // 11 | // Returns the integer value that can be set for the GOARM variable to 12 | // build with this level as target, a value which normally corresponds to the 13 | // ARM architecture version number, although it is the floating point hardware 14 | // support which is the decicive factor. 15 | // 16 | // Only relevant for 32-bit ARM architectures, where GOARCH=arm, which means 17 | // ARMv7 and lower (ARMv8 is GOARCH=arm64 and GOARM is not considered). 18 | // Highest possible value is therefore 7, while other possible values are 19 | // 6 (for ARMv6) and 5 (for ARMv5, which is the lowest currently supported 20 | // in go. Returns value 0 for anything else. 21 | // 22 | // See also: 23 | // 24 | // https://go.dev/src/runtime/os_linux_arm.go 25 | // https://github.com/golang/go/wiki/GoArm 26 | func GetSupportedGOARM() int { 27 | if runtime.GOARCH == "arm" && cpu.Initialized { 28 | // This CPU is an ARM (32-bit), and cpu.Initialized true means its 29 | // features could be retrieved on current GOOS so that we can check 30 | // for floating point hardware support. 31 | if cpu.ARM.HasVFPv3 { 32 | // This CPU has VFPv3 floating point hardware, which means it can 33 | // run programs built with any GOARM value, 7 and lower. 34 | return 7 35 | } else if cpu.ARM.HasVFP { 36 | // This CPU has VFP floating point hardware, but not VFPv3, which 37 | // means it can run programs built with GOARM value 6 and lower, 38 | // but not 7. 39 | return 6 40 | } 41 | // This CPU has no VFP floating point hardware, which means it can 42 | // only run programs built with GOARM value 5, which is minimum supported. 43 | // Note that the CPU can still in reality be based on e.g. ARMv7 44 | // architecture, but simply lack hardfloat support. 45 | return 5 46 | } 47 | return 0 48 | } 49 | 50 | // GetArch tells the rclone executable's architecture target. 51 | func GetArch() string { 52 | // Get the running program's architecture target. 53 | arch := runtime.GOARCH 54 | 55 | // For ARM architectures there are several variants, with different 56 | // inconsistent and ambiguous naming. 57 | // 58 | // The most interesting thing here is which compatibility level of go is 59 | // used, as controlled by GOARM build variable. We cannot in runtime get 60 | // the actual value of GOARM used for building this program, but we can 61 | // check the value supported by the current CPU by calling GetSupportedGOARM. 62 | // This means we return information about the compatibility level (GOARM 63 | // value) supported, when the current rclone executable may in reality be 64 | // built with a lower level. 65 | // 66 | // Note that the kernel architecture, as returned by "uname -m", is not 67 | // considered or included in results here, but it is included in the output 68 | // from function GetOSVersion. It can have values such as armv6l, armv7l, 69 | // armv8l, arm64 and aarch64, which may give relevant information. But it 70 | // can also simply have value "arm", or it can have value "armv7l" for a 71 | // processor based on ARMv7 but without floating point hardware - which 72 | // means it in go needs to be built in ARMv5 compatibility mode (GOARM=5). 73 | if arch == "arm64" { 74 | // 64-bit ARM architecture, known as AArch64, was introduced with ARMv8. 75 | // In go this architecture is a specific one, separate from other ARMs. 76 | arch += " (ARMv8 compatible)" 77 | } else if arch == "arm" { 78 | // 32-bit ARM architecture, which is ARMv7 and lower. 79 | // In go there are different compatibility levels represented by ARM 80 | // architecture version number (like 5, 6 or 7). 81 | switch GetSupportedGOARM() { 82 | case 7: 83 | arch += " (ARMv7 compatible)" 84 | case 6: 85 | arch += " (ARMv6 compatible)" 86 | case 5: 87 | arch += " (ARMv5 compatible, no hardfloat)" 88 | } 89 | } 90 | return arch 91 | } 92 | -------------------------------------------------------------------------------- /cmd/build/osversion.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package build 4 | 5 | import ( 6 | "strings" 7 | 8 | "github.com/shirou/gopsutil/v3/host" 9 | ) 10 | 11 | // GetOSVersion returns OS version, kernel and bitness 12 | func GetOSVersion() (osVersion, osKernel string) { 13 | if platform, _, version, err := host.PlatformInformation(); err == nil && platform != "" { 14 | osVersion = platform 15 | if version != "" { 16 | osVersion += " " + version 17 | } 18 | } 19 | 20 | if version, err := host.KernelVersion(); err == nil && version != "" { 21 | osKernel = version 22 | } 23 | 24 | if arch, err := host.KernelArch(); err == nil && arch != "" { 25 | if strings.HasSuffix(arch, "64") && osVersion != "" { 26 | osVersion += " (64 bit)" 27 | } 28 | if osKernel != "" { 29 | osKernel += " (" + arch + ")" 30 | } 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /cmd/build/osversion_windows.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "unsafe" 7 | 8 | "github.com/shirou/gopsutil/v3/host" 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | // GetOSVersion returns OS version, kernel and bitness 13 | // On Windows it performs additional output enhancements. 14 | func GetOSVersion() (osVersion, osKernel string) { 15 | if platform, _, version, err := host.PlatformInformation(); err == nil && platform != "" { 16 | osVersion = platform 17 | if version != "" { 18 | osVersion += " " + version 19 | } 20 | } 21 | 22 | if version, err := host.KernelVersion(); err == nil && version != "" { 23 | osKernel = version 24 | 25 | // Prevent duplication of output on Windows 26 | if strings.Contains(osVersion, osKernel) { 27 | deduped := strings.TrimSpace(strings.Replace(osVersion, osKernel, "", 1)) 28 | if deduped != "" { 29 | osVersion = deduped 30 | } 31 | } 32 | 33 | // Simplify kernel output: `MAJOR.MINOR.BUILD.REVISION Build BUILD.REVISION` -> `MAJOR.MINOR.BUILD.REVISION` 34 | match := regexp.MustCompile(`^(\d+\.\d+\.(\d+\.\d+)) Build (\d+\.\d+)$`).FindStringSubmatch(osKernel) 35 | if len(match) == 4 && match[2] == match[3] { 36 | osKernel = match[1] 37 | } 38 | } 39 | 40 | if osVersion != "" { 41 | // Include the friendly-name of the version, which is typically what is referred to. 42 | // Until Windows 10 version 2004 (May 2020) this can be found from registry entry 43 | // ReleaseID, after that we must use entry DisplayVersion (ReleaseId is stuck at 2009). 44 | // Source: https://ss64.com/nt/ver.html 45 | friendlyName := getRegistryVersionString("DisplayVersion") 46 | if friendlyName == "" { 47 | friendlyName = getRegistryVersionString("ReleaseId") 48 | } 49 | if friendlyName != "" { 50 | osVersion += " " + friendlyName 51 | } 52 | } 53 | 54 | if arch, err := host.KernelArch(); err == nil && arch != "" { 55 | if strings.HasSuffix(arch, "64") && osVersion != "" { 56 | osVersion += " (64 bit)" 57 | } 58 | if osKernel != "" { 59 | osKernel += " (" + arch + ")" 60 | } 61 | } 62 | 63 | return 64 | } 65 | 66 | var regVersionKeyUTF16 = windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Windows NT\CurrentVersion`) 67 | 68 | func getRegistryVersionString(name string) string { 69 | var ( 70 | err error 71 | handle windows.Handle 72 | bufLen uint32 73 | valType uint32 74 | ) 75 | 76 | err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, regVersionKeyUTF16, 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &handle) 77 | if err != nil { 78 | return "" 79 | } 80 | defer func() { 81 | _ = windows.RegCloseKey(handle) 82 | }() 83 | 84 | nameUTF16 := windows.StringToUTF16Ptr(name) 85 | err = windows.RegQueryValueEx(handle, nameUTF16, nil, &valType, nil, &bufLen) 86 | if err != nil { 87 | return "" 88 | } 89 | 90 | regBuf := make([]uint16, bufLen/2+1) 91 | err = windows.RegQueryValueEx(handle, nameUTF16, nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) 92 | if err != nil { 93 | return "" 94 | } 95 | 96 | return windows.UTF16ToString(regBuf) 97 | } 98 | -------------------------------------------------------------------------------- /errors.generated.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/v2fly/v2ray-core/v5/common/errors" 4 | 5 | type errPathObjHolder struct{} 6 | 7 | func newError(values ...interface{}) *errors.Error { 8 | return errors.New(values...).WithPathObj(errPathObjHolder{}) 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/teddysun/v2ray-plugin 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.4 7 | github.com/shirou/gopsutil/v3 v3.24.5 8 | github.com/v2fly/v2ray-core/v5 v5.25.0 9 | golang.org/x/sys v0.29.0 10 | google.golang.org/protobuf v1.36.3 11 | ) 12 | 13 | require ( 14 | github.com/adrg/xdg v0.5.3 // indirect 15 | github.com/go-ole/go-ole v1.2.6 // indirect 16 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 17 | github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07 // indirect 18 | github.com/gorilla/websocket v1.5.3 // indirect 19 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 20 | github.com/miekg/dns v1.1.62 // indirect 21 | github.com/onsi/ginkgo/v2 v2.17.0 // indirect 22 | github.com/pires/go-proxyproto v0.8.0 // indirect 23 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 24 | github.com/quic-go/quic-go v0.48.2 // indirect 25 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 26 | github.com/tklauser/go-sysconf v0.3.12 // indirect 27 | github.com/tklauser/numcpus v0.6.1 // indirect 28 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 29 | go.uber.org/mock v0.4.0 // indirect 30 | golang.org/x/crypto v0.32.0 // indirect 31 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 32 | golang.org/x/mod v0.18.0 // indirect 33 | golang.org/x/net v0.34.0 // indirect 34 | golang.org/x/sync v0.10.0 // indirect 35 | golang.org/x/text v0.21.0 // indirect 36 | golang.org/x/tools v0.22.0 // indirect 37 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect 38 | google.golang.org/grpc v1.69.4 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= 2 | github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= 3 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY= 4 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg= 5 | github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 6 | github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 7 | github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7 h1:zO38yBOvQ1dLHbSuaU5BFZ8zalnSDQslj+i/9AGOk9s= 8 | github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7/go.mod h1:LoSUY2chVqNQCDyi4IZGqPpXLy1FuCkE37PKwtJvNGg= 9 | github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI= 10 | github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI= 11 | github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= 12 | github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= 13 | github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= 14 | github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 15 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 16 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= 21 | github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= 22 | github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= 23 | github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= 24 | github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU= 25 | github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM= 26 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 27 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 28 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 29 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 30 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 31 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 32 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 33 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 34 | github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= 35 | github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= 36 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 37 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 38 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 39 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 40 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 41 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 42 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 43 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 44 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 45 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 46 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 47 | github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07 h1:57oOH2Mu5Nw16KnZAVLdlUjmPH/TSYCKTJgG0OVfX0Y= 48 | github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 49 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 50 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 51 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 52 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 53 | github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= 54 | github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= 55 | github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= 56 | github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= 57 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 58 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 59 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 60 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 61 | github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU= 62 | github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= 63 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 64 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 65 | github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= 66 | github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= 67 | github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= 68 | github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= 69 | github.com/mustafaturan/bus v1.0.2 h1:2x3ErwZ0uUPwwZ5ZZoknEQprdaxr68Yl3mY8jDye1Ws= 70 | github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q= 71 | github.com/mustafaturan/monoton v1.0.0 h1:8SCej+JiNn0lyps7V+Jzc1CRAkDR4EZPWrTupQ61YCQ= 72 | github.com/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8= 73 | github.com/onsi/ginkgo/v2 v2.17.0 h1:kdnunFXpBjbzN56hcJHrXZ8M+LOkenKA7NnBzTNigTI= 74 | github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= 75 | github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= 76 | github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 77 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 78 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 79 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 80 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 81 | github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= 82 | github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= 83 | github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= 84 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= 85 | github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= 86 | github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= 87 | github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw= 88 | github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU= 89 | github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= 90 | github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= 91 | github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0= 92 | github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY= 93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 95 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 96 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 97 | github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= 98 | github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= 99 | github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= 100 | github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= 101 | github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= 102 | github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= 103 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= 104 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= 105 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 106 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 107 | github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ= 108 | github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E= 109 | github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= 110 | github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= 111 | github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= 112 | github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= 113 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 114 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 115 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 116 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 117 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 118 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 119 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 120 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 121 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 122 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 123 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 124 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 125 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 126 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 127 | github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc= 128 | github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY= 129 | github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre66h7g1BjRKUnLv0HfmmRoz7w= 130 | github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M= 131 | github.com/v2fly/hysteria/core/v2 v2.0.0-20250113081444-b0a0747ac7ab h1:GstVKviVuxRZXxHzeWq0N2M4LG5A5W1HvFX1b7aQ48w= 132 | github.com/v2fly/hysteria/core/v2 v2.0.0-20250113081444-b0a0747ac7ab/go.mod h1:yWDV7zOoL0pPhVlWV6Hqf46gWYenwwT9g4Y+e5yPRz8= 133 | github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= 134 | github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= 135 | github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6 h1:Qea2jW7g1hvQ9TkYq3aT2h0NDWjPQHtvDfmKXoWgJ9E= 136 | github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6/go.mod h1:a/FYYQz8bW7wh2jmI+DVsbVYwLkgmgpml+GrJwV+eIo= 137 | github.com/v2fly/v2ray-core/v5 v5.25.0 h1:0xxh1yx2WPGGuZjzEOhqbnLEK7njiuc2LNIykZ+HHwM= 138 | github.com/v2fly/v2ray-core/v5 v5.25.0/go.mod h1:/6I3fRQmIXtef17SXJH9dMq/hNBvvrOPfj4KSEn7e7g= 139 | github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= 140 | github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= 141 | github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs= 142 | github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ= 143 | github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY= 144 | github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= 145 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 146 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 147 | go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= 148 | go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= 149 | go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= 150 | go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= 151 | go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= 152 | go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= 153 | go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= 154 | go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= 155 | go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= 156 | go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= 157 | go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8= 158 | go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= 159 | go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= 160 | go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 161 | go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= 162 | go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= 163 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 164 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 165 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= 166 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= 167 | golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= 168 | golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 169 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 170 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 171 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 172 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 173 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 175 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 177 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 178 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 179 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 180 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 181 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 182 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 183 | golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= 184 | golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 185 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 186 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= 187 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 188 | google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= 189 | google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 190 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= 191 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 192 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 193 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 194 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 195 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 196 | gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs= 197 | gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8= 198 | lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= 199 | lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 200 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= 201 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= 202 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !android 6 | 7 | package main 8 | 9 | import "log" 10 | 11 | func logInit() { 12 | } 13 | 14 | func logFatal(v ...interface{}) { 15 | log.Println(v...) 16 | } 17 | 18 | func logWarn(v ...interface{}) { 19 | log.Println(v...) 20 | } 21 | 22 | func logInfo(v ...interface{}) { 23 | log.Println(v...) 24 | } 25 | -------------------------------------------------------------------------------- /log_android.go: -------------------------------------------------------------------------------- 1 | // +build android 2 | 3 | package main 4 | 5 | /* 6 | #cgo LDFLAGS: -landroid -llog 7 | 8 | #include 9 | #include 10 | #include 11 | */ 12 | import "C" 13 | 14 | import ( 15 | "fmt" 16 | "unsafe" 17 | 18 | alog "github.com/v2fly/v2ray-core/v5/app/log" 19 | 20 | "github.com/v2fly/v2ray-core/v5/common" 21 | "github.com/v2fly/v2ray-core/v5/common/log" 22 | "github.com/v2fly/v2ray-core/v5/common/serial" 23 | ) 24 | 25 | var ( 26 | ctag = C.CString("v2ray") 27 | ) 28 | 29 | type androidLogger struct{} 30 | 31 | func (l *androidLogger) Handle(msg log.Message) { 32 | var priority = C.ANDROID_LOG_FATAL // this value should never be used in client mode 33 | var message string 34 | switch msg := msg.(type) { 35 | case *log.GeneralMessage: 36 | switch msg.Severity { 37 | case log.Severity_Error: 38 | priority = C.ANDROID_LOG_ERROR 39 | case log.Severity_Warning: 40 | priority = C.ANDROID_LOG_WARN 41 | case log.Severity_Info: 42 | priority = C.ANDROID_LOG_INFO 43 | case log.Severity_Debug: 44 | priority = C.ANDROID_LOG_DEBUG 45 | } 46 | message = serial.ToString(msg.Content) 47 | default: 48 | message = msg.String() 49 | } 50 | cstr := C.CString(message) 51 | defer C.free(unsafe.Pointer(cstr)) 52 | C.__android_log_write(C.int(priority), ctag, cstr) 53 | } 54 | 55 | func logInit() { 56 | common.Must(alog.RegisterHandlerCreator(alog.LogType_Console, func(_ alog.LogType, _ alog.HandlerCreatorOptions) (log.Handler, error) { 57 | return &androidLogger{}, nil 58 | })) 59 | } 60 | 61 | func logFatal(v ...interface{}) { 62 | cstr := C.CString(fmt.Sprintln(v...)) 63 | defer C.free(unsafe.Pointer(cstr)) 64 | C.__android_log_write(C.ANDROID_LOG_FATAL, ctag, cstr) 65 | } 66 | 67 | func logWarn(v ...interface{}) { 68 | (&androidLogger{}).Handle(&log.GeneralMessage{Severity: log.Severity_Warning, Content: fmt.Sprintln(v...)}) 69 | } 70 | 71 | func logInfo(v ...interface{}) { 72 | (&androidLogger{}).Handle(&log.GeneralMessage{Severity: log.Severity_Info, Content: fmt.Sprintln(v...)}) 73 | } 74 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "os/user" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "syscall" 13 | 14 | "github.com/golang/protobuf/proto" 15 | "google.golang.org/protobuf/types/known/anypb" 16 | 17 | _ "github.com/v2fly/v2ray-core/v5/app/proxyman/inbound" 18 | _ "github.com/v2fly/v2ray-core/v5/app/proxyman/outbound" 19 | 20 | core "github.com/v2fly/v2ray-core/v5" 21 | vlog "github.com/v2fly/v2ray-core/v5/app/log" 22 | clog "github.com/v2fly/v2ray-core/v5/common/log" 23 | 24 | "github.com/v2fly/v2ray-core/v5/app/dispatcher" 25 | "github.com/v2fly/v2ray-core/v5/app/proxyman" 26 | "github.com/v2fly/v2ray-core/v5/common/net" 27 | "github.com/v2fly/v2ray-core/v5/common/platform/filesystem" 28 | "github.com/v2fly/v2ray-core/v5/common/protocol" 29 | "github.com/v2fly/v2ray-core/v5/common/serial" 30 | "github.com/v2fly/v2ray-core/v5/proxy/dokodemo" 31 | "github.com/v2fly/v2ray-core/v5/proxy/freedom" 32 | "github.com/v2fly/v2ray-core/v5/transport/internet" 33 | "github.com/v2fly/v2ray-core/v5/transport/internet/grpc" 34 | "github.com/v2fly/v2ray-core/v5/transport/internet/quic" 35 | "github.com/v2fly/v2ray-core/v5/transport/internet/tls" 36 | "github.com/v2fly/v2ray-core/v5/transport/internet/websocket" 37 | "github.com/teddysun/v2ray-plugin/cmd/build" 38 | ) 39 | 40 | var ( 41 | VERSION = "custom" 42 | ) 43 | 44 | var ( 45 | vpn = flag.Bool("V", false, "Run in VPN mode.") 46 | fastOpen = flag.Bool("fast-open", false, "Enable TCP fast open.") 47 | localAddr = flag.String("localAddr", "127.0.0.1", "local address to listen on.") 48 | localPort = flag.String("localPort", "1984", "local port to listen on.") 49 | remoteAddr = flag.String("remoteAddr", "127.0.0.1", "remote address to forward.") 50 | remotePort = flag.String("remotePort", "1080", "remote port to forward.") 51 | path = flag.String("path", "/", "URL path for websocket.") 52 | serviceName = flag.String("serviceName", "GunService", "Service name for grpc.") 53 | host = flag.String("host", "cloudfront.com", "Hostname for server.") 54 | tlsEnabled = flag.Bool("tls", false, "Enable TLS.") 55 | cert = flag.String("cert", "", "Path to TLS certificate file. Overrides certRaw. Default: ~/.acme.sh/{host}/fullchain.cer") 56 | certRaw = flag.String("certRaw", "", "Raw TLS certificate content. Intended only for Android.") 57 | key = flag.String("key", "", "(server) Path to TLS key file. Default: ~/.acme.sh/{host}/{host}.key") 58 | mode = flag.String("mode", "websocket", "Transport mode: websocket, quic (enforced tls), grpc.") 59 | mux = flag.Int("mux", 1, "Concurrent multiplexed connections (websocket client mode only).") 60 | server = flag.Bool("server", false, "Run in server mode") 61 | logLevel = flag.String("loglevel", "", "loglevel for v2ray: debug, info, warning (default), error, none.") 62 | version = flag.Bool("version", false, "Show current version of v2ray-plugin") 63 | fwmark = flag.Int("fwmark", 0, "Set SO_MARK option for outbound sockets.") 64 | ) 65 | 66 | func homeDir() string { 67 | usr, err := user.Current() 68 | if err != nil { 69 | logFatal(err) 70 | os.Exit(1) 71 | } 72 | return usr.HomeDir 73 | } 74 | 75 | func readCertificate() ([]byte, error) { 76 | if *cert != "" { 77 | return filesystem.ReadFile(*cert) 78 | } 79 | if *certRaw != "" { 80 | certHead := "-----BEGIN CERTIFICATE-----" 81 | certTail := "-----END CERTIFICATE-----" 82 | fixedCert := certHead + "\n" + *certRaw + "\n" + certTail 83 | return []byte(fixedCert), nil 84 | } 85 | panic("thou shalt not reach hear") 86 | } 87 | 88 | func logConfig(logLevel string) *vlog.Config { 89 | config := &vlog.Config{ 90 | Error: &vlog.LogSpecification{Type: vlog.LogType_Console, Level: clog.Severity_Warning}, 91 | Access: &vlog.LogSpecification{Type: vlog.LogType_Console}, 92 | } 93 | level := strings.ToLower(logLevel) 94 | switch level { 95 | case "debug": 96 | config.Error.Level = clog.Severity_Debug 97 | case "info": 98 | config.Error.Level = clog.Severity_Info 99 | case "error": 100 | config.Error.Level = clog.Severity_Error 101 | case "none": 102 | config.Error.Type = vlog.LogType_None 103 | config.Access.Type = vlog.LogType_None 104 | } 105 | return config 106 | } 107 | 108 | func parseLocalAddr(localAddr string) []string { 109 | return strings.Split(localAddr, "|") 110 | } 111 | 112 | func generateConfig() (*core.Config, error) { 113 | lport, err := net.PortFromString(*localPort) 114 | if err != nil { 115 | return nil, newError("invalid localPort:", *localPort).Base(err) 116 | } 117 | rport, err := strconv.ParseUint(*remotePort, 10, 32) 118 | if err != nil { 119 | return nil, newError("invalid remotePort:", *remotePort).Base(err) 120 | } 121 | outboundProxy := serial.ToTypedMessage(&freedom.Config{ 122 | DestinationOverride: &freedom.DestinationOverride{ 123 | Server: &protocol.ServerEndpoint{ 124 | Address: net.NewIPOrDomain(net.ParseAddress(*remoteAddr)), 125 | Port: uint32(rport), 126 | }, 127 | }, 128 | }) 129 | 130 | var transportSettings proto.Message 131 | var connectionReuse bool 132 | switch *mode { 133 | case "websocket": 134 | transportSettings = &websocket.Config{ 135 | Path: *path, 136 | Header: []*websocket.Header{ 137 | {Key: "Host", Value: *host}, 138 | }, 139 | } 140 | if *mux != 0 { 141 | connectionReuse = true 142 | } 143 | case "quic": 144 | transportSettings = &quic.Config{ 145 | Security: &protocol.SecurityConfig{Type: protocol.SecurityType_NONE}, 146 | } 147 | *tlsEnabled = true 148 | case "grpc": 149 | transportSettings = &grpc.Config{ 150 | ServiceName: *serviceName, 151 | } 152 | default: 153 | return nil, newError("unsupported mode:", *mode) 154 | } 155 | 156 | // hack v2ray-core grpc protocolName 157 | if *mode == "grpc" { 158 | *mode = "gun" 159 | } 160 | 161 | streamConfig := internet.StreamConfig{ 162 | ProtocolName: *mode, 163 | TransportSettings: []*internet.TransportConfig{{ 164 | ProtocolName: *mode, 165 | Settings: serial.ToTypedMessage(transportSettings), 166 | }}, 167 | } 168 | if *fastOpen || *fwmark != 0 { 169 | socketConfig := &internet.SocketConfig{} 170 | if *fastOpen { 171 | socketConfig.Tfo = internet.SocketConfig_Enable 172 | } 173 | if *fwmark != 0 { 174 | socketConfig.Mark = uint32(*fwmark) 175 | } 176 | 177 | streamConfig.SocketSettings = socketConfig 178 | } 179 | if *tlsEnabled { 180 | tlsConfig := tls.Config{ServerName: *host} 181 | if *server { 182 | certificate := tls.Certificate{} 183 | if *cert == "" && *certRaw == "" { 184 | *cert = fmt.Sprintf("%s/.acme.sh/%s/fullchain.cer", homeDir(), *host) 185 | logWarn("No TLS cert specified, trying", *cert) 186 | } 187 | certificate.Certificate, err = readCertificate() 188 | if err != nil { 189 | return nil, newError("failed to read cert").Base(err) 190 | } 191 | if *key == "" { 192 | *key = fmt.Sprintf("%[1]s/.acme.sh/%[2]s/%[2]s.key", homeDir(), *host) 193 | logWarn("No TLS key specified, trying", *key) 194 | } 195 | certificate.Key, err = filesystem.ReadFile(*key) 196 | if err != nil { 197 | return nil, newError("failed to read key file").Base(err) 198 | } 199 | tlsConfig.Certificate = []*tls.Certificate{&certificate} 200 | } else if *cert != "" || *certRaw != "" { 201 | certificate := tls.Certificate{Usage: tls.Certificate_AUTHORITY_VERIFY} 202 | certificate.Certificate, err = readCertificate() 203 | if err != nil { 204 | return nil, newError("failed to read cert").Base(err) 205 | } 206 | tlsConfig.Certificate = []*tls.Certificate{&certificate} 207 | } 208 | streamConfig.SecurityType = serial.GetMessageType(&tlsConfig) 209 | streamConfig.SecuritySettings = []*anypb.Any{serial.ToTypedMessage(&tlsConfig)} 210 | } 211 | 212 | apps := []*anypb.Any{ 213 | serial.ToTypedMessage(&dispatcher.Config{}), 214 | serial.ToTypedMessage(&proxyman.InboundConfig{}), 215 | serial.ToTypedMessage(&proxyman.OutboundConfig{}), 216 | serial.ToTypedMessage(logConfig(*logLevel)), 217 | } 218 | 219 | if *server { 220 | proxyAddress := net.LocalHostIP 221 | if connectionReuse { 222 | // This address is required when mux is used on client. 223 | // dokodemo is not aware of mux connections by itself. 224 | proxyAddress = net.ParseAddress("v1.mux.cool") 225 | } 226 | localAddrs := parseLocalAddr(*localAddr) 227 | inbounds := make([]*core.InboundHandlerConfig, len(localAddrs)) 228 | 229 | for i := 0; i < len(localAddrs); i++ { 230 | inbounds[i] = &core.InboundHandlerConfig{ 231 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ 232 | PortRange: net.SinglePortRange(lport), 233 | Listen: net.NewIPOrDomain(net.ParseAddress(localAddrs[i])), 234 | StreamSettings: &streamConfig, 235 | }), 236 | ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ 237 | Address: net.NewIPOrDomain(proxyAddress), 238 | Networks: []net.Network{net.Network_TCP}, 239 | }), 240 | } 241 | } 242 | 243 | return &core.Config{ 244 | Inbound: inbounds, 245 | Outbound: []*core.OutboundHandlerConfig{{ 246 | ProxySettings: outboundProxy, 247 | }}, 248 | App: apps, 249 | }, nil 250 | } else { 251 | senderConfig := proxyman.SenderConfig{StreamSettings: &streamConfig} 252 | if connectionReuse { 253 | senderConfig.MultiplexSettings = &proxyman.MultiplexingConfig{Enabled: true, Concurrency: uint32(*mux)} 254 | } 255 | return &core.Config{ 256 | Inbound: []*core.InboundHandlerConfig{{ 257 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ 258 | PortRange: net.SinglePortRange(lport), 259 | Listen: net.NewIPOrDomain(net.ParseAddress(*localAddr)), 260 | }), 261 | ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ 262 | Address: net.NewIPOrDomain(net.LocalHostIP), 263 | Networks: []net.Network{net.Network_TCP}, 264 | }), 265 | }}, 266 | Outbound: []*core.OutboundHandlerConfig{{ 267 | SenderSettings: serial.ToTypedMessage(&senderConfig), 268 | ProxySettings: outboundProxy, 269 | }}, 270 | App: apps, 271 | }, nil 272 | } 273 | } 274 | 275 | func startV2Ray() (core.Server, error) { 276 | 277 | opts, err := parseEnv() 278 | 279 | if err == nil { 280 | if c, b := opts.Get("mode"); b { 281 | *mode = c 282 | } 283 | if c, b := opts.Get("mux"); b { 284 | if i, err := strconv.Atoi(c); err == nil { 285 | *mux = i 286 | } else { 287 | logWarn("failed to parse mux, use default value") 288 | } 289 | } 290 | if _, b := opts.Get("tls"); b { 291 | *tlsEnabled = true 292 | } 293 | if c, b := opts.Get("host"); b { 294 | *host = c 295 | } 296 | if c, b := opts.Get("path"); b { 297 | *path = c 298 | } 299 | if c, b := opts.Get("serviceName"); b { 300 | *serviceName = c 301 | } 302 | if c, b := opts.Get("cert"); b { 303 | *cert = c 304 | } 305 | if c, b := opts.Get("certRaw"); b { 306 | *certRaw = c 307 | } 308 | if c, b := opts.Get("key"); b { 309 | *key = c 310 | } 311 | if c, b := opts.Get("loglevel"); b { 312 | *logLevel = c 313 | } 314 | if _, b := opts.Get("server"); b { 315 | *server = true 316 | } 317 | if c, b := opts.Get("localAddr"); b { 318 | if *server { 319 | *remoteAddr = c 320 | } else { 321 | *localAddr = c 322 | } 323 | } 324 | if c, b := opts.Get("localPort"); b { 325 | if *server { 326 | *remotePort = c 327 | } else { 328 | *localPort = c 329 | } 330 | } 331 | if c, b := opts.Get("remoteAddr"); b { 332 | if *server { 333 | *localAddr = c 334 | } else { 335 | *remoteAddr = c 336 | } 337 | } 338 | if c, b := opts.Get("remotePort"); b { 339 | if *server { 340 | *localPort = c 341 | } else { 342 | *remotePort = c 343 | } 344 | } 345 | 346 | if _, b := opts.Get("fastOpen"); b { 347 | *fastOpen = true 348 | } 349 | 350 | if _, b := opts.Get("__android_vpn"); b { 351 | *vpn = true 352 | } 353 | 354 | if c, b := opts.Get("fwmark"); b { 355 | if i, err := strconv.Atoi(c); err == nil { 356 | *fwmark = i 357 | } else { 358 | logWarn("failed to parse fwmark, use default value") 359 | } 360 | } 361 | 362 | if *vpn { 363 | registerControlFunc() 364 | } 365 | } 366 | 367 | config, err := generateConfig() 368 | if err != nil { 369 | return nil, newError("failed to parse config").Base(err) 370 | } 371 | instance, err := core.New(config) 372 | if err != nil { 373 | return nil, newError("failed to create v2ray instance").Base(err) 374 | } 375 | return instance, nil 376 | } 377 | 378 | func printCoreVersion() { 379 | version := core.VersionStatement() 380 | for _, s := range version { 381 | logInfo(s) 382 | } 383 | } 384 | 385 | func printVersion() { 386 | osVersion, osKernel := build.GetOSVersion() 387 | if osVersion == "" { 388 | osVersion = "unknown" 389 | } 390 | if osKernel == "" { 391 | osKernel = "unknown" 392 | } 393 | 394 | arch := build.GetArch() 395 | 396 | fmt.Println("v2ray-plugin", VERSION) 397 | fmt.Println("Yet another SIP003 plugin for shadowsocks") 398 | fmt.Printf("- os/version: %s\n", osVersion) 399 | fmt.Printf("- os/kernel: %s\n", osKernel) 400 | fmt.Printf("- os/type: %s\n", runtime.GOOS) 401 | fmt.Printf("- os/arch: %s\n", arch) 402 | fmt.Printf("- go/version: %s\n", runtime.Version()) 403 | } 404 | 405 | func main() { 406 | flag.Parse() 407 | 408 | if *version { 409 | printVersion() 410 | return 411 | } 412 | 413 | logInit() 414 | 415 | printCoreVersion() 416 | 417 | server, err := startV2Ray() 418 | if err != nil { 419 | logFatal(err.Error()) 420 | // Configuration error. Exit with a special value to prevent systemd from restarting. 421 | os.Exit(23) 422 | } 423 | if err := server.Start(); err != nil { 424 | logFatal("failed to start server:", err.Error()) 425 | os.Exit(1) 426 | } 427 | 428 | defer func() { 429 | err := server.Close() 430 | if err != nil { 431 | logWarn(err.Error()) 432 | } 433 | }() 434 | 435 | { 436 | osSignals := make(chan os.Signal, 1) 437 | signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM) 438 | <-osSignals 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // +build !android 2 | 3 | package main 4 | 5 | func registerControlFunc() { 6 | } 7 | -------------------------------------------------------------------------------- /utils_android.go: -------------------------------------------------------------------------------- 1 | // +build android 2 | 3 | package main 4 | 5 | /* 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define ANCIL_FD_BUFFER(n) \ 16 | struct { \ 17 | struct cmsghdr h; \ 18 | int fd[n]; \ 19 | } 20 | 21 | int 22 | ancil_send_fds_with_buffer(int sock, const int *fds, unsigned n_fds, void *buffer) 23 | { 24 | struct msghdr msghdr; 25 | char nothing = '!'; 26 | struct iovec nothing_ptr; 27 | struct cmsghdr *cmsg; 28 | int i; 29 | 30 | nothing_ptr.iov_base = ¬hing; 31 | nothing_ptr.iov_len = 1; 32 | msghdr.msg_name = NULL; 33 | msghdr.msg_namelen = 0; 34 | msghdr.msg_iov = ¬hing_ptr; 35 | msghdr.msg_iovlen = 1; 36 | msghdr.msg_flags = 0; 37 | msghdr.msg_control = buffer; 38 | msghdr.msg_controllen = sizeof(struct cmsghdr) + sizeof(int) * n_fds; 39 | cmsg = CMSG_FIRSTHDR(&msghdr); 40 | cmsg->cmsg_len = msghdr.msg_controllen; 41 | cmsg->cmsg_level = SOL_SOCKET; 42 | cmsg->cmsg_type = SCM_RIGHTS; 43 | for(i = 0; i < n_fds; i++) 44 | ((int *)CMSG_DATA(cmsg))[i] = fds[i]; 45 | return(sendmsg(sock, &msghdr, 0) >= 0 ? 0 : -1); 46 | } 47 | 48 | int 49 | ancil_send_fd(int sock, int fd) 50 | { 51 | ANCIL_FD_BUFFER(1) buffer; 52 | 53 | return(ancil_send_fds_with_buffer(sock, &fd, 1, &buffer)); 54 | } 55 | 56 | void 57 | set_timeout(int sock) 58 | { 59 | struct timeval tv; 60 | tv.tv_sec = 3; 61 | tv.tv_usec = 0; 62 | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); 63 | setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); 64 | } 65 | 66 | */ 67 | import "C" 68 | 69 | import ( 70 | "log" 71 | "syscall" 72 | 73 | vinternet "github.com/v2fly/v2ray-core/v5/transport/internet" 74 | ) 75 | 76 | func ControlOnConnSetup(network string, address string, s uintptr) error { 77 | fd := int(s) 78 | path := "protect_path" 79 | 80 | socket, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 81 | if err != nil { 82 | log.Println(err) 83 | return err 84 | } 85 | 86 | defer syscall.Close(socket) 87 | 88 | C.set_timeout(C.int(socket)) 89 | 90 | err = syscall.Connect(socket, &syscall.SockaddrUnix{Name: path}) 91 | if err != nil { 92 | log.Println(err) 93 | return err 94 | } 95 | 96 | C.ancil_send_fd(C.int(socket), C.int(fd)) 97 | 98 | dummy := []byte{1} 99 | n, err := syscall.Read(socket, dummy) 100 | if err != nil { 101 | log.Println(err) 102 | return err 103 | } 104 | if n != 1 { 105 | log.Println("Failed to protect fd: ", fd) 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func registerControlFunc() { 112 | vinternet.RegisterDialerController(ControlOnConnSetup) 113 | vinternet.RegisterListenerController(ControlOnConnSetup) 114 | } 115 | --------------------------------------------------------------------------------