diff --git a/.github/workflows/main.yml b/.github/workflows/release.yml similarity index 82% rename from .github/workflows/main.yml rename to .github/workflows/release.yml index 2080020e..77cefc5a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/release.yml @@ -1,17 +1,12 @@ -name: Build +name: Build and Release AndroidLibXrayLite on: workflow_dispatch: - inputs: - release_tag: - required: false - type: string - pull_request: - branches: - - main + release: + types: [published] push: - branches: - - main + pull_request: + types: [opened, synchronize, reopened] jobs: build: runs-on: ubuntu-latest @@ -52,19 +47,19 @@ jobs: gomobile init go mod tidy gomobile bind -v -androidapi 21 -trimpath -ldflags='-s -w -buildid=' ./ - + - name: Upload build artifacts - if: github.event.inputs.release_tag == '' uses: actions/upload-artifact@v4.6.2 with: name: libv2ray path: | ${{ github.workspace }}/libv2ray*r - + - name: Upload AndroidLibXrayLite to release - if: github.event.inputs.release_tag != '' + if: github.event_name == 'release' && github.event.action == 'published' uses: svenstaro/upload-release-action@v2 with: + repo_token: ${{ secrets.GITHUB_TOKEN }} file: ./libv2ray*r - tag: ${{ github.event.inputs.release_tag }} - file_glob: true \ No newline at end of file + tag: ${{ github.ref }} + file_glob: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..a8aff824 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,55 @@ +name: Test + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test: + permissions: + contents: read + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + steps: + - name: Checkout codebase + uses: actions/checkout@v4.2.2 + + - name: Set up Go + uses: actions/setup-go@v5.4.0 + with: + go-version-file: go.mod + cache: true + check-latest: true + + - name: Cleanup old coverage files + shell: bash + run: | + rm -f coverage.txt || true # For all operating systems + + - name: Install dependencies + shell: bash + run: go mod tidy + + - name: Run Tests + shell: bash + run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... + + - name: Upload Coverage + uses: codecov/codecov-action@v5.4.2 + if: success() + + security: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + - run: | + go install github.com/securego/gosec/v2/cmd/gosec@latest + export PATH="$PATH:$(go env GOPATH)/bin" + gosec ./... + - name: Test + run: go test -timeout 1h -v ./... diff --git a/go.mod b/go.mod index 98d55f64..4ffe771f 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,18 @@ go 1.24.2 require ( github.com/xtls/xray-core v1.250306.1-0.20250331123338-ab5d7cf3d2d6 - golang.org/x/mobile v0.0.0-20250305212854-3a7bc9f8a4de - golang.org/x/sys v0.32.0 + golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 ) require ( github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 // indirect github.com/andybalholm/brotli v1.1.1 // indirect - github.com/cloudflare/circl v1.6.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20250417201159-ae779711f5d1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect @@ -27,7 +26,7 @@ require ( github.com/quic-go/quic-go v0.50.1 // indirect github.com/refraction-networking/utls v1.6.7 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect - github.com/sagernet/sing v0.6.5 // indirect + github.com/sagernet/sing v0.6.6 // indirect github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect @@ -35,22 +34,23 @@ require ( github.com/vishvananda/netns v0.0.5 // indirect github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/mock v0.5.0 // indirect + go.uber.org/mock v0.5.1 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.37.0 // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/tools v0.32.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect google.golang.org/grpc v1.71.1 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gvisor.dev/gvisor v0.0.0-20250403230555-2b1f43f26fbb // indirect + gvisor.dev/gvisor v0.0.0-20250416204613-04a61c0f3bd5 // indirect lukechampine.com/blake3 v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 046047e7..16eeee1c 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,8 +26,8 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20250417201159-ae779711f5d1 h1:ZehIDSjI9BX/Ntq1mt7UlZ8+fItakjJBf6TeQDV0i/0= +github.com/google/pprof v0.0.0-20250417201159-ae779711f5d1/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -62,8 +62,8 @@ github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= -github.com/sagernet/sing v0.6.5 h1:TBKTK6Ms0/MNTZm+cTC2hhKunE42XrNIdsxcYtWqeUU= -github.com/sagernet/sing v0.6.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE= +github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= @@ -99,20 +99,20 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= +go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/mobile v0.0.0-20250305212854-3a7bc9f8a4de h1:WuckfUoaRGJfaQTPZvlmcaQwg4Xj9oS2cvvh3dUqpDo= -golang.org/x/mobile v0.0.0-20250305212854-3a7bc9f8a4de/go.mod h1:/IZuixag1ELW37+FftdmIt59/3esqpAWM/QqWtf7HUI= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 h1:8MGTx39304caZ/OMsjPfuxUoDGI2tRas92F5x97tIYc= +golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7/go.mod h1:ftACcHgQ7vaOnQbHOHvXt9Y6bEPHrs5Ovk67ClwrPJA= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -123,14 +123,14 @@ golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0 h1:0K7wTWyzxZ7J+L47+LbFogJW1nn/gnnMCN0vGXNYtTI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= @@ -145,7 +145,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20250403230555-2b1f43f26fbb h1:rOQHoZqzW4aOUPdXb3HpJmJkEUYqASpXKy4W3sUQfYE= -gvisor.dev/gvisor v0.0.0-20250403230555-2b1f43f26fbb/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= +gvisor.dev/gvisor v0.0.0-20250416204613-04a61c0f3bd5 h1:3Ika0QAhViYnrNQ5xnKhrzs1MePXMcyTURu4IZfbK98= +gvisor.dev/gvisor v0.0.0-20250416204613-04a61c0f3bd5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= diff --git a/libv2ray_main.go b/libv2ray_main.go index 99de802e..fa98db58 100644 --- a/libv2ray_main.go +++ b/libv2ray_main.go @@ -26,64 +26,92 @@ import ( mobasset "golang.org/x/mobile/asset" ) +// Constants for environment variables const ( - coreAsset = "xray.location.asset" - coreCert = "xray.location.cert" - xudpBaseKey = "xray.xudp.basekey" + coreAsset = "xray.location.asset" // Path to assets directory + coreCert = "xray.location.cert" // Path to certificates + xudpBaseKey = "xray.xudp.basekey" // XUDP encryption key ) // CoreController represents a controller for managing Xray core instance lifecycle type CoreController struct { - CallbackHandler CoreCallbackHandler - statsManager corestats.Manager - coreMutex sync.Mutex - coreInstance *core.Instance - IsRunning bool + CallbackHandler CoreCallbackHandler // System callback handler + statsManager corestats.Manager // Traffic statistics + coreMutex sync.Mutex // Mutex for thread safety + coreInstance *core.Instance // Xray core instance + IsRunning bool // Service status flag } // CoreCallbackHandler defines interface for receiving callbacks and notifications from the core service type CoreCallbackHandler interface { - Startup() int - Shutdown() int - OnEmitStatus(int, string) int + Startup() int // Triggered on core start + Shutdown() int // Triggered on core shutdown + OnEmitStatus(int, string) int // Status reporting } // consoleLogWriter implements a log writer without datetime stamps // as Android system already adds timestamps to each log line type consoleLogWriter struct { - logger *log.Logger + logger *log.Logger // Standard logger } // InitCoreEnv initializes environment variables and file system handlers for the core // It sets up asset path, certificate path, XUDP base key and customizes the file reader // to support Android asset system func InitCoreEnv(envPath string, key string) { + // Set asset/cert paths if len(envPath) > 0 { - os.Setenv(coreAsset, envPath) - os.Setenv(coreCert, envPath) + if err := os.Setenv(coreAsset, envPath); err != nil { + log.Printf("failed to set %s: %v", coreAsset, err) + } + if err := os.Setenv(coreCert, envPath); err != nil { + log.Printf("failed to set %s: %v", coreCert, err) + } } - if len(key) > 0 { - os.Setenv(xudpBaseKey, key) + // Set XUDP encryption key + if len(key) > 0 { + if err := os.Setenv(xudpBaseKey, key); err != nil { + log.Printf("failed to set %s: %v", xudpBaseKey, err) + } } + // Custom file reader with path validation corefilesystem.NewFileReader = func(path string) (io.ReadCloser, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - _, file := filepath.Split(path) - return mobasset.Open(file) + // G304 Fix - Path sanitization + baseDir := envPath + cleanPath := filepath.Clean(path) + fullPath := filepath.Join(baseDir, cleanPath) + + // Prevent directory traversal + if baseDir != "" && !strings.HasPrefix(fullPath, baseDir) { + return nil, fmt.Errorf("unauthorized path: %s", path) + } + + // Check file existence + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + _, file := filepath.Split(fullPath) + return mobasset.Open(file) // Fallback to assets + } else if err != nil { + return nil, fmt.Errorf("file access error: %w", err) } - return os.Open(path) + + return os.Open(fullPath) // #nosec G304 - Validated path } } // NewCoreController initializes and returns a new CoreController instance // Sets up the console log handler and associates it with the provided callback handler func NewCoreController(s CoreCallbackHandler) *CoreController { - coreapplog.RegisterHandlerCreator(coreapplog.LogType_Console, - func(lt coreapplog.LogType, - options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) { + // Register custom logger + if err := coreapplog.RegisterHandlerCreator( + coreapplog.LogType_Console, + func(lt coreapplog.LogType, options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) { return corecommlog.NewLogger(createStdoutLogWriter()), nil - }) + }, + ); err != nil { + log.Printf("logger registration failed: %v", err) + } return &CoreController{ CallbackHandler: s, @@ -98,12 +126,11 @@ func (x *CoreController) StartLoop(configContent string) (err error) { defer x.coreMutex.Unlock() if x.IsRunning { - log.Println("The instance is already running") + log.Println("core already running") return nil } - err = x.doStartLoop(configContent) - return + return x.doStartLoop(configContent) } // StopLoop safely stops the core processing loop and releases resources @@ -114,8 +141,7 @@ func (x *CoreController) StopLoop() error { if x.IsRunning { x.doShutdown() - log.Println("Shut down the running instance") - x.CallbackHandler.OnEmitStatus(0, "Closed") + x.CallbackHandler.OnEmitStatus(0, "core stopped") } return nil } @@ -140,7 +166,7 @@ func (x *CoreController) QueryStats(tag string, direct string) int64 { func (x *CoreController) MeasureDelay(url string) (int64, error) { ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) defer cancel() - + return measureInstDelay(ctx, x.coreInstance, url) } @@ -148,13 +174,16 @@ func (x *CoreController) MeasureDelay(url string) (int64, error) { func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) { config, err := coreserial.LoadJSONConfig(strings.NewReader(ConfigureFileContent)) if err != nil { - return -1, fmt.Errorf("failed to load JSON config: %w", err) + return -1, fmt.Errorf("config load error: %w", err) } + // Simplify config for testing config.Inbound = nil var essentialApp []*serial.TypedMessage for _, app := range config.App { - if app.Type == "xray.app.proxyman.OutboundConfig" || app.Type == "xray.app.dispatcher.Config" || app.Type == "xray.app.log.Config" { + if app.Type == "xray.app.proxyman.OutboundConfig" || + app.Type == "xray.app.dispatcher.Config" || + app.Type == "xray.app.log.Config" { essentialApp = append(essentialApp, app) } } @@ -162,10 +191,12 @@ func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error inst, err := core.New(config) if err != nil { - return -1, fmt.Errorf("failed to create core instance: %w", err) + return -1, fmt.Errorf("instance creation failed: %w", err) } - inst.Start() + if err := inst.Start(); err != nil { + return -1, fmt.Errorf("startup failed: %w", err) + } defer inst.Close() return measureInstDelay(context.Background(), inst, url) } @@ -179,8 +210,10 @@ func CheckVersionX() string { // doShutdown shuts down the Xray instance and cleans up resources func (x *CoreController) doShutdown() { if x.coreInstance != nil { - x.coreInstance.Close() - x.coreInstance = nil + if err := x.coreInstance.Close(); err != nil { + log.Printf("core shutdown error: %v", err) + } + x.coreInstance = nil } x.IsRunning = false x.statsManager = nil @@ -188,37 +221,34 @@ func (x *CoreController) doShutdown() { // doStartLoop sets up and starts the Xray core func (x *CoreController) doStartLoop(configContent string) error { - log.Println("Loading core config") + log.Println("initializing core...") config, err := coreserial.LoadJSONConfig(strings.NewReader(configContent)) if err != nil { - return fmt.Errorf("failed to load core config: %w", err) + return fmt.Errorf("config error: %w", err) } - log.Println("Creating new core instance") x.coreInstance, err = core.New(config) if err != nil { - return fmt.Errorf("failed to create core instance: %w", err) + return fmt.Errorf("core init failed: %w", err) } x.statsManager = x.coreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager) - log.Println("Starting core") + log.Println("starting core...") x.IsRunning = true if err := x.coreInstance.Start(); err != nil { x.IsRunning = false - return fmt.Errorf("failed to start core: %w", err) + return fmt.Errorf("startup failed: %w", err) } x.CallbackHandler.Startup() - x.CallbackHandler.OnEmitStatus(0, "Started successfully, running") - - log.Println("Starting core successfully") + x.CallbackHandler.OnEmitStatus(0, "core started") return nil } // measureInstDelay measures the delay for an instance to a given URL func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int64, error) { if inst == nil { - return -1, errors.New("core instance is nil") + return -1, errors.New("nil instance") } tr := &http.Transport{ @@ -238,9 +268,10 @@ func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int Timeout: 12 * time.Second, } - if len(url) == 0 { + if url == "" { url = "https://www.google.com/generate_204" } + req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) start := time.Now() resp, err := client.Do(req) @@ -250,11 +281,12 @@ func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { - return -1, fmt.Errorf("unexpected status code: %s", resp.Status) + return -1, fmt.Errorf("invalid status: %s", resp.Status) } return time.Since(start).Milliseconds(), nil } +// Log writer implementation func (w *consoleLogWriter) Write(s string) error { w.logger.Print(s) return nil