├── .github └── workflows │ ├── main.yml │ └── tidy.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── geoip.dat └── geosite.dat ├── gen_assets.sh ├── go.mod ├── go.sum └── libv2ray_main.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | required: false 8 | description: 'Release tag something like v5.29.3' 9 | type: string 10 | pull_request: 11 | branches: 12 | - master 13 | push: 14 | branches: 15 | - master 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | 21 | - name: Checkout repository 22 | uses: actions/checkout@v4.2.2 23 | 24 | - name: Setup Golang 25 | uses: actions/setup-go@v5.4.0 26 | with: 27 | go-version-file: 'go.mod' 28 | 29 | - name: Install gomobile 30 | run: | 31 | go install golang.org/x/mobile/cmd/gomobile@latest 32 | export PATH=$PATH:~/go/bin 33 | 34 | - name: Setup Android SDK 35 | uses: android-actions/setup-android@v3.2.0 36 | with: 37 | log-accepted-android-sdk-licenses: false 38 | cmdline-tools-version: '12266719' 39 | packages: 'platforms;android-35 build-tools;35.0.0 platform-tools' 40 | 41 | - name: Install NDK 42 | run: | 43 | echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \ 44 | --channel=3 \ 45 | --install "ndk;29.0.13113456" 46 | echo "NDK_HOME=$ANDROID_HOME/ndk/29.0.13113456" >> $GITHUB_ENV 47 | 48 | - name: Build 49 | run: | 50 | mkdir -p assets data 51 | bash gen_assets.sh download 52 | cp -v data/*.dat assets/ 53 | gomobile init 54 | go mod tidy 55 | gomobile bind -v -androidapi 21 -trimpath -ldflags='-s -w -buildid=' ./ 56 | 57 | - name: Upload build artifacts 58 | uses: actions/upload-artifact@v4.6.2 59 | with: 60 | name: libv2ray 61 | path: | 62 | ${{ github.workspace }}/libv2ray*r 63 | 64 | - name: Upload AndroidLibV2rayLite to release 65 | if: github.event.inputs.release_tag != '' 66 | uses: svenstaro/upload-release-action@v2 67 | with: 68 | file: ./libv2ray*r 69 | tag: ${{ github.event.inputs.release_tag }} 70 | file_glob: true 71 | -------------------------------------------------------------------------------- /.github/workflows/tidy.yml: -------------------------------------------------------------------------------- 1 | name: Check and Update v2ray-core 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout our repository 13 | uses: actions/checkout@v4.2.2 14 | with: 15 | fetch-depth: '0' 16 | 17 | - name: Fetch latest release tag from external repository 18 | id: fetch-release 19 | run: | 20 | EXTERNAL_REPO="v2fly/v2ray-core" 21 | LATEST_TAG=$(curl -s https://api.github.com/repos/$EXTERNAL_REPO/tags | jq -r '.[0]') 22 | LATEST_TAG_NAME=$(echo $LATEST_TAG | jq -r .name) 23 | LATEST_TAG_SHA=$(echo $LATEST_TAG | jq -r .commit.sha) 24 | echo "Latest tag from external repo: $LATEST_TAG_NAME" 25 | echo "LATEST_TAG_NAME=$LATEST_TAG_NAME" >> $GITHUB_ENV 26 | echo "LATEST_TAG_SHA=$LATEST_TAG_SHA" >> $GITHUB_ENV 27 | 28 | - name: Fetch current repository release tag 29 | id: fetch-current-tag 30 | run: | 31 | CURRENT_TAG_NAME=$(git describe --tags --abbrev=0) 32 | echo "Current tag in this repo: $CURRENT_TAG_NAME" 33 | echo "CURRENT_TAG_NAME=$CURRENT_TAG_NAME" >> $GITHUB_ENV 34 | 35 | - name: Compare tags 36 | id: compare-tags 37 | run: | 38 | if [ "$LATEST_TAG_NAME" != "$CURRENT_TAG_NAME" ]; then 39 | if [ "$(printf '%s\n' "$LATEST_TAG_NAME" "$CURRENT_TAG_NAME" | sort -V | tail -n1)" == "$CURRENT_TAG_NAME" ]; then 40 | echo "Upstream LATEST_TAG_NAME less than the CURRENT_TAG_NAME, no update needed." 41 | else 42 | echo "Tags are different. Updating..." 43 | echo "needs_update=true" >> $GITHUB_ENV 44 | fi 45 | else 46 | echo "Tags are the same. No update needed." 47 | echo "needs_update=false" >> $GITHUB_ENV 48 | fi 49 | 50 | - name: Setup Golang 51 | if: env.needs_update == 'true' 52 | uses: actions/setup-go@v5.4.0 53 | with: 54 | go-version: 'stable' 55 | 56 | - name: Update dependencies excluding gvisor and grpc 57 | if: env.needs_update == 'true' 58 | run: | 59 | # Lock gvisor version to a specific revision 60 | go mod edit -replace=gvisor.dev/gvisor=gvisor.dev/gvisor@v0.0.0-20231020174304-b8a429915ff1 61 | 62 | # Lock grpc version to a specific revision 63 | go mod edit -replace=google.golang.org/grpc=google.golang.org/grpc@v1.66.3 64 | 65 | # Update specific dependency 66 | go get github.com/v2fly/v2ray-core/v5@${{ env.LATEST_TAG_NAME }} 67 | 68 | # Update all other dependencies 69 | go get -u 70 | go get gvisor.dev/gvisor@go 71 | 72 | # Clean up and verify module dependencies 73 | go mod tidy -v 74 | 75 | # Show changes 76 | git diff 77 | - name: Commit and push changes 78 | id: auto-commit-action 79 | if: env.needs_update == 'true' 80 | uses: stefanzweifel/git-auto-commit-action@v5.1.0 81 | with: 82 | commit_message: Updating v2ray-core to ${{ env.LATEST_TAG_NAME }} ${{ env.LATEST_TAG_SHA }} 83 | tagging_message: ${{ env.LATEST_TAG_NAME }} 84 | 85 | - name: Trigger build 86 | if: env.needs_update == 'true' && steps.auto-commit-action.outputs.changes_detected == 'true' 87 | run: | 88 | curl -X POST \ 89 | -H "Accept: application/vnd.github.v3+json" \ 90 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 91 | https://api.github.com/repos/${{ github.repository }}/actions/workflows/main.yml/dispatches \ 92 | -d "{ 93 | \"ref\": \"main\", 94 | \"inputs\": { 95 | \"release_tag\": \"${{ env.LATEST_TAG_NAME }}\" 96 | } 97 | }" 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore IDE/Editor directories and files 2 | .vscode/ 3 | .idea/ 4 | 5 | # Ignore build directories and files 6 | gradle/build.gradle 7 | demo/ 8 | assets/ 9 | build/ 10 | bin/ 11 | out/ 12 | *.pb.go 13 | binary*.go 14 | 15 | # Ignore OS generated files 16 | .DS_Store 17 | Thumbs.db 18 | 19 | # Ignore compressed files 20 | *.tgz 21 | *.gz 22 | *.zip 23 | 24 | # Ignore logs and temporary files 25 | *.log 26 | *.tmp 27 | 28 | # Ignore Go binary files 29 | *.exe 30 | *.dll 31 | *.so 32 | 33 | # Ignore dependency directories 34 | vendor/ 35 | node_modules/ 36 | 37 | # Ignore generated libraries 38 | libv2ray*.[a|j]ar 39 | 40 | # Ignore configuration files 41 | conf/demo/ 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidLibV2rayLite 2 | 3 | ## Build requirements 4 | * JDK 5 | * Android SDK 6 | * Go 7 | * gomobile 8 | 9 | ## Build instructions 10 | 1. `git clone [repo] && cd AndroidLibV2rayLite` 11 | 2. `gomobile init` 12 | 3. `go mod tidy -v` 13 | 4. `gomobile bind -v -androidapi 21 -ldflags='-s -w' ./` 14 | -------------------------------------------------------------------------------- /assets/geoip.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/ebe15459ee07709ad9621d93f58a74b1bcf8e94d/assets/geoip.dat -------------------------------------------------------------------------------- /assets/geosite.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/ebe15459ee07709ad9621d93f58a74b1bcf8e94d/assets/geosite.dat -------------------------------------------------------------------------------- /gen_assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | # Set magic variables for current file & dir 8 | __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | __file="${__dir}/$(basename "${BASH_SOURCE[0]}")" 10 | __base="$(basename "${__file}" .sh)" 11 | 12 | DATADIR="${__dir}/data" 13 | 14 | 15 | # Check for required dependencies 16 | check_dependencies() { 17 | command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but it's not installed. Aborting."; exit 1; } 18 | command -v go >/dev/null 2>&1 || { echo >&2 "Go is required but it's not installed. Aborting."; exit 1; } 19 | } 20 | 21 | 22 | # Download data function 23 | download_dat() { 24 | echo "Downloading geoip.dat..." 25 | curl -sL https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat -o "$DATADIR/geoip.dat" 26 | 27 | echo "Downloading geosite.dat..." 28 | curl -sL https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat -o "$DATADIR/geosite.dat" 29 | } 30 | 31 | # Main execution logic 32 | ACTION="${1:-download}" 33 | 34 | check_dependencies 35 | 36 | case $ACTION in 37 | "download") download_dat ;; 38 | *) echo "Invalid action: $ACTION" ; exit 1 ;; 39 | esac 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/2dust/AndroidLibV2rayLite 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/v2fly/v2ray-core/v5 v5.33.0 7 | golang.org/x/mobile v0.0.0-20250520180527-a1d90793fc63 8 | ) 9 | 10 | require github.com/pion/transport/v3 v3.0.7 // indirect 11 | 12 | require ( 13 | github.com/adrg/xdg v0.5.3 // indirect 14 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect 15 | github.com/andybalholm/brotli v1.1.1 // indirect 16 | github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7 // indirect 17 | github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect 18 | github.com/bufbuild/protocompile v0.14.1 // indirect 19 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 20 | github.com/cloudflare/circl v1.6.1 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/desertbit/timer v1.0.1 // indirect 23 | github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect 24 | github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a // indirect 25 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 26 | github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 // indirect 27 | github.com/golang/protobuf v1.5.4 // indirect 28 | github.com/google/btree v1.1.3 // indirect 29 | github.com/google/gopacket v1.1.19 // indirect 30 | github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect 31 | github.com/gorilla/websocket v1.5.3 // indirect 32 | github.com/improbable-eng/grpc-web v0.15.0 // indirect 33 | github.com/jhump/protoreflect v1.17.0 // indirect 34 | github.com/klauspost/compress v1.18.0 // indirect 35 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 36 | github.com/klauspost/reedsolomon v1.12.4 // indirect 37 | github.com/lunixbochs/struc v0.0.0-20241101090106-8d528fa2c543 // indirect 38 | github.com/miekg/dns v1.1.66 // indirect 39 | github.com/mustafaturan/bus v1.0.2 // indirect 40 | github.com/mustafaturan/monoton v1.0.0 // indirect 41 | github.com/onsi/ginkgo/v2 v2.23.4 // indirect 42 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 43 | github.com/pelletier/go-toml v1.9.5 // indirect 44 | github.com/pion/dtls/v2 v2.2.12 // indirect 45 | github.com/pion/logging v0.2.3 // indirect 46 | github.com/pion/randutil v0.1.0 // indirect 47 | github.com/pion/sctp v1.8.39 // indirect 48 | github.com/pion/transport/v2 v2.2.10 // indirect 49 | github.com/pires/go-proxyproto v0.8.1 // indirect 50 | github.com/pmezard/go-difflib v1.0.0 // indirect 51 | github.com/quic-go/qpack v0.5.1 // indirect 52 | github.com/quic-go/quic-go v0.52.0 // indirect 53 | github.com/refraction-networking/utls v1.7.3 // indirect 54 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect 55 | github.com/rs/cors v1.11.1 // indirect 56 | github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect 57 | github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect 58 | github.com/stretchr/objx v0.5.2 // indirect 59 | github.com/stretchr/testify v1.10.0 // indirect 60 | github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 // indirect 61 | github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 // indirect 62 | github.com/v2fly/hysteria/core/v2 v2.0.0-20250113081444-b0a0747ac7ab // indirect 63 | github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect 64 | github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6 // indirect 65 | github.com/vincent-petithory/dataurl v1.0.0 // indirect 66 | github.com/xiaokangwang/VLite v0.0.0-20231225174116-75fa4b06e9f2 // indirect 67 | github.com/xtaci/smux v1.5.34 // indirect 68 | go.starlark.net v0.0.0-20250417143717-f57e51f710eb // indirect 69 | go.uber.org/automaxprocs v1.6.0 // indirect 70 | go.uber.org/mock v0.5.2 // indirect 71 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect 72 | golang.org/x/crypto v0.38.0 // indirect 73 | golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect 74 | golang.org/x/mod v0.24.0 // indirect 75 | golang.org/x/net v0.40.0 // indirect 76 | golang.org/x/sync v0.14.0 // indirect 77 | golang.org/x/sys v0.33.0 // indirect 78 | golang.org/x/text v0.25.0 // indirect 79 | golang.org/x/time v0.11.0 // indirect 80 | golang.org/x/tools v0.33.0 // indirect 81 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect 82 | google.golang.org/grpc v1.72.2 // indirect 83 | google.golang.org/protobuf v1.36.6 // indirect 84 | gopkg.in/yaml.v3 v3.0.1 // indirect 85 | gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 // indirect 86 | lukechampine.com/blake3 v1.4.1 // indirect 87 | nhooyr.io/websocket v1.8.17 // indirect 88 | ) 89 | 90 | replace google.golang.org/grpc => google.golang.org/grpc v1.66.3 91 | 92 | replace gvisor.dev/gvisor => gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 93 | -------------------------------------------------------------------------------- /libv2ray_main.go: -------------------------------------------------------------------------------- 1 | package libv2ray 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "net/http" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | core "github.com/v2fly/v2ray-core/v5" 18 | coreapplog "github.com/v2fly/v2ray-core/v5/app/log" 19 | corecommlog "github.com/v2fly/v2ray-core/v5/common/log" 20 | corenet "github.com/v2fly/v2ray-core/v5/common/net" 21 | corefilesystem "github.com/v2fly/v2ray-core/v5/common/platform/filesystem" 22 | corestats "github.com/v2fly/v2ray-core/v5/features/stats" 23 | coreserial "github.com/v2fly/v2ray-core/v5/infra/conf/serial" 24 | _ "github.com/v2fly/v2ray-core/v5/main/distro/all" 25 | mobasset "golang.org/x/mobile/asset" 26 | ) 27 | 28 | // Constants for environment variables 29 | const ( 30 | coreAsset = "v2ray.location.asset" 31 | ) 32 | 33 | // CoreController represents a controller for managing v2fly core instance lifecycle 34 | type CoreController struct { 35 | CallbackHandler CoreCallbackHandler 36 | statsManager corestats.Manager 37 | coreMutex sync.Mutex 38 | coreInstance *core.Instance 39 | IsRunning bool 40 | } 41 | 42 | // CoreCallbackHandler defines interface for receiving callbacks and notifications from the core service 43 | type CoreCallbackHandler interface { 44 | Startup() int 45 | Shutdown() int 46 | OnEmitStatus(int, string) int 47 | } 48 | 49 | // consoleLogWriter implements a log writer without datetime stamps 50 | // as Android system already adds timestamps to each log line 51 | type consoleLogWriter struct { 52 | logger *log.Logger // Standard logger 53 | } 54 | 55 | // setEnvVariable safely sets an environment variable and logs any errors encountered. 56 | func setEnvVariable(key, value string) { 57 | if err := os.Setenv(key, value); err != nil { 58 | log.Printf("Failed to set environment variable %s: %v. Please check your configuration.", key, err) 59 | } 60 | } 61 | 62 | // InitCoreEnv initializes environment variables and file system handlers for the core 63 | // It sets up asset path, certificate path, XUDP base key and customizes the file reader 64 | // to support Android asset system 65 | func InitCoreEnv(envPath string, key string) { 66 | // Set asset/cert paths 67 | if len(envPath) > 0 { 68 | setEnvVariable(coreAsset, envPath) 69 | } 70 | 71 | // Custom file reader with path validation 72 | corefilesystem.NewFileReader = func(path string) (io.ReadCloser, error) { 73 | if _, err := os.Stat(path); os.IsNotExist(err) { 74 | _, file := filepath.Split(path) 75 | return mobasset.Open(file) 76 | } 77 | return os.Open(path) 78 | } 79 | } 80 | 81 | // NewCoreController initializes and returns a new CoreController instance 82 | // Sets up the console log handler and associates it with the provided callback handler 83 | func NewCoreController(s CoreCallbackHandler) *CoreController { 84 | // Register custom logger 85 | if err := coreapplog.RegisterHandlerCreator( 86 | coreapplog.LogType_Console, 87 | func(lt coreapplog.LogType, options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) { 88 | return corecommlog.NewLogger(createStdoutLogWriter()), nil 89 | }, 90 | ); err != nil { 91 | log.Printf("Logger registration failed: %v", err) 92 | } 93 | 94 | return &CoreController{ 95 | CallbackHandler: s, 96 | } 97 | } 98 | 99 | // StartLoop initializes and starts the core processing loop 100 | // Thread-safe method that configures and runs the v2fly core with the provided configuration 101 | // Returns immediately if the core is already running 102 | func (x *CoreController) StartLoop(configContent string) (err error) { 103 | x.coreMutex.Lock() 104 | defer x.coreMutex.Unlock() 105 | 106 | if x.IsRunning { 107 | log.Println("Core is already running") 108 | return nil 109 | } 110 | 111 | return x.doStartLoop(configContent) 112 | } 113 | 114 | // StopLoop safely stops the core processing loop and releases resources 115 | // Thread-safe method that shuts down the core instance and triggers necessary callbacks 116 | func (x *CoreController) StopLoop() error { 117 | x.coreMutex.Lock() 118 | defer x.coreMutex.Unlock() 119 | 120 | if x.IsRunning { 121 | x.doShutdown() 122 | x.CallbackHandler.OnEmitStatus(0, "Core stopped") 123 | } 124 | return nil 125 | } 126 | 127 | // QueryStats retrieves and resets traffic statistics for a specific outbound tag and direction 128 | // Returns the accumulated traffic value and resets the counter to zero 129 | // Returns 0 if the stats manager is not initialized or the counter doesn't exist 130 | func (x *CoreController) QueryStats(tag string, direct string) int64 { 131 | if x.statsManager == nil { 132 | return 0 133 | } 134 | counter := x.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct)) 135 | if counter == nil { 136 | return 0 137 | } 138 | return counter.Set(0) 139 | } 140 | 141 | // MeasureDelay measures network latency to a specified URL through the current core instance 142 | // Uses a 12-second timeout context and returns the round-trip time in milliseconds 143 | // An error is returned if the connection fails or returns an unexpected status 144 | func (x *CoreController) MeasureDelay(url string) (int64, error) { 145 | ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) 146 | defer cancel() 147 | 148 | return measureInstDelay(ctx, x.coreInstance, url) 149 | } 150 | 151 | // MeasureOutboundDelay measures the outbound delay for a given configuration and URL 152 | func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) { 153 | config, err := coreserial.LoadJSONConfig(strings.NewReader(ConfigureFileContent)) 154 | if err != nil { 155 | return -1, fmt.Errorf("Configuration load error: %w", err) 156 | } 157 | 158 | // Simplify config for testing 159 | config.Inbound = nil 160 | 161 | inst, err := core.New(config) 162 | if err != nil { 163 | return -1, fmt.Errorf("Instance creation failed: %w", err) 164 | } 165 | 166 | if err := inst.Start(); err != nil { 167 | return -1, fmt.Errorf("startup failed: %w", err) 168 | } 169 | defer inst.Close() 170 | return measureInstDelay(context.Background(), inst, url) 171 | } 172 | 173 | // CheckVersionX returns the library and v2fly versions 174 | func CheckVersionX() string { 175 | var version = 31 176 | return fmt.Sprintf("Lib v%d, V2fly-core v%s", version, core.Version()) 177 | } 178 | 179 | // doShutdown shuts down the v2fly instance and cleans up resources 180 | func (x *CoreController) doShutdown() { 181 | if x.coreInstance != nil { 182 | if err := x.coreInstance.Close(); err != nil { 183 | log.Printf("Core shutdown error: %v", err) 184 | } 185 | x.coreInstance = nil 186 | } 187 | x.IsRunning = false 188 | x.statsManager = nil 189 | } 190 | 191 | // doStartLoop sets up and starts the v2fly core 192 | func (x *CoreController) doStartLoop(configContent string) error { 193 | log.Println("Initializing core...") 194 | config, err := coreserial.LoadJSONConfig(strings.NewReader(configContent)) 195 | if err != nil { 196 | return fmt.Errorf("configuration error: %w", err) 197 | } 198 | 199 | x.coreInstance, err = core.New(config) 200 | if err != nil { 201 | return fmt.Errorf("core initialization failed: %w", err) 202 | } 203 | x.statsManager = x.coreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager) 204 | 205 | log.Println("Starting core...") 206 | x.IsRunning = true 207 | if err := x.coreInstance.Start(); err != nil { 208 | x.IsRunning = false 209 | return fmt.Errorf("startup failed: %w", err) 210 | } 211 | 212 | x.CallbackHandler.Startup() 213 | x.CallbackHandler.OnEmitStatus(0, "Started successfully, running") 214 | 215 | log.Println("Core started successfully") 216 | return nil 217 | } 218 | 219 | // measureInstDelay measures the delay for an instance to a given URL 220 | func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int64, error) { 221 | if inst == nil { 222 | return -1, errors.New("core instance is nil") 223 | } 224 | 225 | tr := &http.Transport{ 226 | TLSHandshakeTimeout: 6 * time.Second, 227 | DisableKeepAlives: true, 228 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 229 | dest, err := corenet.ParseDestination(fmt.Sprintf("%s:%s", network, addr)) 230 | if err != nil { 231 | return nil, err 232 | } 233 | return core.Dial(ctx, inst, dest) 234 | }, 235 | } 236 | 237 | client := &http.Client{ 238 | Transport: tr, 239 | Timeout: 12 * time.Second, 240 | } 241 | 242 | if url == "" { 243 | url = "https://www.google.com/generate_204" 244 | } 245 | 246 | req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) 247 | start := time.Now() 248 | resp, err := client.Do(req) 249 | if err != nil { 250 | return -1, err 251 | } 252 | defer resp.Body.Close() 253 | 254 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { 255 | return -1, fmt.Errorf("invalid status: %s", resp.Status) 256 | } 257 | return time.Since(start).Milliseconds(), nil 258 | } 259 | 260 | // Log writer implementation 261 | func (w *consoleLogWriter) Write(s string) error { 262 | w.logger.Print(s) 263 | return nil 264 | } 265 | 266 | func (w *consoleLogWriter) Close() error { 267 | return nil 268 | } 269 | 270 | // createStdoutLogWriter creates a logger that won't print date/time stamps 271 | func createStdoutLogWriter() corecommlog.WriterCreator { 272 | return func() corecommlog.Writer { 273 | return &consoleLogWriter{ 274 | logger: log.New(os.Stdout, "", 0), 275 | } 276 | } 277 | } 278 | --------------------------------------------------------------------------------