diff --git a/.github/build/friendly-filenames.json b/.github/build/friendly-filenames.json index 65c20300e1c6..f7a3e52620f3 100644 --- a/.github/build/friendly-filenames.json +++ b/.github/build/friendly-filenames.json @@ -22,6 +22,7 @@ "linux-ppc64le": { "friendlyName": "linux-ppc64le" }, "linux-ppc64": { "friendlyName": "linux-ppc64" }, "linux-riscv64": { "friendlyName": "linux-riscv64" }, + "linux-loong64": { "friendlyName": "linux-loong64" }, "linux-s390x": { "friendlyName": "linux-s390x" }, "openbsd-386": { "friendlyName": "openbsd-32" }, "openbsd-amd64": { "friendlyName": "openbsd-64" }, @@ -31,4 +32,4 @@ "windows-amd64": { "friendlyName": "windows-64" }, "windows-arm64": { "friendlyName": "windows-arm64-v8a" }, "windows-arm7": { "friendlyName": "windows-arm32-v7a" } -} \ No newline at end of file +} diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile new file mode 100644 index 000000000000..ad1e8c3dc214 --- /dev/null +++ b/.github/docker/Dockerfile @@ -0,0 +1,22 @@ +# syntax=docker/dockerfile:1 +FROM --platform=$BUILDPLATFORM golang:alpine AS build +WORKDIR /src +COPY . . +ARG TARGETOS TARGETARCH +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main + +FROM --platform=${TARGETPLATFORM} alpine:latest +WORKDIR /root +COPY .github/docker/files/config.json /etc/xray/config.json +COPY --from=build /src/xray /usr/bin/xray +RUN set -ex \ + && apk add --no-cache tzdata ca-certificates \ + && mkdir -p /var/log/xray /usr/share/xray \ + && chmod +x /usr/bin/xray \ + && wget -O /usr/share/xray/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat \ + && wget -O /usr/share/xray/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat + +VOLUME /etc/xray +ENV TZ=Asia/Shanghai +ENTRYPOINT [ "/usr/bin/xray" ] +CMD [ "-config", "/etc/xray/config.json" ] diff --git a/.github/docker/files/config.json b/.github/docker/files/config.json new file mode 100644 index 000000000000..10675856bb55 --- /dev/null +++ b/.github/docker/files/config.json @@ -0,0 +1,18 @@ +{ + "inbounds": [{ + "port": 9000, + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "1eb6e917-774b-4a84-aff6-b058577c60a5", + "level": 1 + } + ] + } + }], + "outbounds": [{ + "protocol": "freedom", + "settings": {} + }] +} \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000000..b839d9863a33 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,45 @@ +name: Build docker image + +on: + push: + branches: + - main + +jobs: + build-image: + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - uses: actions/checkout@v4 + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/xray-core + flavor: latest=true + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - # Add support for more platforms with QEMU (optional) + # https://github.com/docker/setup-qemu-action + name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + file: .github/docker/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71fb20009da1..30b96142df2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,51 @@ on: - "go.sum" - ".github/workflows/*.yml" jobs: + prepare: + runs-on: ubuntu-latest + steps: + - name: Restore Cache + uses: actions/cache/restore@v3 + with: + path: resources + key: xray-geodat- + + - name: Update Geodat + id: update + uses: nick-fields/retry@v2 + with: + timeout_minutes: 60 + retry_wait_seconds: 60 + max_attempts: 60 + command: | + [ -d 'resources' ] || mkdir resources + LIST=('geoip geoip geoip' 'domain-list-community dlc geosite') + for i in "${LIST[@]}" + do + INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}')) + FILE_NAME="${INFO[2]}.dat" + echo -e "Verifying HASH key..." + HASH="$(curl -sL "https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')" + if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then + continue + else + echo -e "Downloading https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat..." + curl -L "https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat" -o ./resources/${FILE_NAME} + echo -e "Verifying HASH key..." + [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; } + echo "unhit=true" >> $GITHUB_OUTPUT + fi + done + + - name: Save Cache + uses: actions/cache/save@v3 + if: ${{ steps.update.outputs.unhit }} + with: + path: resources + key: xray-geodat-${{ github.sha }}-${{ github.run_number }} + build: + needs: prepare permissions: contents: write strategy: @@ -61,12 +105,14 @@ jobs: goarch: arm goarm: 7 # BEGIN Other architectures - # BEGIN riscv64 & ARM64 + # BEGIN riscv64 & ARM64 & LOONG64 - goos: linux goarch: arm64 - goos: linux goarch: riscv64 - # END riscv64 & ARM64 + - goos: linux + goarch: loong64 + # END riscv64 & ARM64 & LOONG64 # BEGIN MIPS - goos: linux goarch: mips64 @@ -112,7 +158,7 @@ jobs: CGO_ENABLED: 0 steps: - name: Checkout codebase - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Show workflow information run: | @@ -121,9 +167,9 @@ jobs: echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.19 + go-version: '1.21' check-latest: true - name: Get project dependencies @@ -160,27 +206,17 @@ jobs: cd ./build_assets || exit 1 mv xray xray.exe - - name: Prepare to release - uses: nick-fields/retry@v2 + - name: Restore Cache + uses: actions/cache/restore@v3 with: - timeout_minutes: 60 - retry_wait_seconds: 60 - max_attempts: 60 - command: | - cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md - cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE - LIST=('geoip geoip geoip' 'domain-list-community dlc geosite') - for i in "${LIST[@]}" - do - INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}')) - LASTEST_TAG="$(curl -sL "https://api.github.com/repos/v2fly/${INFO[0]}/releases/latest" | jq -r ".tag_name" || echo "latest")" - FILE_NAME="${INFO[2]}.dat" - echo -e "Downloading https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat..." - curl -L "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME} - echo -e "Verifying HASH key..." - HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')" - [ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; } - done + path: resources + key: xray-geodat- + + - name: Copy README.md & LICENSE + run: | + mv -f resources/* build_assets + cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md + cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE - name: Create ZIP archive shell: bash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a04c3fc04ce..0ab32cd4757d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,24 +28,17 @@ jobs: os: [windows-latest, ubuntu-latest, macos-latest] steps: - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.19 + go-version: '1.21' check-latest: true - name: Checkout codebase - uses: actions/checkout@v3 - - - name: Prepare geo*dat - if: ${{ matrix.os != 'windows-latest' }} - run: | - mkdir resources - wget -O ./resources/geoip.dat https://github.com/v2fly/geoip/releases/latest/download/geoip.dat - wget -O ./resources/geosite.dat https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat - - name: Prepare geo*dat for Windows - if: ${{ matrix.os == 'windows-latest' }} - run: | - mkdir resources - Invoke-WebRequest -Uri "https://github.com/v2fly/geoip/releases/latest/download/geoip.dat" -OutFile "./resources/geoip.dat" - Invoke-WebRequest -Uri "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat" -OutFile "./resources/geosite.dat" + uses: actions/checkout@v4 + - name: Restore Cache + uses: actions/cache/restore@v3 + with: + path: resources + key: xray-geodat- + enableCrossOsArchive: true - name: Test run: go test -timeout 1h -v ./... diff --git a/README.md b/README.md index 54a73d6ff63b..5011bcc88e34 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,124 @@ # Project X -[Project X](https://github.com/XTLS) originates from XTLS protocol, provides a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core). +[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY). + +[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls). ## License [Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE) +## Documentation + +[Project X Official Website](https://xtls.github.io) + +## Telegram + +[Project X](https://t.me/projectXray) + +[Project X Channel](https://t.me/projectXtls) + ## Installation - Linux Script - - [Xray-install](https://github.com/XTLS/Xray-install) - - [Xray-script](https://github.com/kirin10000/Xray-script) + - [XTLS/Xray-install](https://github.com/XTLS/Xray-install) - Docker + - [iamybj/docker-xray](https://hub.docker.com/r/iamybj/docker-xray) - [teddysun/xray](https://hub.docker.com/r/teddysun/xray) +- Web Panel + - [X-UI](https://github.com/FranzKafkaYu/x-ui), [X-UI-English](https://github.com/NidukaAkalanka/x-ui-english), [3X-UI](https://github.com/MHSanaei/3x-ui), [X-UI](https://github.com/alireza0/x-ui), [X-UI](https://github.com/diditra/x-ui) + - [Xray-UI](https://github.com/qist/xray-ui), [X-UI](https://github.com/sing-web/x-ui) + - [Hiddify](https://github.com/hiddify/hiddify-config) + - [Marzban](https://github.com/Gozargah/Marzban) + - [Libertea](https://github.com/VZiChoushaDui/Libertea) - One Click - - [ProxySU](https://github.com/proxysu/ProxySU) - - [v2ray-agent](https://github.com/mack-a/v2ray-agent) - - [Xray-yes](https://github.com/jiuqi9997/Xray-yes) - - [Xray_onekey](https://github.com/wulabing/Xray_onekey) + - [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz) + - [Xray-script](https://github.com/kirin10000/Xray-script), [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool) + - [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU) - Magisk - - [Xray4Magisk](https://github.com/CerteKim/Xray4Magisk) + - [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk) - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk) - Homebrew - `brew install xray` - - [(Tap) Repository 0](https://github.com/N4FA/homebrew-xray) - - [(Tap) Repository 1](https://github.com/xiruizhao/homebrew-xray) - -## Contributing -[Code Of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md) ## Usage -[Xray-examples](https://github.com/XTLS/Xray-examples) / [VLESS-TCP-XTLS-WHATEVER](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-WHATEVER) +- Example + - [VLESS-XTLS-uTLS-REALITY](https://github.com/XTLS/REALITY#readme) + - [VLESS-TCP-XTLS-Vision](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision) + - [All-in-One-fallbacks-Nginx](https://github.com/XTLS/Xray-examples/tree/main/All-in-One-fallbacks-Nginx) +- Xray-examples + - [XTLS/Xray-examples](https://github.com/XTLS/Xray-examples) + - [chika0801/Xray-examples](https://github.com/chika0801/Xray-examples) + - [lxhao61/integrated-examples](https://github.com/lxhao61/integrated-examples) +- Tutorial + - [XTLS Vision](https://github.com/chika0801/Xray-install) + - [REALITY (English)](https://cscot.pages.dev/2023/03/02/Xray-REALITY-tutorial/) + - [XTLS-Iran-Reality (English)](https://github.com/SasukeFreestyle/XTLS-Iran-Reality) + - [Xray REALITY with 'steal oneself' (English)](https://computerscot.github.io/vless-xtls-utls-reality-steal-oneself.html) ## GUI Clients - OpenWrt - - [PassWall](https://github.com/xiaorouji/openwrt-passwall) - - [Hello World](https://github.com/jerrykuku/luci-app-vssr) + - [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2) - [ShadowSocksR Plus+](https://github.com/fw876/helloworld) - [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray)) - Windows - [v2rayN](https://github.com/2dust/v2rayN) - - [Qv2ray](https://github.com/Qv2ray/Qv2ray) (This project had been archived and currently inactive) - - [Netch (NetFilter & TUN/TAP)](https://github.com/NetchX/Netch) (This project had been archived and currently inactive) + - [NekoRay](https://github.com/Matsuridayo/nekoray) + - [Furious](https://github.com/LorenEteval/Furious) + - [HiddifyN](https://github.com/hiddify/HiddifyN) + - [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient) - Android - [v2rayNG](https://github.com/2dust/v2rayNG) - - [Kitsunebi](https://github.com/rurirei/Kitsunebi/tree/release_xtls) -- iOS & macOS (with M1 chip) - - [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118) - - [Stash](https://apps.apple.com/app/stash/id1596063349) -- macOS (Intel chip & M1 chip) - - [Qv2ray](https://github.com/Qv2ray/Qv2ray) (This project had been archived and currently inactive) + - [HiddifyNG](https://github.com/hiddify/HiddifyNG) + - [X-flutter](https://github.com/XTLS/X-flutter) +- iOS & macOS arm64 + - [Mango](https://github.com/arror/Mango) + - [FoXray](https://apps.apple.com/app/foxray/id6448898396) + - [Streisand](https://apps.apple.com/app/streisand/id6450534064) +- macOS arm64 & x64 + - [V2rayU](https://github.com/yanue/V2rayU) - [V2RayXS](https://github.com/tzmax/V2RayXS) + - [Furious](https://github.com/LorenEteval/Furious) + - [FoXray](https://apps.apple.com/app/foxray/id6448898396) +- Linux + - [v2rayA](https://github.com/v2rayA/v2rayA) + - [NekoRay](https://github.com/Matsuridayo/nekoray) + - [Furious](https://github.com/LorenEteval/Furious) + +## Others that support VLESS, XTLS, REALITY, XUDP, PLUX... + +- iOS & macOS arm64 + - [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118) +- Xray Wrapper + - [XTLS/libXray](https://github.com/XTLS/libXray) + - [xtlsapi](https://github.com/hiddify/xtlsapi) + - [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite) + - [XrayKit](https://github.com/arror/XrayKit) + - [Xray-core-python](https://github.com/LorenEteval/Xray-core-python) +- [XrayR](https://github.com/XrayR-project/XrayR) + - [XrayR-release](https://github.com/XrayR-project/XrayR-release) + - [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board) +- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) + - [Clash Verge](https://github.com/zzzgydi/clash-verge) + - [clashN](https://github.com/2dust/clashN) + - [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid) + - [meta_for_ios](https://t.me/meta_for_ios) +- [sing-box](https://github.com/SagerNet/sing-box) + - [installReality](https://github.com/BoxXt/installReality) + - [sbox-reality](https://github.com/Misaka-blog/sbox-reality) + - [sing-box-for-ios](https://github.com/SagerNet/sing-box-for-ios) + +## Contributing + +[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md) ## Credits -This repo relies on the following third-party projects: - -- Special thanks: - - [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core) -- In production: - - [ghodss/yaml](https://github.com/ghodss/yaml) - - [gorilla/websocket](https://github.com/gorilla/websocket) - - [lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go) - - [pelletier/go-toml](https://github.com/pelletier/go-toml) - - [pires/go-proxyproto](https://github.com/pires/go-proxyproto) - - [refraction-networking/utls](https://github.com/refraction-networking/utls) - - [seiflotfy/cuckoofilter](https://github.com/seiflotfy/cuckoofilter) - - [google/starlark-go](https://github.com/google/starlark-go) -- For testing only: - - [miekg/dns](https://github.com/miekg/dns) - - [stretchr/testify](https://github.com/stretchr/testify) - - [h12w/socks](https://github.com/h12w/socks) +- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases). +- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod). ## Compilation @@ -89,12 +134,6 @@ go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main ``` -## Telegram - -[Project X](https://t.me/projectXray) - -[Project X Channel](https://t.me/projectXtls) - ## Stargazers over time [![Stargazers over time](https://starchart.cc/XTLS/Xray-core.svg)](https://starchart.cc/XTLS/Xray-core) diff --git a/app/commander/config.pb.go b/app/commander/config.pb.go index 7777de4188c2..73037653d44a 100644 --- a/app/commander/config.pb.go +++ b/app/commander/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/commander/config.proto package commander diff --git a/app/dispatcher/config.pb.go b/app/dispatcher/config.pb.go index a4777b9db339..e0a55ab279cd 100644 --- a/app/dispatcher/config.pb.go +++ b/app/dispatcher/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/dispatcher/config.proto package dispatcher diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index 6f30d91c0a3a..265763c3322d 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -4,7 +4,6 @@ package dispatcher import ( "context" - "fmt" "strings" "sync" "time" @@ -141,77 +140,10 @@ func (*DefaultDispatcher) Start() error { // Close implements common.Closable. func (*DefaultDispatcher) Close() error { return nil } -func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link) { - downOpt := pipe.OptionsFromContext(ctx) - upOpt := downOpt - - if network == net.Network_UDP { - var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns - // Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs. - // When target replies, server will restore the domain and send back to client. - // Note: this map is not global but per connection context - upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer { - for i, buffer := range mb { - if buffer.UDP == nil { - continue - } - addr := buffer.UDP.Address - if addr.Family().IsIP() { - if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled { - domain := fkr0.GetDomainFromFakeDNS(addr) - if len(domain) > 0 { - buffer.UDP.Address = net.DomainAddress(domain) - newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) - } else { - newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx)) - } - } - } else { - if ip2domain == nil { - ip2domain = new(sync.Map) - newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx)) - } - domain := addr.Domain() - ips, err := d.dns.LookupIP(domain, dns.IPOption{true, true, false}) - if err == nil { - for _, ip := range ips { - ip2domain.Store(ip.String(), domain) - } - newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) - } else { - newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - } - } - return mb - })) - downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer { - for i, buffer := range mb { - if buffer.UDP == nil { - continue - } - addr := buffer.UDP.Address - if addr.Family().IsIP() { - if ip2domain == nil { - continue - } - if domain, found := ip2domain.Load(addr.IP().String()); found { - buffer.UDP.Address = net.DomainAddress(domain.(string)) - newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) - } - } else { - if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok { - fakeIp := fkr0.GetFakeIPForDomain(addr.Domain()) - buffer.UDP.Address = fakeIp[0] - newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) - } - } - } - return mb - })) - } - uplinkReader, uplinkWriter := pipe.New(upOpt...) - downlinkReader, downlinkWriter := pipe.New(downOpt...) +func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link) { + opt := pipe.OptionsFromContext(ctx) + uplinkReader, uplinkWriter := pipe.New(opt...) + downlinkReader, downlinkWriter := pipe.New(opt...) inboundLink := &transport.Link{ Reader: downlinkReader, @@ -333,7 +265,7 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu protocolString = resComp.ProtocolForDomainResult() } for _, p := range request.OverrideDestinationForProtocol { - if strings.HasPrefix(protocolString, p) { + if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) { return true } if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" && @@ -356,18 +288,20 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin if !destination.IsValid() { panic("Dispatcher: Invalid destination.") } - ob := &session.Outbound{ - Target: destination, + ob := session.OutboundFromContext(ctx) + if ob == nil { + ob = &session.Outbound{} + ctx = session.ContextWithOutbound(ctx, ob) } - ctx = session.ContextWithOutbound(ctx, ob) + ob.OriginalTarget = destination + ob.Target = destination content := session.ContentFromContext(ctx) if content == nil { content = new(session.Content) ctx = session.ContextWithContent(ctx, content) } - sniffingRequest := content.SniffingRequest - inbound, outbound := d.getLink(ctx, destination.Network, sniffingRequest) + inbound, outbound := d.getLink(ctx) if !sniffingRequest.Enabled { go d.routedDispatch(ctx, outbound, destination) } else { @@ -384,7 +318,15 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin domain := result.Domain() newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) destination.Address = net.ParseAddress(domain) - if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" { + protocol := result.Protocol() + if resComp, ok := result.(SnifferResultComposite); ok { + protocol = resComp.ProtocolForDomainResult() + } + isFakeIP := false + if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && ob.Target.Address.Family().IsIP() && fkr0.IsIPInIPPool(ob.Target.Address) { + isFakeIP = true + } + if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP { ob.RouteTarget = destination } else { ob.Target = destination @@ -401,10 +343,13 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De if !destination.IsValid() { return newError("Dispatcher: Invalid destination.") } - ob := &session.Outbound{ - Target: destination, + ob := session.OutboundFromContext(ctx) + if ob == nil { + ob = &session.Outbound{} + ctx = session.ContextWithOutbound(ctx, ob) } - ctx = session.ContextWithOutbound(ctx, ob) + ob.OriginalTarget = destination + ob.Target = destination content := session.ContentFromContext(ctx) if content == nil { content = new(session.Content) @@ -412,29 +357,35 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De } sniffingRequest := content.SniffingRequest if !sniffingRequest.Enabled { - go d.routedDispatch(ctx, outbound, destination) + d.routedDispatch(ctx, outbound, destination) } else { - go func() { - cReader := &cachedReader{ - reader: outbound.Reader.(*pipe.Reader), + cReader := &cachedReader{ + reader: outbound.Reader.(*pipe.Reader), + } + outbound.Reader = cReader + result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network) + if err == nil { + content.Protocol = result.Protocol() + } + if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { + domain := result.Domain() + newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) + destination.Address = net.ParseAddress(domain) + protocol := result.Protocol() + if resComp, ok := result.(SnifferResultComposite); ok { + protocol = resComp.ProtocolForDomainResult() } - outbound.Reader = cReader - result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network) - if err == nil { - content.Protocol = result.Protocol() + isFakeIP := false + if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && ob.Target.Address.Family().IsIP() && fkr0.IsIPInIPPool(ob.Target.Address) { + isFakeIP = true } - if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { - domain := result.Domain() - newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) - destination.Address = net.ParseAddress(domain) - if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" { - ob.RouteTarget = destination - } else { - ob.Target = destination - } + if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP { + ob.RouteTarget = destination + } else { + ob.Target = destination } - d.routedDispatch(ctx, outbound, destination) - }() + } + d.routedDispatch(ctx, outbound, destination) } return nil diff --git a/app/dns/config.pb.go b/app/dns/config.pb.go index 3d7f537e1753..d5bc18d26b14 100644 --- a/app/dns/config.pb.go +++ b/app/dns/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/dns/config.proto package dns @@ -219,14 +219,14 @@ type Config struct { // the moment. A special value 'localhost' as a domain address can be set to // use DNS on local system. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/dns/config.proto. NameServers []*net.Endpoint `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"` // NameServer list used by this DNS client. NameServer []*NameServer `protobuf:"bytes,5,rep,name=name_server,json=nameServer,proto3" json:"name_server,omitempty"` // Static hosts. Domain to IP. // Deprecated. Use static_hosts. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/dns/config.proto. Hosts map[string]*net.IPOrDomain `protobuf:"bytes,2,rep,name=Hosts,proto3" json:"Hosts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes // (IPv6). @@ -273,7 +273,7 @@ func (*Config) Descriptor() ([]byte, []int) { return file_app_dns_config_proto_rawDescGZIP(), []int{1} } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/dns/config.proto. func (x *Config) GetNameServers() []*net.Endpoint { if x != nil { return x.NameServers @@ -288,7 +288,7 @@ func (x *Config) GetNameServer() []*NameServer { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/dns/config.proto. func (x *Config) GetHosts() map[string]*net.IPOrDomain { if x != nil { return x.Hosts diff --git a/app/dns/dns.go b/app/dns/dns.go index af5f285b88ff..6efcb825a931 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -215,7 +215,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) { newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog() errs = append(errs, err) } - if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch { + if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse { return nil, err } } diff --git a/app/dns/dns_test.go b/app/dns/dns_test.go index b3a8def88559..74c7a125a946 100644 --- a/app/dns/dns_test.go +++ b/app/dns/dns_test.go @@ -13,6 +13,7 @@ import ( _ "github.com/xtls/xray-core/app/proxyman/outbound" "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/core" @@ -260,7 +261,7 @@ func TestUDPServer(t *testing.T) { IPv6Enable: true, FakeEnable: false, }) - if err != feature_dns.ErrEmptyResponse { + if !errors.AllEqual(feature_dns.ErrEmptyResponse, errors.Cause(err)) { t.Fatal("error: ", err) } if len(ips) != 0 { diff --git a/app/dns/dnscommon.go b/app/dns/dnscommon.go index fa3ac406789a..df1b17afc8c4 100644 --- a/app/dns/dnscommon.go +++ b/app/dns/dnscommon.go @@ -7,8 +7,8 @@ import ( "time" "github.com/xtls/xray-core/common" - "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/core" diff --git a/app/dns/fakedns/fakedns.pb.go b/app/dns/fakedns/fakedns.pb.go index 13f7bb35160b..dc9970f9e930 100644 --- a/app/dns/fakedns/fakedns.pb.go +++ b/app/dns/fakedns/fakedns.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/dns/fakedns/fakedns.proto package fakedns diff --git a/app/dns/nameserver_quic.go b/app/dns/nameserver_quic.go index e1c005ead43e..e82a6a3de0d0 100644 --- a/app/dns/nameserver_quic.go +++ b/app/dns/nameserver_quic.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - "github.com/lucas-clemente/quic-go" + "github.com/quic-go/quic-go" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/log" @@ -373,8 +373,8 @@ func (s *QUICNameServer) openConnection() (quic.Connection, error) { quicConfig := &quic.Config{ HandshakeIdleTimeout: handshakeTimeout, } - - conn, err := quic.DialAddrContext(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig) + tlsConfig.ServerName = s.destination.Address.String() + conn, err := quic.DialAddr(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig) log.Record(&log.AccessMessage{ From: "DNS", To: s.destination, diff --git a/app/log/command/config.pb.go b/app/log/command/config.pb.go index b8b5635014c7..6a3d66028306 100644 --- a/app/log/command/config.pb.go +++ b/app/log/command/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/log/command/config.proto package command diff --git a/app/log/command/config_grpc.pb.go b/app/log/command/config_grpc.pb.go index 7908855b1804..93a695b0a3d1 100644 --- a/app/log/command/config_grpc.pb.go +++ b/app/log/command/config_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.18.0 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.1 // source: app/log/command/config.proto package command @@ -18,6 +18,10 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + LoggerService_RestartLogger_FullMethodName = "/xray.app.log.command.LoggerService/RestartLogger" +) + // LoggerServiceClient is the client API for LoggerService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -35,7 +39,7 @@ func NewLoggerServiceClient(cc grpc.ClientConnInterface) LoggerServiceClient { func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) { out := new(RestartLoggerResponse) - err := c.cc.Invoke(ctx, "/xray.app.log.command.LoggerService/RestartLogger", in, out, opts...) + err := c.cc.Invoke(ctx, LoggerService_RestartLogger_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -80,7 +84,7 @@ func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.log.command.LoggerService/RestartLogger", + FullMethod: LoggerService_RestartLogger_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LoggerServiceServer).RestartLogger(ctx, req.(*RestartLoggerRequest)) diff --git a/app/log/config.pb.go b/app/log/config.pb.go index 95b5468efb4d..0bc09d40421c 100644 --- a/app/log/config.pb.go +++ b/app/log/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/log/config.proto package log diff --git a/app/metrics/config.pb.go b/app/metrics/config.pb.go index 614f2d85fe6d..2cf6761801ba 100644 --- a/app/metrics/config.pb.go +++ b/app/metrics/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/metrics/config.proto package metrics diff --git a/app/observatory/command/command.pb.go b/app/observatory/command/command.pb.go index 743ca7c23ec9..9eab15333772 100644 --- a/app/observatory/command/command.pb.go +++ b/app/observatory/command/command.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/observatory/command/command.proto package command diff --git a/app/observatory/command/command_grpc.pb.go b/app/observatory/command/command_grpc.pb.go index 0cbd99210c17..b0a59779826e 100644 --- a/app/observatory/command/command_grpc.pb.go +++ b/app/observatory/command/command_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.18.0 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.1 // source: app/observatory/command/command.proto package command @@ -18,6 +18,10 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + ObservatoryService_GetOutboundStatus_FullMethodName = "/xray.core.app.observatory.command.ObservatoryService/GetOutboundStatus" +) + // ObservatoryServiceClient is the client API for ObservatoryService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -35,7 +39,7 @@ func NewObservatoryServiceClient(cc grpc.ClientConnInterface) ObservatoryService func (c *observatoryServiceClient) GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error) { out := new(GetOutboundStatusResponse) - err := c.cc.Invoke(ctx, "/xray.core.app.observatory.command.ObservatoryService/GetOutboundStatus", in, out, opts...) + err := c.cc.Invoke(ctx, ObservatoryService_GetOutboundStatus_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -80,7 +84,7 @@ func _ObservatoryService_GetOutboundStatus_Handler(srv interface{}, ctx context. } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.core.app.observatory.command.ObservatoryService/GetOutboundStatus", + FullMethod: ObservatoryService_GetOutboundStatus_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, req.(*GetOutboundStatusRequest)) diff --git a/app/observatory/config.pb.go b/app/observatory/config.pb.go index 2c9a5bc8e84d..741da50649ae 100644 --- a/app/observatory/config.pb.go +++ b/app/observatory/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/observatory/config.proto package observatory diff --git a/app/observatory/observer.go b/app/observatory/observer.go index 576818c457a2..9d961f66b9f8 100644 --- a/app/observatory/observer.go +++ b/app/observatory/observer.go @@ -9,7 +9,6 @@ import ( "sync" "time" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common" v2net "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" @@ -19,6 +18,7 @@ import ( "github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/transport/internet/tagged" + "google.golang.org/protobuf/proto" ) type Observer struct { diff --git a/app/policy/config.pb.go b/app/policy/config.pb.go index 45b3082cd130..9841fff02c3a 100644 --- a/app/policy/config.pb.go +++ b/app/policy/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/policy/config.proto package policy diff --git a/app/proxyman/command/command.pb.go b/app/proxyman/command/command.pb.go index 4408c93e4b7c..9add8afbfd84 100644 --- a/app/proxyman/command/command.pb.go +++ b/app/proxyman/command/command.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/proxyman/command/command.proto package command diff --git a/app/proxyman/command/command_grpc.pb.go b/app/proxyman/command/command_grpc.pb.go index 2b3344f7b563..0765fc80a157 100644 --- a/app/proxyman/command/command_grpc.pb.go +++ b/app/proxyman/command/command_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.18.0 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.1 // source: app/proxyman/command/command.proto package command @@ -18,6 +18,15 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + HandlerService_AddInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddInbound" + HandlerService_RemoveInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveInbound" + HandlerService_AlterInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterInbound" + HandlerService_AddOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddOutbound" + HandlerService_RemoveOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveOutbound" + HandlerService_AlterOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterOutbound" +) + // HandlerServiceClient is the client API for HandlerService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -40,7 +49,7 @@ func NewHandlerServiceClient(cc grpc.ClientConnInterface) HandlerServiceClient { func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) { out := new(AddInboundResponse) - err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AddInbound", in, out, opts...) + err := c.cc.Invoke(ctx, HandlerService_AddInbound_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -49,7 +58,7 @@ func (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundReq func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) { out := new(RemoveInboundResponse) - err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/RemoveInbound", in, out, opts...) + err := c.cc.Invoke(ctx, HandlerService_RemoveInbound_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -58,7 +67,7 @@ func (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInbo func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) { out := new(AlterInboundResponse) - err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AlterInbound", in, out, opts...) + err := c.cc.Invoke(ctx, HandlerService_AlterInbound_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -67,7 +76,7 @@ func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboun func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) { out := new(AddOutboundResponse) - err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AddOutbound", in, out, opts...) + err := c.cc.Invoke(ctx, HandlerService_AddOutbound_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -76,7 +85,7 @@ func (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundR func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) { out := new(RemoveOutboundResponse) - err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/RemoveOutbound", in, out, opts...) + err := c.cc.Invoke(ctx, HandlerService_RemoveOutbound_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -85,7 +94,7 @@ func (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOut func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) { out := new(AlterOutboundResponse) - err := c.cc.Invoke(ctx, "/xray.app.proxyman.command.HandlerService/AlterOutbound", in, out, opts...) + err := c.cc.Invoke(ctx, HandlerService_AlterOutbound_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -150,7 +159,7 @@ func _HandlerService_AddInbound_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.proxyman.command.HandlerService/AddInbound", + FullMethod: HandlerService_AddInbound_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AddInbound(ctx, req.(*AddInboundRequest)) @@ -168,7 +177,7 @@ func _HandlerService_RemoveInbound_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.proxyman.command.HandlerService/RemoveInbound", + FullMethod: HandlerService_RemoveInbound_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).RemoveInbound(ctx, req.(*RemoveInboundRequest)) @@ -186,7 +195,7 @@ func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.proxyman.command.HandlerService/AlterInbound", + FullMethod: HandlerService_AlterInbound_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AlterInbound(ctx, req.(*AlterInboundRequest)) @@ -204,7 +213,7 @@ func _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.proxyman.command.HandlerService/AddOutbound", + FullMethod: HandlerService_AddOutbound_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AddOutbound(ctx, req.(*AddOutboundRequest)) @@ -222,7 +231,7 @@ func _HandlerService_RemoveOutbound_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.proxyman.command.HandlerService/RemoveOutbound", + FullMethod: HandlerService_RemoveOutbound_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).RemoveOutbound(ctx, req.(*RemoveOutboundRequest)) @@ -240,7 +249,7 @@ func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.proxyman.command.HandlerService/AlterOutbound", + FullMethod: HandlerService_AlterOutbound_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HandlerServiceServer).AlterOutbound(ctx, req.(*AlterOutboundRequest)) diff --git a/app/proxyman/config.pb.go b/app/proxyman/config.pb.go index d7ff274ce430..cabc09dd7c6f 100644 --- a/app/proxyman/config.pb.go +++ b/app/proxyman/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/proxyman/config.proto package proxyman @@ -326,7 +326,7 @@ type ReceiverConfig struct { // Override domains for the given protocol. // Deprecated. Use sniffing_settings. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/proxyman/config.proto. DomainOverride []KnownProtocols `protobuf:"varint,7,rep,packed,name=domain_override,json=domainOverride,proto3,enum=xray.app.proxyman.KnownProtocols" json:"domain_override,omitempty"` SniffingSettings *SniffingConfig `protobuf:"bytes,8,opt,name=sniffing_settings,json=sniffingSettings,proto3" json:"sniffing_settings,omitempty"` } @@ -398,7 +398,7 @@ func (x *ReceiverConfig) GetReceiveOriginalDestination() bool { return false } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/proxyman/config.proto. func (x *ReceiverConfig) GetDomainOverride() []KnownProtocols { if x != nil { return x.DomainOverride @@ -594,7 +594,11 @@ type MultiplexingConfig struct { // Whether or not Mux is enabled. Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` // Max number of concurrent connections that one Mux connection can handle. - Concurrency uint32 `protobuf:"varint,2,opt,name=concurrency,proto3" json:"concurrency,omitempty"` + Concurrency int32 `protobuf:"varint,2,opt,name=concurrency,proto3" json:"concurrency,omitempty"` + // Transport XUDP in another Mux. + XudpConcurrency int32 `protobuf:"varint,3,opt,name=xudpConcurrency,proto3" json:"xudpConcurrency,omitempty"` + // "reject" (default), "allow" or "skip". + XudpProxyUDP443 string `protobuf:"bytes,4,opt,name=xudpProxyUDP443,proto3" json:"xudpProxyUDP443,omitempty"` } func (x *MultiplexingConfig) Reset() { @@ -636,13 +640,27 @@ func (x *MultiplexingConfig) GetEnabled() bool { return false } -func (x *MultiplexingConfig) GetConcurrency() uint32 { +func (x *MultiplexingConfig) GetConcurrency() int32 { if x != nil { return x.Concurrency } return 0 } +func (x *MultiplexingConfig) GetXudpConcurrency() int32 { + if x != nil { + return x.XudpConcurrency + } + return 0 +} + +func (x *MultiplexingConfig) GetXudpProxyUDP443() string { + if x != nil { + return x.XudpProxyUDP443 + } + return "" +} + type AllocationStrategy_AllocationStrategyConcurrency struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -856,21 +874,26 @@ var file_app_proxyman_config_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, - 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x50, 0x0a, 0x12, - 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, - 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2a, 0x23, - 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, - 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x4c, - 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, - 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x26, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, - 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, - 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, - 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xa4, 0x01, 0x0a, + 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, + 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, + 0x28, 0x0a, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, + 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x78, 0x75, 0x64, + 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, + 0x34, 0x34, 0x33, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, + 0x07, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, + 0x6e, 0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, + 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, + 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/proxyman/config.proto b/app/proxyman/config.proto index 24216d2ceb19..5d063ebaa0c7 100644 --- a/app/proxyman/config.proto +++ b/app/proxyman/config.proto @@ -97,5 +97,9 @@ message MultiplexingConfig { // Whether or not Mux is enabled. bool enabled = 1; // Max number of concurrent connections that one Mux connection can handle. - uint32 concurrency = 2; + int32 concurrency = 2; + // Transport XUDP in another Mux. + int32 xudpConcurrency = 3; + // "reject" (default), "allow" or "skip". + string xudpProxyUDP443 = 4; } diff --git a/app/proxyman/inbound/worker.go b/app/proxyman/inbound/worker.go index 5b227c068439..1fe866552908 100644 --- a/app/proxyman/inbound/worker.go +++ b/app/proxyman/inbound/worker.go @@ -60,6 +60,7 @@ func (w *tcpWorker) callback(conn stat.Connection) { sid := session.NewID() ctx = session.ContextWithID(ctx, sid) + var outbound = &session.Outbound{} if w.recvOrigDest { var dest net.Destination switch getTProxyType(w.stream) { @@ -74,11 +75,10 @@ func (w *tcpWorker) callback(conn stat.Connection) { dest = net.DestinationFromAddr(conn.LocalAddr()) } if dest.IsValid() { - ctx = session.ContextWithOutbound(ctx, &session.Outbound{ - Target: dest, - }) + outbound.Target = dest } } + ctx = session.ContextWithOutbound(ctx, outbound) if w.uplinkCounter != nil || w.downlinkCounter != nil { conn = &stat.CounterConnection{ @@ -362,7 +362,7 @@ func (w *udpWorker) clean() error { } for addr, conn := range w.activeConn { - if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 5*60 { // TODO Timeout too small + if nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 2*60 { if !conn.inactive { conn.setInactive() delete(w.activeConn, addr) diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index 42554b72aaf6..d290b016d33c 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -8,6 +8,7 @@ import ( "github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" @@ -57,6 +58,8 @@ type Handler struct { proxy proxy.Outbound outboundManager outbound.Manager mux *mux.ClientManager + xudp *mux.ClientManager + udp443 string uplinkCounter stats.Counter downlinkCounter stats.Counter } @@ -106,22 +109,50 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou } if h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil { - config := h.senderSettings.MultiplexSettings - if config.Concurrency < 1 || config.Concurrency > 1024 { - return nil, newError("invalid mux concurrency: ", config.Concurrency).AtWarning() - } - h.mux = &mux.ClientManager{ - Enabled: h.senderSettings.MultiplexSettings.Enabled, - Picker: &mux.IncrementalWorkerPicker{ - Factory: &mux.DialingWorkerFactory{ - Proxy: proxyHandler, - Dialer: h, - Strategy: mux.ClientStrategy{ - MaxConcurrency: config.Concurrency, - MaxConnection: 128, + if config := h.senderSettings.MultiplexSettings; config.Enabled { + if config.Concurrency < 0 { + h.mux = &mux.ClientManager{Enabled: false} + } + if config.Concurrency == 0 { + config.Concurrency = 8 // same as before + } + if config.Concurrency > 0 { + h.mux = &mux.ClientManager{ + Enabled: true, + Picker: &mux.IncrementalWorkerPicker{ + Factory: &mux.DialingWorkerFactory{ + Proxy: proxyHandler, + Dialer: h, + Strategy: mux.ClientStrategy{ + MaxConcurrency: uint32(config.Concurrency), + MaxConnection: 128, + }, + }, }, - }, - }, + } + } + if config.XudpConcurrency < 0 { + h.xudp = &mux.ClientManager{Enabled: false} + } + if config.XudpConcurrency == 0 { + h.xudp = nil // same as before + } + if config.XudpConcurrency > 0 { + h.xudp = &mux.ClientManager{ + Enabled: true, + Picker: &mux.IncrementalWorkerPicker{ + Factory: &mux.DialingWorkerFactory{ + Proxy: proxyHandler, + Dialer: h, + Strategy: mux.ClientStrategy{ + MaxConcurrency: uint32(config.XudpConcurrency), + MaxConnection: 128, + }, + }, + }, + } + } + h.udp443 = config.XudpProxyUDP443 } } @@ -136,31 +167,58 @@ func (h *Handler) Tag() string { // Dispatch implements proxy.Outbound.Dispatch. func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) { - if h.mux != nil && (h.mux.Enabled || session.MuxPreferedFromContext(ctx)) { - if err := h.mux.Dispatch(ctx, link); err != nil { - err := newError("failed to process mux outbound traffic").Base(err) - session.SubmitOutboundErrorToOriginator(ctx, err) - err.WriteToLog(session.ExportIDToError(ctx)) - common.Interrupt(link.Writer) + outbound := session.OutboundFromContext(ctx) + if outbound.Target.Network == net.Network_UDP && outbound.OriginalTarget.Address != nil && outbound.OriginalTarget.Address != outbound.Target.Address { + link.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: outbound.Target.Address, OriginalDest: outbound.OriginalTarget.Address} + link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: outbound.Target.Address, OriginalDest: outbound.OriginalTarget.Address} + } + if h.mux != nil { + test := func(err error) { + if err != nil { + err := newError("failed to process mux outbound traffic").Base(err) + session.SubmitOutboundErrorToOriginator(ctx, err) + err.WriteToLog(session.ExportIDToError(ctx)) + common.Interrupt(link.Writer) + } } - } else { - err := h.proxy.Process(ctx, link, h) - if err != nil { - if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, context.Canceled) { - err = nil + if outbound.Target.Network == net.Network_UDP && outbound.Target.Port == 443 { + switch h.udp443 { + case "reject": + test(newError("XUDP rejected UDP/443 traffic").AtInfo()) + return + case "skip": + goto out } } - if err != nil { - // Ensure outbound ray is properly closed. - err := newError("failed to process outbound traffic").Base(err) - session.SubmitOutboundErrorToOriginator(ctx, err) - err.WriteToLog(session.ExportIDToError(ctx)) - common.Interrupt(link.Writer) - } else { - common.Must(common.Close(link.Writer)) + if h.xudp != nil && outbound.Target.Network == net.Network_UDP { + if !h.xudp.Enabled { + goto out + } + test(h.xudp.Dispatch(ctx, link)) + return + } + if h.mux.Enabled { + test(h.mux.Dispatch(ctx, link)) + return + } + } +out: + err := h.proxy.Process(ctx, link, h) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, context.Canceled) { + err = nil } - common.Interrupt(link.Reader) } + if err != nil { + // Ensure outbound ray is properly closed. + err := newError("failed to process outbound traffic").Base(err) + session.SubmitOutboundErrorToOriginator(ctx, err) + err.WriteToLog(session.ExportIDToError(ctx)) + common.Interrupt(link.Writer) + } else { + common.Close(link.Writer) + } + common.Interrupt(link.Reader) } // Address implements internet.Dialer. @@ -216,7 +274,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti } conn, err := internet.Dial(ctx, dest, h.streamSettings) - return h.getStatCouterConnection(conn), err + conn = h.getStatCouterConnection(conn) + outbound := session.OutboundFromContext(ctx) + if outbound != nil { + outbound.Conn = conn + } + return conn, err } func (h *Handler) getStatCouterConnection(conn stat.Connection) stat.Connection { diff --git a/app/proxyman/outbound/uot.go b/app/proxyman/outbound/uot.go index a4af220cb79b..f3c7426e683f 100644 --- a/app/proxyman/outbound/uot.go +++ b/app/proxyman/outbound/uot.go @@ -11,13 +11,24 @@ import ( ) func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) { - if !dest.Address.Family().IsDomain() || dest.Address.Domain() != uot.UOTMagicAddress { + if dest.Address == nil { + return nil, newError("nil destination address") + } + if !dest.Address.Family().IsDomain() { + return nil, os.ErrInvalid + } + var uotVersion int + if dest.Address.Domain() == uot.MagicAddress { + uotVersion = uot.Version + } else if dest.Address.Domain() == uot.LegacyMagicAddress { + uotVersion = uot.LegacyVersion + } else { return nil, os.ErrInvalid } packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings) if err != nil { return nil, newError("unable to listen socket").Base(err) } - conn := uot.NewServerConn(packetConn) + conn := uot.NewServerConn(packetConn, uotVersion) return h.getStatCouterConnection(conn), nil } diff --git a/app/reverse/bridge.go b/app/reverse/bridge.go index 4b86c3a2919d..be0fdcd9778c 100644 --- a/app/reverse/bridge.go +++ b/app/reverse/bridge.go @@ -4,7 +4,6 @@ import ( "context" "time" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" @@ -12,6 +11,7 @@ import ( "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/pipe" + "google.golang.org/protobuf/proto" ) // Bridge is a component in reverse proxy, that relays connections from Portal to local address. diff --git a/app/reverse/config.pb.go b/app/reverse/config.pb.go index aebf9f9c9915..0e1bc941f014 100644 --- a/app/reverse/config.pb.go +++ b/app/reverse/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/reverse/config.proto package reverse diff --git a/app/reverse/portal.go b/app/reverse/portal.go index b0860a6ee2e4..fb0b693002aa 100644 --- a/app/reverse/portal.go +++ b/app/reverse/portal.go @@ -5,7 +5,6 @@ import ( "sync" "time" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/mux" @@ -15,6 +14,7 @@ import ( "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/pipe" + "google.golang.org/protobuf/proto" ) type Portal struct { diff --git a/app/router/command/command.pb.go b/app/router/command/command.pb.go index 598ea2d05034..eb7c85306985 100644 --- a/app/router/command/command.pb.go +++ b/app/router/command/command.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/router/command/command.proto package command diff --git a/app/router/command/command_grpc.pb.go b/app/router/command/command_grpc.pb.go index 3b830dfbf535..4d3b4ab4e34b 100644 --- a/app/router/command/command_grpc.pb.go +++ b/app/router/command/command_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.18.0 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.1 // source: app/router/command/command.proto package command @@ -18,6 +18,11 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + RoutingService_SubscribeRoutingStats_FullMethodName = "/xray.app.router.command.RoutingService/SubscribeRoutingStats" + RoutingService_TestRoute_FullMethodName = "/xray.app.router.command.RoutingService/TestRoute" +) + // RoutingServiceClient is the client API for RoutingService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -35,7 +40,7 @@ func NewRoutingServiceClient(cc grpc.ClientConnInterface) RoutingServiceClient { } func (c *routingServiceClient) SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (RoutingService_SubscribeRoutingStatsClient, error) { - stream, err := c.cc.NewStream(ctx, &RoutingService_ServiceDesc.Streams[0], "/xray.app.router.command.RoutingService/SubscribeRoutingStats", opts...) + stream, err := c.cc.NewStream(ctx, &RoutingService_ServiceDesc.Streams[0], RoutingService_SubscribeRoutingStats_FullMethodName, opts...) if err != nil { return nil, err } @@ -68,7 +73,7 @@ func (x *routingServiceSubscribeRoutingStatsClient) Recv() (*RoutingContext, err func (c *routingServiceClient) TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) { out := new(RoutingContext) - err := c.cc.Invoke(ctx, "/xray.app.router.command.RoutingService/TestRoute", in, out, opts...) + err := c.cc.Invoke(ctx, RoutingService_TestRoute_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -138,7 +143,7 @@ func _RoutingService_TestRoute_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.router.command.RoutingService/TestRoute", + FullMethod: RoutingService_TestRoute_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(RoutingServiceServer).TestRoute(ctx, req.(*TestRouteRequest)) diff --git a/app/router/condition.go b/app/router/condition.go index cdcb6747504f..29056555e260 100644 --- a/app/router/condition.go +++ b/app/router/condition.go @@ -1,13 +1,12 @@ package router import ( + "regexp" "strings" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/strmatcher" "github.com/xtls/xray-core/features/routing" - "go.starlark.net/starlark" - "go.starlark.net/syntax" ) type Condition interface { @@ -284,44 +283,22 @@ func (m *ProtocolMatcher) Apply(ctx routing.Context) bool { } type AttributeMatcher struct { - program *starlark.Program -} - -func NewAttributeMatcher(code string) (*AttributeMatcher, error) { - starFile, err := syntax.Parse("attr.star", "satisfied=("+code+")", 0) - if err != nil { - return nil, newError("attr rule").Base(err) - } - p, err := starlark.FileProgram(starFile, func(name string) bool { - return name == "attrs" - }) - if err != nil { - return nil, err - } - return &AttributeMatcher{ - program: p, - }, nil + configuredKeys map[string]*regexp.Regexp } // Match implements attributes matching. func (m *AttributeMatcher) Match(attrs map[string]string) bool { - attrsDict := new(starlark.Dict) + // header keys are case insensitive most likely. So we do a convert + httpHeaders := make(map[string]string) for key, value := range attrs { - attrsDict.SetKey(starlark.String(key), starlark.String(value)) + httpHeaders[strings.ToLower(key)] = value } - - predefined := make(starlark.StringDict) - predefined["attrs"] = attrsDict - - thread := &starlark.Thread{ - Name: "matcher", - } - results, err := m.program.Init(thread, predefined) - if err != nil { - newError("attr matcher").Base(err).WriteToLog() + for key, regex := range m.configuredKeys { + if a, ok := httpHeaders[key]; !ok || !regex.MatchString(a) { + return false + } } - satisfied := results["satisfied"] - return satisfied != nil && bool(satisfied.Truth()) + return true } // Apply implements Condition. diff --git a/app/router/condition_geoip.go b/app/router/condition_geoip.go index eb47be833c29..09c81fa85e9e 100644 --- a/app/router/condition_geoip.go +++ b/app/router/condition_geoip.go @@ -1,81 +1,49 @@ package router import ( - "encoding/binary" - "sort" + "net/netip" + "strconv" "github.com/xtls/xray-core/common/net" + "go4.org/netipx" ) -type ipv6 struct { - a uint64 - b uint64 -} - type GeoIPMatcher struct { countryCode string reverseMatch bool - ip4 []uint32 - prefix4 []uint8 - ip6 []ipv6 - prefix6 []uint8 -} - -func normalize4(ip uint32, prefix uint8) uint32 { - return (ip >> (32 - prefix)) << (32 - prefix) -} - -func normalize6(ip ipv6, prefix uint8) ipv6 { - if prefix <= 64 { - ip.a = (ip.a >> (64 - prefix)) << (64 - prefix) - ip.b = 0 - } else { - ip.b = (ip.b >> (128 - prefix)) << (128 - prefix) - } - return ip + ip4 *netipx.IPSet + ip6 *netipx.IPSet } func (m *GeoIPMatcher) Init(cidrs []*CIDR) error { - ip4Count := 0 - ip6Count := 0 + var builder4, builder6 netipx.IPSetBuilder for _, cidr := range cidrs { - ip := cidr.Ip + ip := net.IP(cidr.GetIp()) + ipPrefixString := ip.String() + "/" + strconv.Itoa(int(cidr.GetPrefix())) + ipPrefix, err := netip.ParsePrefix(ipPrefixString) + if err != nil { + return err + } + switch len(ip) { - case 4: - ip4Count++ - case 16: - ip6Count++ - default: - return newError("unexpect ip length: ", len(ip)) + case net.IPv4len: + builder4.AddPrefix(ipPrefix) + case net.IPv6len: + builder6.AddPrefix(ipPrefix) } } - cidrList := CIDRList(cidrs) - sort.Sort(&cidrList) - - m.ip4 = make([]uint32, 0, ip4Count) - m.prefix4 = make([]uint8, 0, ip4Count) - m.ip6 = make([]ipv6, 0, ip6Count) - m.prefix6 = make([]uint8, 0, ip6Count) - - for _, cidr := range cidrList { - ip := cidr.Ip - prefix := uint8(cidr.Prefix) - switch len(ip) { - case 4: - m.ip4 = append(m.ip4, normalize4(binary.BigEndian.Uint32(ip), prefix)) - m.prefix4 = append(m.prefix4, prefix) - case 16: - ip6 := ipv6{ - a: binary.BigEndian.Uint64(ip[0:8]), - b: binary.BigEndian.Uint64(ip[8:16]), - } - ip6 = normalize6(ip6, prefix) + if ip4, err := builder4.IPSet(); err != nil { + return err + } else { + m.ip4 = ip4 + } - m.ip6 = append(m.ip6, ip6) - m.prefix6 = append(m.prefix6, prefix) - } + if ip6, err := builder6.IPSet(); err != nil { + return err + } else { + m.ip6 = ip6 } return nil @@ -85,91 +53,37 @@ func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) { m.reverseMatch = isReverseMatch } -func (m *GeoIPMatcher) match4(ip uint32) bool { - if len(m.ip4) == 0 { - return false - } - - if ip < m.ip4[0] { +func (m *GeoIPMatcher) match4(ip net.IP) bool { + nip, ok := netipx.FromStdIP(ip) + if !ok { return false } - size := uint32(len(m.ip4)) - l := uint32(0) - r := size - for l < r { - x := ((l + r) >> 1) - if ip < m.ip4[x] { - r = x - continue - } - - nip := normalize4(ip, m.prefix4[x]) - if nip == m.ip4[x] { - return true - } - - l = x + 1 - } - - return l > 0 && normalize4(ip, m.prefix4[l-1]) == m.ip4[l-1] + return m.ip4.Contains(nip) } -func less6(a ipv6, b ipv6) bool { - return a.a < b.a || (a.a == b.a && a.b < b.b) -} - -func (m *GeoIPMatcher) match6(ip ipv6) bool { - if len(m.ip6) == 0 { - return false - } - - if less6(ip, m.ip6[0]) { +func (m *GeoIPMatcher) match6(ip net.IP) bool { + nip, ok := netipx.FromStdIP(ip) + if !ok { return false } - size := uint32(len(m.ip6)) - l := uint32(0) - r := size - for l < r { - x := (l + r) / 2 - if less6(ip, m.ip6[x]) { - r = x - continue - } - - if normalize6(ip, m.prefix6[x]) == m.ip6[x] { - return true - } - - l = x + 1 - } - - return l > 0 && normalize6(ip, m.prefix6[l-1]) == m.ip6[l-1] + return m.ip6.Contains(nip) } // Match returns true if the given ip is included by the GeoIP. func (m *GeoIPMatcher) Match(ip net.IP) bool { + isMatched := false switch len(ip) { - case 4: - if m.reverseMatch { - return !m.match4(binary.BigEndian.Uint32(ip)) - } - return m.match4(binary.BigEndian.Uint32(ip)) - case 16: - if m.reverseMatch { - return !m.match6(ipv6{ - a: binary.BigEndian.Uint64(ip[0:8]), - b: binary.BigEndian.Uint64(ip[8:16]), - }) - } - return m.match6(ipv6{ - a: binary.BigEndian.Uint64(ip[0:8]), - b: binary.BigEndian.Uint64(ip[8:16]), - }) - default: - return false + case net.IPv4len: + isMatched = m.match4(ip) + case net.IPv6len: + isMatched = m.match6(ip) + } + if m.reverseMatch { + return !isMatched } + return isMatched } // GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code. diff --git a/app/router/condition_geoip_test.go b/app/router/condition_geoip_test.go index b5a5ef90966f..63bd222ec901 100644 --- a/app/router/condition_geoip_test.go +++ b/app/router/condition_geoip_test.go @@ -5,12 +5,12 @@ import ( "path/filepath" "testing" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform/filesystem" + "google.golang.org/protobuf/proto" ) func init() { @@ -53,7 +53,7 @@ func TestGeoIPMatcherContainer(t *testing.T) { } func TestGeoIPMatcher(t *testing.T) { - cidrList := router.CIDRList{ + cidrList := []*router.CIDR{ {Ip: []byte{0, 0, 0, 0}, Prefix: 8}, {Ip: []byte{10, 0, 0, 0}, Prefix: 8}, {Ip: []byte{100, 64, 0, 0}, Prefix: 10}, @@ -124,8 +124,40 @@ func TestGeoIPMatcher(t *testing.T) { } } +func TestGeoIPMatcherRegression(t *testing.T) { + cidrList := []*router.CIDR{ + {Ip: []byte{98, 108, 20, 0}, Prefix: 22}, + {Ip: []byte{98, 108, 20, 0}, Prefix: 23}, + } + + matcher := &router.GeoIPMatcher{} + common.Must(matcher.Init(cidrList)) + + testCases := []struct { + Input string + Output bool + }{ + { + Input: "98.108.22.11", + Output: true, + }, + { + Input: "98.108.25.0", + Output: false, + }, + } + + for _, testCase := range testCases { + ip := net.ParseAddress(testCase.Input).IP() + actual := matcher.Match(ip) + if actual != testCase.Output { + t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual) + } + } +} + func TestGeoIPReverseMatcher(t *testing.T) { - cidrList := router.CIDRList{ + cidrList := []*router.CIDR{ {Ip: []byte{8, 8, 8, 8}, Prefix: 32}, {Ip: []byte{91, 108, 4, 0}, Prefix: 16}, } diff --git a/app/router/condition_test.go b/app/router/condition_test.go index 5d98eb43e79b..b5d59297a25b 100644 --- a/app/router/condition_test.go +++ b/app/router/condition_test.go @@ -6,7 +6,6 @@ import ( "strconv" "testing" - "github.com/golang/protobuf/proto" . "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" @@ -18,6 +17,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/features/routing" routing_session "github.com/xtls/xray-core/features/routing/session" + "google.golang.org/protobuf/proto" ) func init() { @@ -307,8 +307,10 @@ func TestRoutingRule(t *testing.T) { }, { rule: &RoutingRule{ - Protocol: []string{"http"}, - Attributes: "attrs[':path'].startswith('/test')", + Protocol: []string{"http"}, + Attributes: map[string]string{ + ":path": "/test", + }, }, test: []ruleTest{ { @@ -317,6 +319,19 @@ func TestRoutingRule(t *testing.T) { }, }, }, + { + rule: &RoutingRule{ + Attributes: map[string]string{ + "Custom": "p([a-z]+)ch", + }, + }, + test: []ruleTest{ + { + input: withContent(&session.Content{Attributes: map[string]string{"custom": "peach"}}), + output: true, + }, + }, + }, } for _, test := range cases { diff --git a/app/router/config.go b/app/router/config.go index 9bb4bc46738f..f50f02a1dd9d 100644 --- a/app/router/config.go +++ b/app/router/config.go @@ -1,50 +1,14 @@ package router import ( + "regexp" + "strings" + "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/routing" ) -// CIDRList is an alias of []*CIDR to provide sort.Interface. -type CIDRList []*CIDR - -// Len implements sort.Interface. -func (l *CIDRList) Len() int { - return len(*l) -} - -// Less implements sort.Interface. -func (l *CIDRList) Less(i int, j int) bool { - ci := (*l)[i] - cj := (*l)[j] - - if len(ci.Ip) < len(cj.Ip) { - return true - } - - if len(ci.Ip) > len(cj.Ip) { - return false - } - - for k := 0; k < len(ci.Ip); k++ { - if ci.Ip[k] < cj.Ip[k] { - return true - } - - if ci.Ip[k] > cj.Ip[k] { - return false - } - } - - return ci.Prefix < cj.Prefix -} - -// Swap implements sort.Interface. -func (l *CIDRList) Swap(i int, j int) { - (*l)[i], (*l)[j] = (*l)[j], (*l)[i] -} - type Rule struct { Tag string Balancer *Balancer @@ -143,11 +107,11 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) { } if len(rr.Attributes) > 0 { - cond, err := NewAttributeMatcher(rr.Attributes) - if err != nil { - return nil, err + configuredKeys := make(map[string]*regexp.Regexp) + for key, value := range rr.Attributes { + configuredKeys[strings.ToLower(key)] = regexp.MustCompile(value) } - conds.Add(cond) + conds.Add(&AttributeMatcher{configuredKeys}) } if conds.Len() == 0 { diff --git a/app/router/config.pb.go b/app/router/config.pb.go index f425fe260205..3fd01cc77a0c 100644 --- a/app/router/config.pb.go +++ b/app/router/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/router/config.proto package router @@ -486,7 +486,7 @@ type RoutingRule struct { // List of CIDRs for target IP address matching. // Deprecated. Use geoip below. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/router/config.proto. Cidr []*CIDR `protobuf:"bytes,3,rep,name=cidr,proto3" json:"cidr,omitempty"` // List of GeoIPs for target IP address matching. If this entry exists, the // cidr above will have no effect. GeoIP fields with the same country code are @@ -496,30 +496,30 @@ type RoutingRule struct { // A range of port [from, to]. If the destination port is in this range, this // rule takes effect. Deprecated. Use port_list. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/router/config.proto. PortRange *net.PortRange `protobuf:"bytes,4,opt,name=port_range,json=portRange,proto3" json:"port_range,omitempty"` // List of ports. PortList *net.PortList `protobuf:"bytes,14,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"` // List of networks. Deprecated. Use networks. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/router/config.proto. NetworkList *net.NetworkList `protobuf:"bytes,5,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"` // List of networks for matching. Networks []net.Network `protobuf:"varint,13,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"` // List of CIDRs for source IP address matching. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in app/router/config.proto. SourceCidr []*CIDR `protobuf:"bytes,6,rep,name=source_cidr,json=sourceCidr,proto3" json:"source_cidr,omitempty"` // List of GeoIPs for source IP address matching. If this entry exists, the // source_cidr above will have no effect. SourceGeoip []*GeoIP `protobuf:"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3" json:"source_geoip,omitempty"` // List of ports for source port matching. - SourcePortList *net.PortList `protobuf:"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3" json:"source_port_list,omitempty"` - UserEmail []string `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"` - InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` - Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"` - Attributes string `protobuf:"bytes,15,opt,name=attributes,proto3" json:"attributes,omitempty"` - DomainMatcher string `protobuf:"bytes,17,opt,name=domain_matcher,json=domainMatcher,proto3" json:"domain_matcher,omitempty"` + SourcePortList *net.PortList `protobuf:"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3" json:"source_port_list,omitempty"` + UserEmail []string `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"` + InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` + Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"` + Attributes map[string]string `protobuf:"bytes,15,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + DomainMatcher string `protobuf:"bytes,17,opt,name=domain_matcher,json=domainMatcher,proto3" json:"domain_matcher,omitempty"` } func (x *RoutingRule) Reset() { @@ -582,7 +582,7 @@ func (x *RoutingRule) GetDomain() []*Domain { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/router/config.proto. func (x *RoutingRule) GetCidr() []*CIDR { if x != nil { return x.Cidr @@ -597,7 +597,7 @@ func (x *RoutingRule) GetGeoip() []*GeoIP { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/router/config.proto. func (x *RoutingRule) GetPortRange() *net.PortRange { if x != nil { return x.PortRange @@ -612,7 +612,7 @@ func (x *RoutingRule) GetPortList() *net.PortList { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/router/config.proto. func (x *RoutingRule) GetNetworkList() *net.NetworkList { if x != nil { return x.NetworkList @@ -627,7 +627,7 @@ func (x *RoutingRule) GetNetworks() []net.Network { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in app/router/config.proto. func (x *RoutingRule) GetSourceCidr() []*CIDR { if x != nil { return x.SourceCidr @@ -670,11 +670,11 @@ func (x *RoutingRule) GetProtocol() []string { return nil } -func (x *RoutingRule) GetAttributes() string { +func (x *RoutingRule) GetAttributes() map[string]string { if x != nil { return x.Attributes } - return "" + return nil } func (x *RoutingRule) GetDomainMatcher() string { @@ -969,7 +969,7 @@ var file_app_router_config_proto_rawDesc = []byte{ 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, - 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xb5, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, + 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xa2, 0x07, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, @@ -1015,43 +1015,49 @@ var file_app_router_config_proto_rawDesc = []byte{ 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, - 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x6a, - 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, - 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, - 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, - 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, - 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, - 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, - 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, - 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, - 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, - 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, - 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, - 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, - 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, - 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, - 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, - 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, - 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x4c, 0x0a, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, + 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x41, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x1a, 0x3d, + 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, + 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x6a, 0x0a, 0x0d, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, + 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, + 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, + 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, + 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, + 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, + 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, + 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1067,7 +1073,7 @@ func file_app_router_config_proto_rawDescGZIP() []byte { } var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_app_router_config_proto_goTypes = []interface{}{ (Domain_Type)(0), // 0: xray.app.router.Domain.Type (Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy @@ -1081,10 +1087,11 @@ var file_app_router_config_proto_goTypes = []interface{}{ (*BalancingRule)(nil), // 9: xray.app.router.BalancingRule (*Config)(nil), // 10: xray.app.router.Config (*Domain_Attribute)(nil), // 11: xray.app.router.Domain.Attribute - (*net.PortRange)(nil), // 12: xray.common.net.PortRange - (*net.PortList)(nil), // 13: xray.common.net.PortList - (*net.NetworkList)(nil), // 14: xray.common.net.NetworkList - (net.Network)(0), // 15: xray.common.net.Network + nil, // 12: xray.app.router.RoutingRule.AttributesEntry + (*net.PortRange)(nil), // 13: xray.common.net.PortRange + (*net.PortList)(nil), // 14: xray.common.net.PortList + (*net.NetworkList)(nil), // 15: xray.common.net.NetworkList + (net.Network)(0), // 16: xray.common.net.Network } var file_app_router_config_proto_depIdxs = []int32{ 0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type @@ -1096,21 +1103,22 @@ var file_app_router_config_proto_depIdxs = []int32{ 2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain 3, // 7: xray.app.router.RoutingRule.cidr:type_name -> xray.app.router.CIDR 4, // 8: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP - 12, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange - 13, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList - 14, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList - 15, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network + 13, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange + 14, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList + 15, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList + 16, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network 3, // 13: xray.app.router.RoutingRule.source_cidr:type_name -> xray.app.router.CIDR 4, // 14: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP - 13, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList - 1, // 16: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy - 8, // 17: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule - 9, // 18: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 14, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList + 12, // 16: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry + 1, // 17: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy + 8, // 18: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule + 9, // 19: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule + 20, // [20:20] is the sub-list for method output_type + 20, // [20:20] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_app_router_config_proto_init() } @@ -1254,7 +1262,7 @@ func file_app_router_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_router_config_proto_rawDesc, NumEnums: 2, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/app/router/config.proto b/app/router/config.proto index 2886077a865d..ab9957f3d08c 100644 --- a/app/router/config.proto +++ b/app/router/config.proto @@ -119,7 +119,7 @@ message RoutingRule { repeated string inbound_tag = 8; repeated string protocol = 9; - string attributes = 15; + map attributes = 15; string domain_matcher = 17; } diff --git a/app/stats/command/command.pb.go b/app/stats/command/command.pb.go index ac995f702504..6d4d0d38c12d 100644 --- a/app/stats/command/command.pb.go +++ b/app/stats/command/command.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/stats/command/command.proto package command diff --git a/app/stats/command/command_grpc.pb.go b/app/stats/command/command_grpc.pb.go index b38fadce268a..cbd024852cdf 100644 --- a/app/stats/command/command_grpc.pb.go +++ b/app/stats/command/command_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.18.0 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.1 // source: app/stats/command/command.proto package command @@ -18,6 +18,12 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + StatsService_GetStats_FullMethodName = "/xray.app.stats.command.StatsService/GetStats" + StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats" + StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats" +) + // StatsServiceClient is the client API for StatsService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -37,7 +43,7 @@ func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient { func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) { out := new(GetStatsResponse) - err := c.cc.Invoke(ctx, "/xray.app.stats.command.StatsService/GetStats", in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -46,7 +52,7 @@ func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) { out := new(QueryStatsResponse) - err := c.cc.Invoke(ctx, "/xray.app.stats.command.StatsService/QueryStats", in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -55,7 +61,7 @@ func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsReque func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) { out := new(SysStatsResponse) - err := c.cc.Invoke(ctx, "/xray.app.stats.command.StatsService/GetSysStats", in, out, opts...) + err := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -108,7 +114,7 @@ func _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec fu } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.stats.command.StatsService/GetStats", + FullMethod: StatsService_GetStats_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest)) @@ -126,7 +132,7 @@ func _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.stats.command.StatsService/QueryStats", + FullMethod: StatsService_QueryStats_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest)) @@ -144,7 +150,7 @@ func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/xray.app.stats.command.StatsService/GetSysStats", + FullMethod: StatsService_GetSysStats_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest)) diff --git a/app/stats/config.pb.go b/app/stats/config.pb.go index b8146ff4b5ef..216da0448020 100644 --- a/app/stats/config.pb.go +++ b/app/stats/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: app/stats/config.proto package stats diff --git a/common/buf/buffer.go b/common/buf/buffer.go index 370d4a3196f8..82795b98c766 100644 --- a/common/buf/buffer.go +++ b/common/buf/buffer.go @@ -160,6 +160,19 @@ func (b *Buffer) BytesTo(to int32) []byte { return b.v[b.start : b.start+to] } +// Check makes sure that 0 <= b.start <= b.end. +func (b *Buffer) Check() { + if b.start < 0 { + b.start = 0 + } + if b.end < 0 { + b.end = 0 + } + if b.start > b.end { + b.start = b.end + } +} + // Resize cuts the buffer at the given position. func (b *Buffer) Resize(from, to int32) { if from < 0 { @@ -173,6 +186,7 @@ func (b *Buffer) Resize(from, to int32) { } b.end = b.start + to b.start += from + b.Check() } // Advance cuts the buffer at the given position. @@ -181,6 +195,7 @@ func (b *Buffer) Advance(from int32) { from += b.Len() } b.start += from + b.Check() } // Len returns the length of the buffer content. diff --git a/common/buf/copy.go b/common/buf/copy.go index 601771bee2cc..3096dc57c13e 100644 --- a/common/buf/copy.go +++ b/common/buf/copy.go @@ -6,6 +6,7 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/signal" + "github.com/xtls/xray-core/features/stats" ) type dataHandler func(MultiBuffer) @@ -40,6 +41,17 @@ func CountSize(sc *SizeCounter) CopyOption { } } +// AddToStatCounter a CopyOption add to stat counter +func AddToStatCounter(sc stats.Counter) CopyOption { + return func(handler *copyHandler) { + handler.onData = append(handler.onData, func(b MultiBuffer) { + if sc != nil { + sc.Add(int64(b.Len())) + } + }) + } +} + type readError struct { error } diff --git a/common/buf/override.go b/common/buf/override.go new file mode 100644 index 000000000000..7b2f1554534f --- /dev/null +++ b/common/buf/override.go @@ -0,0 +1,38 @@ +package buf + +import ( + "github.com/xtls/xray-core/common/net" +) + +type EndpointOverrideReader struct { + Reader + Dest net.Address + OriginalDest net.Address +} + +func (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) { + mb, err := r.Reader.ReadMultiBuffer() + if err == nil { + for _, b := range mb { + if b.UDP != nil && b.UDP.Address == r.OriginalDest { + b.UDP.Address = r.Dest + } + } + } + return mb, err +} + +type EndpointOverrideWriter struct { + Writer + Dest net.Address + OriginalDest net.Address +} + +func (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error { + for _, b := range mb { + if b.UDP != nil && b.UDP.Address == w.Dest { + b.UDP.Address = w.OriginalDest + } + } + return w.Writer.WriteMultiBuffer(mb) +} diff --git a/common/errors/multi_error.go b/common/errors/multi_error.go index 8f19c97a315a..8066ac9e7c5b 100644 --- a/common/errors/multi_error.go +++ b/common/errors/multi_error.go @@ -28,3 +28,20 @@ func Combine(maybeError ...error) error { } return errs } + +func AllEqual(expected error, actual error) bool { + switch errs := actual.(type) { + case multiError: + if len(errs) == 0 { + return false + } + for _, err := range errs { + if err != expected { + return false + } + } + return true + default: + return errs == expected + } +} diff --git a/common/log/log.pb.go b/common/log/log.pb.go index 43f9e4250395..92ea08116fda 100644 --- a/common/log/log.pb.go +++ b/common/log/log.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/log/log.proto package log diff --git a/common/mux/client.go b/common/mux/client.go index 2019738ff114..88621be0f0ef 100644 --- a/common/mux/client.go +++ b/common/mux/client.go @@ -14,6 +14,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/task" + "github.com/xtls/xray-core/common/xudp" "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" @@ -247,22 +248,20 @@ func fetchInput(ctx context.Context, s *Session, output buf.Writer) { transferType = protocol.TransferTypePacket } s.transferType = transferType - writer := NewWriter(s.ID, dest, output, transferType) - defer s.Close() + writer := NewWriter(s.ID, dest, output, transferType, xudp.GetGlobalID(ctx)) + defer s.Close(false) defer writer.Close() newError("dispatching request to ", dest).WriteToLog(session.ExportIDToError(ctx)) if err := writeFirstPayload(s.input, writer); err != nil { newError("failed to write first payload").Base(err).WriteToLog(session.ExportIDToError(ctx)) writer.hasError = true - common.Interrupt(s.input) return } if err := buf.Copy(s.input, writer); err != nil { newError("failed to fetch all input").Base(err).WriteToLog(session.ExportIDToError(ctx)) writer.hasError = true - common.Interrupt(s.input) return } } @@ -335,15 +334,8 @@ func (m *ClientWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.Buffere err := buf.Copy(rr, s.output) if err != nil && buf.IsWriteError(err) { newError("failed to write to downstream. closing session ", s.ID).Base(err).WriteToLog() - - // Notify remote peer to close this session. - closingWriter := NewResponseWriter(meta.SessionID, m.link.Writer, protocol.TransferTypeStream) - closingWriter.Close() - - drainErr := buf.Copy(rr, buf.Discard) - common.Interrupt(s.input) - s.Close() - return drainErr + s.Close(false) + return buf.Copy(rr, buf.Discard) } return err @@ -351,12 +343,7 @@ func (m *ClientWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.Buffere func (m *ClientWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error { if s, found := m.sessionManager.Get(meta.SessionID); found { - if meta.Option.Has(OptionError) { - common.Interrupt(s.input) - common.Interrupt(s.output) - } - common.Interrupt(s.input) - s.Close() + s.Close(false) } if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) diff --git a/common/mux/frame.go b/common/mux/frame.go index 30f3c1db4563..d53ac2020f90 100644 --- a/common/mux/frame.go +++ b/common/mux/frame.go @@ -58,6 +58,7 @@ type FrameMetadata struct { SessionID uint16 Option bitmask.Byte SessionStatus SessionStatus + GlobalID [8]byte } func (f FrameMetadata) WriteTo(b *buf.Buffer) error { @@ -81,6 +82,9 @@ func (f FrameMetadata) WriteTo(b *buf.Buffer) error { if err := addrParser.WriteAddressPort(b, f.Target.Address, f.Target.Port); err != nil { return err } + if b.UDP != nil { // make sure it's user's proxy request + b.Write(f.GlobalID[:]) // no need to check whether it's empty + } } else if b.UDP != nil { b.WriteByte(byte(TargetNetworkUDP)) addrParser.WriteAddressPort(b, b.UDP.Address, b.UDP.Port) @@ -122,7 +126,8 @@ func (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer) error { f.Option = bitmask.Byte(b.Byte(3)) f.Target.Network = net.Network_Unknown - if f.SessionStatus == SessionStatusNew || (f.SessionStatus == SessionStatusKeep && b.Len() != 4) { + if f.SessionStatus == SessionStatusNew || (f.SessionStatus == SessionStatusKeep && b.Len() > 4 && + TargetNetwork(b.Byte(4)) == TargetNetworkUDP) { // MUST check the flag first if b.Len() < 8 { return newError("insufficient buffer: ", b.Len()) } @@ -144,5 +149,11 @@ func (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer) error { } } + // Application data is essential, to test whether the pipe is closed. + if f.SessionStatus == SessionStatusNew && f.Option.Has(OptionData) && + f.Target.Network == net.Network_UDP && b.Len() >= 8 { + copy(f.GlobalID[:], b.Bytes()) + } + return nil } diff --git a/common/mux/mux_test.go b/common/mux/mux_test.go index 39def2ab0a70..f326ffd7765c 100644 --- a/common/mux/mux_test.go +++ b/common/mux/mux_test.go @@ -32,13 +32,13 @@ func TestReaderWriter(t *testing.T) { pReader, pWriter := pipe.New(pipe.WithSizeLimit(1024)) dest := net.TCPDestination(net.DomainAddress("example.com"), 80) - writer := NewWriter(1, dest, pWriter, protocol.TransferTypeStream) + writer := NewWriter(1, dest, pWriter, protocol.TransferTypeStream, [8]byte{}) dest2 := net.TCPDestination(net.LocalHostIP, 443) - writer2 := NewWriter(2, dest2, pWriter, protocol.TransferTypeStream) + writer2 := NewWriter(2, dest2, pWriter, protocol.TransferTypeStream, [8]byte{}) dest3 := net.TCPDestination(net.LocalHostIPv6, 18374) - writer3 := NewWriter(3, dest3, pWriter, protocol.TransferTypeStream) + writer3 := NewWriter(3, dest3, pWriter, protocol.TransferTypeStream, [8]byte{}) writePayload := func(writer *Writer, payload ...byte) error { b := buf.New() diff --git a/common/mux/server.go b/common/mux/server.go index df461be799e5..2d33189fbde7 100644 --- a/common/mux/server.go +++ b/common/mux/server.go @@ -99,7 +99,7 @@ func handle(ctx context.Context, s *Session, output buf.Writer) { } writer.Close() - s.Close() + s.Close(false) } func (w *ServerWorker) ActiveConnections() uint32 { @@ -131,6 +131,81 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, } ctx = log.ContextWithAccessMessage(ctx, msg) } + + if network := session.AllowedNetworkFromContext(ctx); network != net.Network_Unknown { + if meta.Target.Network != network { + return newError("unexpected network ", meta.Target.Network) // it will break the whole Mux connection + } + } + + if meta.GlobalID != [8]byte{} { // MUST ignore empty Global ID + mb, err := NewPacketReader(reader, &meta.Target).ReadMultiBuffer() + if err != nil { + return err + } + XUDPManager.Lock() + x := XUDPManager.Map[meta.GlobalID] + if x == nil { + x = &XUDP{GlobalID: meta.GlobalID} + XUDPManager.Map[meta.GlobalID] = x + XUDPManager.Unlock() + } else { + if x.Status == Initializing { // nearly impossible + XUDPManager.Unlock() + newError("XUDP hit ", meta.GlobalID).Base(errors.New("conflict")).AtWarning().WriteToLog(session.ExportIDToError(ctx)) + // It's not a good idea to return an err here, so just let client wait. + // Client will receive an End frame after sending a Keep frame. + return nil + } + x.Status = Initializing + XUDPManager.Unlock() + x.Mux.Close(false) // detach from previous Mux + b := buf.New() + b.Write(mb[0].Bytes()) + b.UDP = mb[0].UDP + if err = x.Mux.output.WriteMultiBuffer(mb); err != nil { + x.Interrupt() + mb = buf.MultiBuffer{b} + } else { + b.Release() + mb = nil + } + newError("XUDP hit ", meta.GlobalID).Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + if mb != nil { + ctx = session.ContextWithTimeoutOnly(ctx, true) + // Actually, it won't return an error in Xray-core's implementations. + link, err := w.dispatcher.Dispatch(ctx, meta.Target) + if err != nil { + XUDPManager.Lock() + delete(XUDPManager.Map, x.GlobalID) + XUDPManager.Unlock() + err = newError("XUDP new ", meta.GlobalID).Base(errors.New("failed to dispatch request to ", meta.Target).Base(err)) + return err // it will break the whole Mux connection + } + link.Writer.WriteMultiBuffer(mb) // it's meaningless to test a new pipe + x.Mux = &Session{ + input: link.Reader, + output: link.Writer, + } + newError("XUDP new ", meta.GlobalID).Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + x.Mux = &Session{ + input: x.Mux.input, + output: x.Mux.output, + parent: w.sessionManager, + ID: meta.SessionID, + transferType: protocol.TransferTypePacket, + XUDP: x, + } + go handle(ctx, x.Mux, w.link.Writer) + x.Status = Active + if !w.sessionManager.Add(x.Mux) { + x.Mux.Close(false) + } + return nil + } + link, err := w.dispatcher.Dispatch(ctx, meta.Target) if err != nil { if meta.Option.Has(OptionData) { @@ -157,8 +232,7 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, rr := s.NewReader(reader, &meta.Target) if err := buf.Copy(rr, s.output); err != nil { buf.Copy(rr, buf.Discard) - common.Interrupt(s.input) - return s.Close() + return s.Close(false) } return nil } @@ -182,15 +256,8 @@ func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.Buffere if err != nil && buf.IsWriteError(err) { newError("failed to write to downstream writer. closing session ", s.ID).Base(err).WriteToLog() - - // Notify remote peer to close this session. - closingWriter := NewResponseWriter(meta.SessionID, w.link.Writer, protocol.TransferTypeStream) - closingWriter.Close() - - drainErr := buf.Copy(rr, buf.Discard) - common.Interrupt(s.input) - s.Close() - return drainErr + s.Close(false) + return buf.Copy(rr, buf.Discard) } return err @@ -198,12 +265,7 @@ func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.Buffere func (w *ServerWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error { if s, found := w.sessionManager.Get(meta.SessionID); found { - if meta.Option.Has(OptionError) { - common.Interrupt(s.input) - common.Interrupt(s.output) - } - common.Interrupt(s.input) - s.Close() + s.Close(false) } if meta.Option.Has(OptionData) { return buf.Copy(NewStreamReader(reader), buf.Discard) diff --git a/common/mux/session.go b/common/mux/session.go index 2f21b97a33e4..695974402d25 100644 --- a/common/mux/session.go +++ b/common/mux/session.go @@ -1,12 +1,16 @@ package mux import ( + "io" + "runtime" "sync" + "time" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/transport/pipe" ) type SessionManager struct { @@ -61,21 +65,25 @@ func (m *SessionManager) Allocate() *Session { return s } -func (m *SessionManager) Add(s *Session) { +func (m *SessionManager) Add(s *Session) bool { m.Lock() defer m.Unlock() if m.closed { - return + return false } m.count++ m.sessions[s.ID] = s + return true } -func (m *SessionManager) Remove(id uint16) { - m.Lock() - defer m.Unlock() +func (m *SessionManager) Remove(locked bool, id uint16) { + if !locked { + m.Lock() + defer m.Unlock() + } + locked = true if m.closed { return @@ -83,9 +91,11 @@ func (m *SessionManager) Remove(id uint16) { delete(m.sessions, id) - if len(m.sessions) == 0 { - m.sessions = make(map[uint16]*Session, 16) - } + /* + if len(m.sessions) == 0 { + m.sessions = make(map[uint16]*Session, 16) + } + */ } func (m *SessionManager) Get(id uint16) (*Session, bool) { @@ -127,8 +137,7 @@ func (m *SessionManager) Close() error { m.closed = true for _, s := range m.sessions { - common.Close(s.input) - common.Close(s.output) + s.Close(true) } m.sessions = nil @@ -142,13 +151,40 @@ type Session struct { parent *SessionManager ID uint16 transferType protocol.TransferType + closed bool + XUDP *XUDP } // Close closes all resources associated with this session. -func (s *Session) Close() error { - common.Close(s.output) - common.Close(s.input) - s.parent.Remove(s.ID) +func (s *Session) Close(locked bool) error { + if !locked { + s.parent.Lock() + defer s.parent.Unlock() + } + locked = true + if s.closed { + return nil + } + s.closed = true + if s.XUDP == nil { + common.Interrupt(s.input) + common.Close(s.output) + } else { + // Stop existing handle(), then trigger writer.Close(). + // Note that s.output may be dispatcher.SizeStatWriter. + s.input.(*pipe.Reader).ReturnAnError(io.EOF) + runtime.Gosched() + // If the error set by ReturnAnError still exists, clear it. + s.input.(*pipe.Reader).Recover() + XUDPManager.Lock() + if s.XUDP.Status == Active { + s.XUDP.Expire = time.Now().Add(time.Minute) + s.XUDP.Status = Expiring + newError("XUDP put ", s.XUDP.GlobalID).AtDebug().WriteToLog() + } + XUDPManager.Unlock() + } + s.parent.Remove(locked, s.ID) return nil } @@ -159,3 +195,45 @@ func (s *Session) NewReader(reader *buf.BufferedReader, dest *net.Destination) b } return NewPacketReader(reader, dest) } + +const ( + Initializing = 0 + Active = 1 + Expiring = 2 +) + +type XUDP struct { + GlobalID [8]byte + Status uint64 + Expire time.Time + Mux *Session +} + +func (x *XUDP) Interrupt() { + common.Interrupt(x.Mux.input) + common.Close(x.Mux.output) +} + +var XUDPManager struct { + sync.Mutex + Map map[[8]byte]*XUDP +} + +func init() { + XUDPManager.Map = make(map[[8]byte]*XUDP) + go func() { + for { + time.Sleep(time.Minute) + now := time.Now() + XUDPManager.Lock() + for id, x := range XUDPManager.Map { + if x.Status == Expiring && now.After(x.Expire) { + x.Interrupt() + delete(XUDPManager.Map, id) + newError("XUDP del ", id).AtDebug().WriteToLog() + } + } + XUDPManager.Unlock() + } + }() +} diff --git a/common/mux/session_test.go b/common/mux/session_test.go index 7139df10995c..d81ad8c405eb 100644 --- a/common/mux/session_test.go +++ b/common/mux/session_test.go @@ -44,7 +44,7 @@ func TestSessionManagerClose(t *testing.T) { if m.CloseIfNoSession() { t.Error("able to close") } - m.Remove(s.ID) + m.Remove(false, s.ID) if !m.CloseIfNoSession() { t.Error("not able to close") } diff --git a/common/mux/writer.go b/common/mux/writer.go index f7a22b2d7c3c..a6dc551d6bdd 100644 --- a/common/mux/writer.go +++ b/common/mux/writer.go @@ -15,15 +15,17 @@ type Writer struct { followup bool hasError bool transferType protocol.TransferType + globalID [8]byte } -func NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType protocol.TransferType) *Writer { +func NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType protocol.TransferType, globalID [8]byte) *Writer { return &Writer{ id: id, dest: dest, writer: writer, followup: false, transferType: transferType, + globalID: globalID, } } @@ -40,6 +42,7 @@ func (w *Writer) getNextFrameMeta() FrameMetadata { meta := FrameMetadata{ SessionID: w.id, Target: w.dest, + GlobalID: w.globalID, } if w.followup { diff --git a/common/net/address.pb.go b/common/net/address.pb.go index fb996502f3d9..82240db35a76 100644 --- a/common/net/address.pb.go +++ b/common/net/address.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/net/address.proto package net diff --git a/common/net/destination.pb.go b/common/net/destination.pb.go index f0c77b991356..ea6339cbfd64 100644 --- a/common/net/destination.pb.go +++ b/common/net/destination.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/net/destination.proto package net diff --git a/common/net/network.pb.go b/common/net/network.pb.go index 9ca8415063fd..18e0df4b0987 100644 --- a/common/net/network.pb.go +++ b/common/net/network.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/net/network.proto package net @@ -24,7 +24,7 @@ type Network int32 const ( Network_Unknown Network = 0 - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in common/net/network.proto. Network_RawTCP Network = 1 Network_TCP Network = 2 Network_UDP Network = 3 diff --git a/common/net/port.pb.go b/common/net/port.pb.go index 395b3e6b7ec2..255bd9405d98 100644 --- a/common/net/port.pb.go +++ b/common/net/port.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/net/port.proto package net diff --git a/common/protocol/headers.go b/common/protocol/headers.go index 1dcc467e5bb1..261e21d93413 100644 --- a/common/protocol/headers.go +++ b/common/protocol/headers.go @@ -6,6 +6,7 @@ import ( "github.com/xtls/xray-core/common/bitmask" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/uuid" + "golang.org/x/sys/cpu" ) // RequestCommand is a custom command in a proxy request. @@ -29,11 +30,10 @@ func (c RequestCommand) TransferType() TransferType { } const ( - // RequestOptionChunkStream indicates request payload is chunked. Each chunk consists of length, authentication and payload. + // [DEPRECATED 2023-06] RequestOptionChunkStream indicates request payload is chunked. Each chunk consists of length, authentication and payload. RequestOptionChunkStream bitmask.Byte = 0x01 - // RequestOptionConnectionReuse indicates client side expects to reuse the connection. - RequestOptionConnectionReuse bitmask.Byte = 0x02 + // 0x02 legacy setting RequestOptionChunkMasking bitmask.Byte = 0x04 @@ -75,13 +75,24 @@ type CommandSwitchAccount struct { Port net.Port ID uuid.UUID Level uint32 - AlterIds uint16 ValidMin byte } +var ( + hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + // Keep in sync with crypto/aes/cipher_s390x.go. + hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && + (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) + + hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 || + runtime.GOARCH == "arm64" && hasGCMAsmARM64 || + runtime.GOARCH == "s390x" && hasGCMAsmS390X +) + func (sc *SecurityConfig) GetSecurityType() SecurityType { if sc == nil || sc.Type == SecurityType_AUTO { - if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { + if hasAESGCMHardwareSupport { return SecurityType_AES128_GCM } return SecurityType_CHACHA20_POLY1305 diff --git a/common/protocol/headers.pb.go b/common/protocol/headers.pb.go index 3a3f64c4620c..96c427d4080a 100644 --- a/common/protocol/headers.pb.go +++ b/common/protocol/headers.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/protocol/headers.proto package protocol @@ -24,11 +24,10 @@ type SecurityType int32 const ( SecurityType_UNKNOWN SecurityType = 0 - SecurityType_LEGACY SecurityType = 1 SecurityType_AUTO SecurityType = 2 SecurityType_AES128_GCM SecurityType = 3 SecurityType_CHACHA20_POLY1305 SecurityType = 4 - SecurityType_NONE SecurityType = 5 + SecurityType_NONE SecurityType = 5 // [DEPRECATED 2023-06] SecurityType_ZERO SecurityType = 6 ) @@ -36,7 +35,6 @@ const ( var ( SecurityType_name = map[int32]string{ 0: "UNKNOWN", - 1: "LEGACY", 2: "AUTO", 3: "AES128_GCM", 4: "CHACHA20_POLY1305", @@ -45,7 +43,6 @@ var ( } SecurityType_value = map[string]int32{ "UNKNOWN": 0, - "LEGACY": 1, "AUTO": 2, "AES128_GCM": 3, "CHACHA20_POLY1305": 4, @@ -139,20 +136,19 @@ var file_common_protocol_headers_proto_rawDesc = []byte{ 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, - 0x6c, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, - 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, - 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x45, 0x53, 0x31, 0x32, 0x38, 0x5f, 0x47, 0x43, 0x4d, - 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, 0x30, 0x5f, 0x50, - 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, - 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x5a, 0x45, 0x52, 0x4f, 0x10, 0x06, 0x42, 0x5e, 0x0a, - 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, - 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x60, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x45, 0x53, 0x31, 0x32, 0x38, + 0x5f, 0x47, 0x43, 0x4d, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, + 0x32, 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, 0x30, 0x35, 0x10, 0x04, 0x12, 0x08, 0x0a, + 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x5a, 0x45, 0x52, 0x4f, 0x10, + 0x06, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x50, 0x01, 0x5a, + 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, + 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, + 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/common/protocol/headers.proto b/common/protocol/headers.proto index cb0b8ff0498c..1ae3537f52d9 100644 --- a/common/protocol/headers.proto +++ b/common/protocol/headers.proto @@ -8,11 +8,10 @@ option java_multiple_files = true; enum SecurityType { UNKNOWN = 0; - LEGACY = 1; AUTO = 2; AES128_GCM = 3; CHACHA20_POLY1305 = 4; - NONE = 5; + NONE = 5; // [DEPRECATED 2023-06] ZERO = 6; } diff --git a/common/protocol/id.go b/common/protocol/id.go index 2a1eb17adcbb..211fc5782568 100644 --- a/common/protocol/id.go +++ b/common/protocol/id.go @@ -1,9 +1,7 @@ package protocol import ( - "crypto/hmac" "crypto/md5" - "hash" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/uuid" @@ -13,12 +11,6 @@ const ( IDBytesLen = 16 ) -type IDHash func(key []byte) hash.Hash - -func DefaultIDHash(key []byte) hash.Hash { - return hmac.New(md5.New, key) -} - // The ID of en entity, in the form of a UUID. type ID struct { uuid uuid.UUID @@ -55,28 +47,3 @@ func NewID(uuid uuid.UUID) *ID { md5hash.Sum(id.cmdKey[:0]) return id } - -func nextID(u *uuid.UUID) uuid.UUID { - md5hash := md5.New() - common.Must2(md5hash.Write(u.Bytes())) - common.Must2(md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81"))) - var newid uuid.UUID - for { - md5hash.Sum(newid[:0]) - if !newid.Equals(u) { - return newid - } - common.Must2(md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2"))) - } -} - -func NewAlterIDs(primary *ID, alterIDCount uint16) []*ID { - alterIDs := make([]*ID, alterIDCount) - prevID := primary.UUID() - for idx := range alterIDs { - newid := nextID(&prevID) - alterIDs[idx] = NewID(newid) - prevID = newid - } - return alterIDs -} diff --git a/common/protocol/quic/qtls_go118.go b/common/protocol/quic/qtls_go118.go index ce5169b550c3..bfa5e245741b 100644 --- a/common/protocol/quic/qtls_go118.go +++ b/common/protocol/quic/qtls_go118.go @@ -1,16 +1,18 @@ package quic import ( + "crypto" "crypto/cipher" - - "github.com/marten-seemann/qtls-go1-18" -) - -type ( - // A CipherSuiteTLS13 is a cipher suite for TLS 1.3 - CipherSuiteTLS13 = qtls.CipherSuiteTLS13 + _ "crypto/tls" + _ "unsafe" ) -func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD { - return qtls.AEADAESGCMTLS13(key, fixedNonce) +type CipherSuiteTLS13 struct { + ID uint16 + KeyLen int + AEAD func(key, fixedNonce []byte) cipher.AEAD + Hash crypto.Hash } + +//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13 +func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD diff --git a/common/protocol/quic/sniff.go b/common/protocol/quic/sniff.go index 0065742cc844..bf4614648420 100644 --- a/common/protocol/quic/sniff.go +++ b/common/protocol/quic/sniff.go @@ -7,9 +7,10 @@ import ( "encoding/binary" "io" - "github.com/lucas-clemente/quic-go/quicvarint" + "github.com/quic-go/quic-go/quicvarint" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/bytespool" "github.com/xtls/xray-core/common/errors" ptls "github.com/xtls/xray-core/common/protocol/tls" "golang.org/x/crypto/hkdf" @@ -141,7 +142,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) { packetNumber = uint32(n) } - if packetNumber != 0 { + if packetNumber != 0 && packetNumber != 1 { return nil, errNotQuicInitial } @@ -159,32 +160,92 @@ func SniffQUIC(b []byte) (*SniffHeader, error) { return nil, err } buffer = buf.FromBytes(decrypted) - frameType, err := buffer.ReadByte() - if err != nil { - return nil, io.ErrUnexpectedEOF - } - if frameType != 0x6 { - // not crypto frame - return &SniffHeader{domain: ""}, nil - } - if common.Error2(quicvarint.Read(buffer)) != nil { - return nil, io.ErrUnexpectedEOF - } - dataLen, err := quicvarint.Read(buffer) - if err != nil { - return nil, io.ErrUnexpectedEOF - } - if dataLen > uint64(buffer.Len()) { - return nil, io.ErrUnexpectedEOF + + cryptoLen := uint(0) + cryptoData := bytespool.Alloc(buffer.Len()) + defer bytespool.Free(cryptoData) + for i := 0; !buffer.IsEmpty(); i++ { + frameType := byte(0x0) // Default to PADDING frame + for frameType == 0x0 && !buffer.IsEmpty() { + frameType, _ = buffer.ReadByte() + } + switch frameType { + case 0x00: // PADDING frame + case 0x01: // PING frame + case 0x02, 0x03: // ACK frame + if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged + return nil, io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay + return nil, io.ErrUnexpectedEOF + } + ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count + if err != nil { + return nil, io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range + return nil, io.ErrUnexpectedEOF + } + for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range + if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap + return nil, io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length + return nil, io.ErrUnexpectedEOF + } + } + if frameType == 0x03 { + if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count + return nil, io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count + return nil, io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count + return nil, io.ErrUnexpectedEOF + } + } + case 0x06: // CRYPTO frame, we will use this frame + offset, err := quicvarint.Read(buffer) // Field: Offset + if err != nil { + return nil, io.ErrUnexpectedEOF + } + length, err := quicvarint.Read(buffer) // Field: Length + if err != nil || length > uint64(buffer.Len()) { + return nil, io.ErrUnexpectedEOF + } + if cryptoLen < uint(offset+length) { + cryptoLen = uint(offset + length) + } + if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data + return nil, io.ErrUnexpectedEOF + } + case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet + if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code + return nil, io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type + return nil, io.ErrUnexpectedEOF + } + length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length + if err != nil { + return nil, io.ErrUnexpectedEOF + } + if _, err := buffer.ReadBytes(int32(length)); err != nil { // Field: Reason Phrase + return nil, io.ErrUnexpectedEOF + } + default: + // Only above frame types are permitted in initial packet. + // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8 + return nil, errNotQuicInitial + } } - frameData, err := buffer.ReadBytes(int32(dataLen)) - common.Must(err) + tlsHdr := &ptls.SniffHeader{} - err = ptls.ReadClientHello(frameData, tlsHdr) + err = ptls.ReadClientHello(cryptoData[:cryptoLen], tlsHdr) if err != nil { return nil, err } - return &SniffHeader{domain: tlsHdr.Domain()}, nil } diff --git a/common/protocol/server_spec.pb.go b/common/protocol/server_spec.pb.go index e06b8a65966c..da69851848d1 100644 --- a/common/protocol/server_spec.pb.go +++ b/common/protocol/server_spec.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/protocol/server_spec.proto package protocol diff --git a/common/protocol/user.pb.go b/common/protocol/user.pb.go index d1cdf5be57b9..6f063e73af0a 100644 --- a/common/protocol/user.pb.go +++ b/common/protocol/user.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/protocol/user.proto package protocol diff --git a/common/serial/typed_message.go b/common/serial/typed_message.go index e59d1d0da31f..baecc92ecd46 100644 --- a/common/serial/typed_message.go +++ b/common/serial/typed_message.go @@ -1,10 +1,9 @@ package serial import ( - "errors" - "reflect" - - "github.com/golang/protobuf/proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" ) // ToTypedMessage converts a proto Message into TypedMessage. @@ -21,16 +20,17 @@ func ToTypedMessage(message proto.Message) *TypedMessage { // GetMessageType returns the name of this proto Message. func GetMessageType(message proto.Message) string { - return proto.MessageName(message) + return string(message.ProtoReflect().Descriptor().FullName()) } // GetInstance creates a new instance of the message with messageType. func GetInstance(messageType string) (interface{}, error) { - mType := proto.MessageType(messageType) - if mType == nil || mType.Elem() == nil { - return nil, errors.New("Serial: Unknown type: " + messageType) + messageTypeDescriptor := protoreflect.FullName(messageType) + mType, err := protoregistry.GlobalTypes.FindMessageByName(messageTypeDescriptor) + if err != nil { + return nil, err } - return reflect.New(mType.Elem()).Interface(), nil + return mType.New().Interface(), nil } // GetInstance converts current TypedMessage into a proto Message. diff --git a/common/serial/typed_message.pb.go b/common/serial/typed_message.pb.go index bb21f7019945..b03f2aaa469c 100644 --- a/common/serial/typed_message.pb.go +++ b/common/serial/typed_message.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: common/serial/typed_message.proto package serial diff --git a/common/session/context.go b/common/session/context.go index 2959807e535a..329a5a658dae 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -2,10 +2,15 @@ package session import ( "context" + _ "unsafe" + "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/features/routing" ) +//go:linkname IndependentCancelCtx context.newCancelCtx +func IndependentCancelCtx(parent context.Context) context.Context + type sessionKey int const ( @@ -17,6 +22,8 @@ const ( sockoptSessionKey trackedConnectionErrorKey dispatcherKey + timeoutOnlyKey + allowedNetworkKey ) // ContextWithID returns a new context with the given ID. @@ -131,3 +138,25 @@ func DispatcherFromContext(ctx context.Context) routing.Dispatcher { } return nil } + +func ContextWithTimeoutOnly(ctx context.Context, only bool) context.Context { + return context.WithValue(ctx, timeoutOnlyKey, only) +} + +func TimeoutOnlyFromContext(ctx context.Context) bool { + if val, ok := ctx.Value(timeoutOnlyKey).(bool); ok { + return val + } + return false +} + +func ContextWithAllowedNetwork(ctx context.Context, network net.Network) context.Context { + return context.WithValue(ctx, allowedNetworkKey, network) +} + +func AllowedNetworkFromContext(ctx context.Context) net.Network { + if val, ok := ctx.Value(allowedNetworkKey).(net.Network); ok { + return val + } + return net.Network_Unknown +} diff --git a/common/session/session.go b/common/session/session.go index 656a24044e50..4af610157866 100644 --- a/common/session/session.go +++ b/common/session/session.go @@ -42,21 +42,38 @@ type Inbound struct { Gateway net.Destination // Tag of the inbound proxy that handles the connection. Tag string + // Name of the inbound proxy that handles the connection. + Name string // User is the user that authencates for the inbound. May be nil if the protocol allows anounymous traffic. User *protocol.MemoryUser // Conn is actually internet.Connection. May be nil. Conn net.Conn // Timer of the inbound buf copier. May be nil. Timer *signal.ActivityTimer + // CanSpliceCopy is a property for this connection, set by both inbound and outbound + // 1 = can, 2 = after processing protocol info should be able to, 3 = cannot + CanSpliceCopy int +} + +func(i *Inbound) SetCanSpliceCopy(canSpliceCopy int) int { + if canSpliceCopy > i.CanSpliceCopy { + i.CanSpliceCopy = canSpliceCopy + } + return i.CanSpliceCopy } // Outbound is the metadata of an outbound connection. type Outbound struct { // Target address of the outbound connection. - Target net.Destination - RouteTarget net.Destination + OriginalTarget net.Destination + Target net.Destination + RouteTarget net.Destination // Gateway address Gateway net.Address + // Name of the outbound proxy that handles the connection. + Name string + // Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings + Conn net.Conn } // SniffingRequest controls the behavior of content sniffing. diff --git a/common/singbridge/destination.go b/common/singbridge/destination.go new file mode 100644 index 000000000000..98aed2587119 --- /dev/null +++ b/common/singbridge/destination.go @@ -0,0 +1,52 @@ +package singbridge + +import ( + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/net" +) + +func ToNetwork(network string) net.Network { + switch N.NetworkName(network) { + case N.NetworkTCP: + return net.Network_TCP + case N.NetworkUDP: + return net.Network_UDP + default: + return net.Network_Unknown + } +} + +func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination { + // IsFqdn() implicitly checks if the domain name is valid + if socksaddr.IsFqdn() { + return net.Destination{ + Network: network, + Address: net.DomainAddress(socksaddr.Fqdn), + Port: net.Port(socksaddr.Port), + } + } + + // IsIP() implicitly checks if the IP address is valid + if socksaddr.IsIP() { + return net.Destination{ + Network: network, + Address: net.IPAddress(socksaddr.Addr.AsSlice()), + Port: net.Port(socksaddr.Port), + } + } + + return net.Destination{} +} + +func ToSocksaddr(destination net.Destination) M.Socksaddr { + var addr M.Socksaddr + switch destination.Address.Family() { + case net.AddressFamilyDomain: + addr.Fqdn = destination.Address.Domain() + default: + addr.Addr = M.AddrFromIP(destination.Address.IP()) + } + addr.Port = uint16(destination.Port) + return addr +} diff --git a/common/singbridge/dialer.go b/common/singbridge/dialer.go new file mode 100644 index 000000000000..dfc128d863e9 --- /dev/null +++ b/common/singbridge/dialer.go @@ -0,0 +1,59 @@ +package singbridge + +import ( + "context" + "os" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/net/cnc" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/proxy" + "github.com/xtls/xray-core/transport" + "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/pipe" +) + +var _ N.Dialer = (*XrayDialer)(nil) + +type XrayDialer struct { + internet.Dialer +} + +func NewDialer(dialer internet.Dialer) *XrayDialer { + return &XrayDialer{dialer} +} + +func (d *XrayDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network))) +} + +func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +type XrayOutboundDialer struct { + outbound proxy.Outbound + dialer internet.Dialer +} + +func NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOutboundDialer { + return &XrayOutboundDialer{outbound, dialer} +} + +func (d *XrayOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx = session.ContextWithOutbound(context.Background(), &session.Outbound{ + Target: ToDestination(destination, ToNetwork(network)), + }) + opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)} + uplinkReader, uplinkWriter := pipe.New(opts...) + downlinkReader, downlinkWriter := pipe.New(opts...) + conn := cnc.NewConnection(cnc.ConnectionInputMulti(downlinkWriter), cnc.ConnectionOutputMulti(uplinkReader)) + go d.outbound.Process(ctx, &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, d.dialer) + return conn, nil +} + +func (d *XrayOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} diff --git a/common/singbridge/error.go b/common/singbridge/error.go new file mode 100644 index 000000000000..ac9e63517781 --- /dev/null +++ b/common/singbridge/error.go @@ -0,0 +1,10 @@ +package singbridge + +import E "github.com/sagernet/sing/common/exceptions" + +func ReturnError(err error) error { + if E.IsClosedOrCanceled(err) { + return nil + } + return err +} diff --git a/common/singbridge/handler.go b/common/singbridge/handler.go new file mode 100644 index 000000000000..18d4ad71aa0c --- /dev/null +++ b/common/singbridge/handler.go @@ -0,0 +1,51 @@ +package singbridge + +import ( + "context" + "io" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/transport" +) + +var ( + _ N.TCPConnectionHandler = (*Dispatcher)(nil) + _ N.UDPConnectionHandler = (*Dispatcher)(nil) +) + +type Dispatcher struct { + upstream routing.Dispatcher + newErrorFunc func(values ...any) *errors.Error +} + +func NewDispatcher(dispatcher routing.Dispatcher, newErrorFunc func(values ...any) *errors.Error) *Dispatcher { + return &Dispatcher{ + upstream: dispatcher, + newErrorFunc: newErrorFunc, + } +} + +func (d *Dispatcher) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + xConn := NewConn(conn) + return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{ + Reader: xConn, + Writer: xConn, + }) +} + +func (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{ + Reader: buf.NewPacketReader(conn.(io.Reader)), + Writer: buf.NewWriter(conn.(io.Writer)), + }) +} + +func (d *Dispatcher) NewError(ctx context.Context, err error) { + d.newErrorFunc(err).WriteToLog(session.ExportIDToError(ctx)) +} diff --git a/common/singbridge/logger.go b/common/singbridge/logger.go new file mode 100644 index 000000000000..c1702363700a --- /dev/null +++ b/common/singbridge/logger.go @@ -0,0 +1,71 @@ +package singbridge + +import ( + "context" + + "github.com/sagernet/sing/common/logger" + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/session" +) + +var _ logger.ContextLogger = (*XrayLogger)(nil) + +type XrayLogger struct { + newError func(values ...any) *errors.Error +} + +func NewLogger(newErrorFunc func(values ...any) *errors.Error) *XrayLogger { + return &XrayLogger{ + newErrorFunc, + } +} + +func (l *XrayLogger) Trace(args ...any) { +} + +func (l *XrayLogger) Debug(args ...any) { + l.newError(args...).AtDebug().WriteToLog() +} + +func (l *XrayLogger) Info(args ...any) { + l.newError(args...).AtInfo().WriteToLog() +} + +func (l *XrayLogger) Warn(args ...any) { + l.newError(args...).AtWarning().WriteToLog() +} + +func (l *XrayLogger) Error(args ...any) { + l.newError(args...).AtError().WriteToLog() +} + +func (l *XrayLogger) Fatal(args ...any) { +} + +func (l *XrayLogger) Panic(args ...any) { +} + +func (l *XrayLogger) TraceContext(ctx context.Context, args ...any) { +} + +func (l *XrayLogger) DebugContext(ctx context.Context, args ...any) { + l.newError(args...).AtDebug().WriteToLog(session.ExportIDToError(ctx)) +} + +func (l *XrayLogger) InfoContext(ctx context.Context, args ...any) { + l.newError(args...).AtInfo().WriteToLog(session.ExportIDToError(ctx)) +} + +func (l *XrayLogger) WarnContext(ctx context.Context, args ...any) { + l.newError(args...).AtWarning().WriteToLog(session.ExportIDToError(ctx)) +} + +func (l *XrayLogger) ErrorContext(ctx context.Context, args ...any) { + l.newError(args...).AtError().WriteToLog(session.ExportIDToError(ctx)) +} + +func (l *XrayLogger) FatalContext(ctx context.Context, args ...any) { +} + +func (l *XrayLogger) PanicContext(ctx context.Context, args ...any) { +} diff --git a/common/singbridge/packet.go b/common/singbridge/packet.go new file mode 100644 index 000000000000..fef955e76a1c --- /dev/null +++ b/common/singbridge/packet.go @@ -0,0 +1,82 @@ +package singbridge + +import ( + "context" + + B "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport" +) + +func CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error { + conn := &PacketConnWrapper{ + Reader: link.Reader, + Writer: link.Writer, + Dest: destination, + Conn: inboundConn, + } + return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn))) +} + +type PacketConnWrapper struct { + buf.Reader + buf.Writer + net.Conn + Dest net.Destination + cached buf.MultiBuffer +} + +func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) { + if w.cached != nil { + mb, bb := buf.SplitFirst(w.cached) + if bb == nil { + w.cached = nil + } else { + buffer.Write(bb.Bytes()) + w.cached = mb + var destination net.Destination + if bb.UDP != nil { + destination = *bb.UDP + } else { + destination = w.Dest + } + bb.Release() + return ToSocksaddr(destination), nil + } + } + mb, err := w.ReadMultiBuffer() + if err != nil { + return M.Socksaddr{}, err + } + nb, bb := buf.SplitFirst(mb) + if bb == nil { + return M.Socksaddr{}, nil + } else { + buffer.Write(bb.Bytes()) + w.cached = nb + var destination net.Destination + if bb.UDP != nil { + destination = *bb.UDP + } else { + destination = w.Dest + } + bb.Release() + return ToSocksaddr(destination), nil + } +} + +func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error { + vBuf := buf.New() + vBuf.Write(buffer.Bytes()) + endpoint := ToDestination(destination, net.Network_UDP) + vBuf.UDP = &endpoint + return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf}) +} + +func (w *PacketConnWrapper) Close() error { + buf.ReleaseMulti(w.cached) + return nil +} diff --git a/common/singbridge/pipe.go b/common/singbridge/pipe.go new file mode 100644 index 000000000000..d04ebda44e02 --- /dev/null +++ b/common/singbridge/pipe.go @@ -0,0 +1,61 @@ +package singbridge + +import ( + "context" + "io" + "net" + + "github.com/sagernet/sing/common/bufio" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/transport" +) + +func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, serverConn net.Conn) error { + conn := &PipeConnWrapper{ + W: link.Writer, + Conn: inboundConn, + } + if ir, ok := link.Reader.(io.Reader); ok { + conn.R = ir + } else { + conn.R = &buf.BufferedReader{Reader: link.Reader} + } + return ReturnError(bufio.CopyConn(ctx, conn, serverConn)) +} + +type PipeConnWrapper struct { + R io.Reader + W buf.Writer + net.Conn +} + +func (w *PipeConnWrapper) Close() error { + return nil +} + +func (w *PipeConnWrapper) Read(b []byte) (n int, err error) { + return w.R.Read(b) +} + +func (w *PipeConnWrapper) Write(p []byte) (n int, err error) { + n = len(p) + var mb buf.MultiBuffer + pLen := len(p) + for pLen > 0 { + buffer := buf.New() + if pLen > buf.Size { + _, err = buffer.Write(p[:buf.Size]) + p = p[buf.Size:] + } else { + buffer.Write(p) + } + pLen -= int(buffer.Len()) + mb = append(mb, buffer) + } + err = w.W.WriteMultiBuffer(mb) + if err != nil { + n = 0 + buf.ReleaseMulti(mb) + } + return +} diff --git a/common/singbridge/reader.go b/common/singbridge/reader.go new file mode 100644 index 000000000000..1ace1845f031 --- /dev/null +++ b/common/singbridge/reader.go @@ -0,0 +1,66 @@ +package singbridge + +import ( + "time" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net" +) + +var ( + _ buf.Reader = (*Conn)(nil) + _ buf.TimeoutReader = (*Conn)(nil) + _ buf.Writer = (*Conn)(nil) +) + +type Conn struct { + net.Conn + writer N.VectorisedWriter +} + +func NewConn(conn net.Conn) *Conn { + writer, _ := bufio.CreateVectorisedWriter(conn) + return &Conn{ + Conn: conn, + writer: writer, + } +} + +func (c *Conn) ReadMultiBuffer() (buf.MultiBuffer, error) { + buffer, err := buf.ReadBuffer(c.Conn) + if err != nil { + return nil, err + } + return buf.MultiBuffer{buffer}, nil +} + +func (c *Conn) ReadMultiBufferTimeout(duration time.Duration) (buf.MultiBuffer, error) { + err := c.SetReadDeadline(time.Now().Add(duration)) + if err != nil { + return nil, err + } + defer c.SetReadDeadline(time.Time{}) + return c.ReadMultiBuffer() +} + +func (c *Conn) WriteMultiBuffer(bufferList buf.MultiBuffer) error { + defer buf.ReleaseMulti(bufferList) + if c.writer != nil { + bytesList := make([][]byte, len(bufferList)) + for i, buffer := range bufferList { + bytesList[i] = buffer.Bytes() + } + return common.Error(bufio.WriteVectorised(c.writer, bytesList)) + } + // Since this conn is only used by tun, we don't force buffer writes to merge. + for _, buffer := range bufferList { + _, err := c.Conn.Write(buffer.Bytes()) + if err != nil { + return err + } + } + return nil +} diff --git a/common/strmatcher/ac_automaton_matcher.go b/common/strmatcher/ac_automaton_matcher.go index ab7c09bdd310..24be9dac9193 100644 --- a/common/strmatcher/ac_automaton_matcher.go +++ b/common/strmatcher/ac_automaton_matcher.go @@ -225,7 +225,11 @@ func (ac *ACAutomaton) Match(s string) bool { // 2. the match string is through a fail edge. NOT FULL MATCH // 2.1 Through a fail edge, but there exists a valid node. SUBSTR for i := len(s) - 1; i >= 0; i-- { - idx := char2Index[s[i]] + chr := int(s[i]) + if chr >= len(char2Index) { + return false + } + idx := char2Index[chr] fullMatch = fullMatch && ac.trie[node][idx].edgeType node = ac.trie[node][idx].nextNode switch ac.exists[node].matchType { diff --git a/common/strmatcher/strmatcher_test.go b/common/strmatcher/strmatcher_test.go index 2e48c1b7f9ed..408ae6285617 100644 --- a/common/strmatcher/strmatcher_test.go +++ b/common/strmatcher/strmatcher_test.go @@ -217,6 +217,10 @@ func TestACAutomaton(t *testing.T) { pattern: "vvgoogle.com", res: true, }, + { + pattern: "½", + res: false, + }, } for _, test := range cases2Output { if m := ac.Match(test.pattern); m != test.res { @@ -224,7 +228,6 @@ func TestACAutomaton(t *testing.T) { } } } - { cases3Input := []struct { pattern string diff --git a/common/task/task.go b/common/task/task.go index 52b0d44b6320..eeba1dcd71f9 100644 --- a/common/task/task.go +++ b/common/task/task.go @@ -38,6 +38,12 @@ func Run(ctx context.Context, tasks ...func() error) error { }(task) } + /* + if altctx := ctx.Value("altctx"); altctx != nil { + ctx = altctx.(context.Context) + } + */ + for i := 0; i < n; i++ { select { case err := <-done: @@ -48,5 +54,11 @@ func Run(ctx context.Context, tasks ...func() error) error { } } + /* + if cancel := ctx.Value("cancel"); cancel != nil { + cancel.(context.CancelFunc)() + } + */ + return nil } diff --git a/common/xudp/xudp.go b/common/xudp/xudp.go index 80a35e411a92..513247c39a28 100644 --- a/common/xudp/xudp.go +++ b/common/xudp/xudp.go @@ -1,30 +1,79 @@ package xudp import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" "io" + "os" + "strings" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/common/session" + "lukechampine.com/blake3" ) -var addrParser = protocol.NewAddressParser( +var AddrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4), protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain), protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6), protocol.PortThenAddress(), ) -func NewPacketWriter(writer buf.Writer, dest net.Destination) *PacketWriter { +var ( + Show bool + BaseKey []byte +) + +const ( + EnvShow = "XRAY_XUDP_SHOW" + EnvBaseKey = "XRAY_XUDP_BASEKEY" +) + +func init() { + if strings.ToLower(os.Getenv(EnvShow)) == "true" { + Show = true + } + if raw, found := os.LookupEnv(EnvBaseKey); found { + if BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 { + return + } + panic(EnvBaseKey + ": invalid value: " + raw) + } + rand.Read(BaseKey) +} + +func GetGlobalID(ctx context.Context) (globalID [8]byte) { + if cone := ctx.Value("cone"); cone == nil || !cone.(bool) { // cone is nil only in some unit tests + return + } + if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP && + (inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks") { + h := blake3.New(8, BaseKey) + h.Write([]byte(inbound.Source.String())) + copy(globalID[:], h.Sum(nil)) + if Show { + fmt.Printf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID) + } + } + return +} + +func NewPacketWriter(writer buf.Writer, dest net.Destination, globalID [8]byte) *PacketWriter { return &PacketWriter{ - Writer: writer, - Dest: dest, + Writer: writer, + Dest: dest, + GlobalID: globalID, } } type PacketWriter struct { - Writer buf.Writer - Dest net.Destination + Writer buf.Writer + Dest net.Destination + GlobalID [8]byte } func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { @@ -37,19 +86,22 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { } eb := buf.New() - eb.Write([]byte{0, 0, 0, 0}) + eb.Write([]byte{0, 0, 0, 0}) // Meta data length; Mux Session ID if w.Dest.Network == net.Network_UDP { eb.WriteByte(1) // New eb.WriteByte(1) // Opt eb.WriteByte(2) // UDP - addrParser.WriteAddressPort(eb, w.Dest.Address, w.Dest.Port) + AddrParser.WriteAddressPort(eb, w.Dest.Address, w.Dest.Port) + if b.UDP != nil { // make sure it's user's proxy request + eb.Write(w.GlobalID[:]) // no need to check whether it's empty + } w.Dest.Network = net.Network_Unknown } else { eb.WriteByte(2) // Keep - eb.WriteByte(1) + eb.WriteByte(1) // Opt if b.UDP != nil { - eb.WriteByte(2) - addrParser.WriteAddressPort(eb, b.UDP.Address, b.UDP.Port) + eb.WriteByte(2) // UDP + AddrParser.WriteAddressPort(eb, b.UDP.Address, b.UDP.Port) } } l := eb.Len() - 2 @@ -96,9 +148,10 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { discard := false switch b.Byte(2) { case 2: - if l != 4 { + if l > 4 && b.Byte(4) == 2 { // MUST check the flag first b.Advance(5) - addr, port, err := addrParser.ReadAddressPort(nil, b) + // b.Clear() will be called automatically if all data had been read. + addr, port, err := AddrParser.ReadAddressPort(nil, b) if err != nil { b.Release() return nil, err @@ -115,6 +168,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { b.Release() return nil, io.EOF } + b.Clear() // in case there is padding (empty bytes) attached if b.Byte(3) == 1 { if _, err := io.ReadFull(r.Reader, r.cache); err != nil { b.Release() @@ -122,7 +176,6 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { } length := int32(r.cache[0])<<8 | int32(r.cache[1]) if length > 0 { - b.Clear() if _, err := b.ReadFullFrom(r.Reader, length); err != nil { b.Release() return nil, err diff --git a/common/xudp/xudp_test.go b/common/xudp/xudp_test.go new file mode 100644 index 000000000000..78ddfa2728c9 --- /dev/null +++ b/common/xudp/xudp_test.go @@ -0,0 +1,36 @@ +package xudp + +import ( + "testing" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net" +) + +func TestXudpReadWrite(t *testing.T) { + addr, _ := net.ParseDestination("tcp:127.0.0.1:1345") + mb := make(buf.MultiBuffer, 0, 16) + m := buf.MultiBufferContainer{ + MultiBuffer: mb, + } + var arr [8]byte + writer := NewPacketWriter(&m, addr, arr) + + source := make(buf.MultiBuffer, 0, 16) + b := buf.New() + b.WriteByte('a') + b.UDP = &addr + source = append(source, b) + writer.WriteMultiBuffer(source) + + reader := NewPacketReader(&m) + dest, err := reader.ReadMultiBuffer() + common.Must(err) + if dest[0].Byte(0) != 'a' { + t.Error("failed to parse xudp buffer") + } + if dest[0].UDP.Port != 1345 { + t.Error("failed to parse xudp buffer") + } +} diff --git a/core/config.go b/core/config.go index 5892226b0e4c..f4077449006f 100644 --- a/core/config.go +++ b/core/config.go @@ -4,11 +4,11 @@ import ( "io" "strings" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/cmdarg" "github.com/xtls/xray-core/main/confloader" + "google.golang.org/protobuf/proto" ) // ConfigFormat is a configurable format of Xray config file. @@ -57,7 +57,7 @@ func GetFormatByExtension(ext string) string { return "yaml" case "toml": return "toml" - case "json": + case "json", "jsonc": return "json" default: return "" diff --git a/core/config.pb.go b/core/config.pb.go index c6f8c6c70cbd..c36e7fd048d2 100644 --- a/core/config.pb.go +++ b/core/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: core/config.proto package core @@ -42,7 +42,7 @@ type Config struct { // Deprecated. Each inbound and outbound should choose their own transport // config. Date to remove: 2020-01-13 // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in core/config.proto. Transport *global.Config `protobuf:"bytes,5,opt,name=transport,proto3" json:"transport,omitempty"` // Configuration for extensions. The config may not work if corresponding // extension is not loaded into Xray. Xray will ignore such config during @@ -103,7 +103,7 @@ func (x *Config) GetApp() []*serial.TypedMessage { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in core/config.proto. func (x *Config) GetTransport() *global.Config { if x != nil { return x.Transport diff --git a/core/context.go b/core/context.go index d28ac7e328af..50427964a59a 100644 --- a/core/context.go +++ b/core/context.go @@ -26,7 +26,8 @@ func MustFromContext(ctx context.Context) *Instance { return x } -/* toContext returns ctx from the given context, or creates an Instance if the context doesn't find that. +/* + toContext returns ctx from the given context, or creates an Instance if the context doesn't find that. It is unsupported to use this function to create a context that is suitable to invoke Xray's internal component in third party code, you shouldn't use //go:linkname to alias of this function into your own package and @@ -34,7 +35,6 @@ use this function in your third party code. For third party code, usage enabled by creating a context to interact with Xray's internal component is unsupported, and may break at any time. - */ func toContext(ctx context.Context, v *Instance) context.Context { if FromContext(ctx) != v { @@ -43,7 +43,8 @@ func toContext(ctx context.Context, v *Instance) context.Context { return ctx } -/*ToBackgroundDetachedContext create a detached context from another context +/* +ToBackgroundDetachedContext create a detached context from another context Internal API */ func ToBackgroundDetachedContext(ctx context.Context) context.Context { diff --git a/core/core.go b/core/core.go index a999ce7f07f0..e1128775c9ed 100644 --- a/core/core.go +++ b/core/core.go @@ -12,13 +12,19 @@ package core //go:generate go run github.com/xtls/xray-core/common/errors/errorgen import ( + "fmt" "runtime" "github.com/xtls/xray-core/common/serial" ) var ( - version = "1.6.6" + Version_x byte = 1 + Version_y byte = 8 + Version_z byte = 4 +) + +var ( build = "Custom" codename = "Xray, Penetrates Everything." intro = "A unified platform for anti-censorship." @@ -27,7 +33,7 @@ var ( // Version returns Xray's version as a string, in the form of "x.y.z" where x, y and z are numbers. // ".z" part may be omitted in regular releases. func Version() string { - return version + return fmt.Sprintf("%v.%v.%v", Version_x, Version_y, Version_z) } // VersionStatement returns a list of strings representing the full version info. diff --git a/core/functions_test.go b/core/functions_test.go index 2355cc03e5e2..5658de1c145b 100644 --- a/core/functions_test.go +++ b/core/functions_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/golang/protobuf/proto" "github.com/google/go-cmp/cmp" "github.com/xtls/xray-core/app/dispatcher" "github.com/xtls/xray-core/app/proxyman" @@ -18,6 +17,7 @@ import ( "github.com/xtls/xray-core/proxy/freedom" "github.com/xtls/xray-core/testing/servers/tcp" "github.com/xtls/xray-core/testing/servers/udp" + "google.golang.org/protobuf/proto" ) func xor(b []byte) []byte { diff --git a/core/xray_test.go b/core/xray_test.go index 59de0f46d56d..a4a8ec9f0b32 100644 --- a/core/xray_test.go +++ b/core/xray_test.go @@ -3,7 +3,6 @@ package core_test import ( "testing" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/dispatcher" "github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/common" @@ -19,6 +18,7 @@ import ( "github.com/xtls/xray-core/proxy/vmess" "github.com/xtls/xray-core/proxy/vmess/outbound" "github.com/xtls/xray-core/testing/servers/tcp" + "google.golang.org/protobuf/proto" ) func TestXrayDependency(t *testing.T) { diff --git a/features/extension/observatory.go b/features/extension/observatory.go index eb51a61bdb95..8e8710306956 100644 --- a/features/extension/observatory.go +++ b/features/extension/observatory.go @@ -3,8 +3,8 @@ package extension import ( "context" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/features" + "google.golang.org/protobuf/proto" ) type Observatory interface { diff --git a/go.mod b/go.mod index 008d0c9c2c17..e98a6f116a99 100644 --- a/go.mod +++ b/go.mod @@ -1,61 +1,59 @@ module github.com/xtls/xray-core -go 1.19 +go 1.21 require ( - github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 + github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/golang/mock v1.6.0 - github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.9 github.com/gorilla/websocket v1.5.0 - github.com/lucas-clemente/quic-go v0.31.1 - github.com/marten-seemann/qtls-go1-18 v0.1.3 - github.com/miekg/dns v1.1.50 + github.com/miekg/dns v1.1.56 github.com/pelletier/go-toml v1.9.5 - github.com/pires/go-proxyproto v0.6.2 - github.com/refraction-networking/utls v1.2.0 - github.com/sagernet/sing v0.1.1 - github.com/sagernet/sing-shadowsocks v0.1.0 + github.com/pires/go-proxyproto v0.7.0 + github.com/quic-go/quic-go v0.38.1 + github.com/refraction-networking/utls v1.4.3 + github.com/sagernet/sing v0.2.9 + github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.4 github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e - github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 - go.starlark.net v0.0.0-20221205180719-3fd0dac74452 - golang.org/x/crypto v0.4.0 - golang.org/x/net v0.4.0 - golang.org/x/sync v0.1.0 - golang.org/x/sys v0.3.0 - google.golang.org/grpc v1.51.0 - google.golang.org/protobuf v1.28.1 - gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c + github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 + go4.org/netipx v0.0.0-20230824141953-6213f710f925 + golang.org/x/crypto v0.13.0 + golang.org/x/net v0.15.0 + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.12.0 + google.golang.org/grpc v1.58.1 + google.golang.org/protobuf v1.31.0 + gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 h12.io/socks v1.0.3 + lukechampine.com/blake3 v1.2.1 ) require ( - github.com/andybalholm/brotli v1.0.4 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect - github.com/klauspost/compress v1.15.13 // indirect - github.com/klauspost/cpuid/v2 v2.2.2 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect - github.com/onsi/ginkgo/v2 v2.6.0 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/text v0.5.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.4.0 // indirect - google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.1.7 // indirect ) diff --git a/go.sum b/go.sum index c1d896f67334..d4b097b9fc9e 100644 --- a/go.sum +++ b/go.sum @@ -8,19 +8,14 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -28,20 +23,21 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= +github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -51,25 +47,13 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -77,8 +61,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= -github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -92,66 +76,57 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= -github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.2 h1:xPMwiykqNK9VK0NYC3+jTMYv9I6Vl3YdjZgPZKG3zO0= -github.com/klauspost/cpuid/v2 v2.2.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= -github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= -github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE= -github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= -github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/refraction-networking/utls v1.2.0 h1:U5f8wkij2NVinfLuJdFP3gCMwIHs+EzvhxmYdXgiapo= -github.com/refraction-networking/utls v1.2.0/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE= +github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4= +github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM= +github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQfPtemEqLBXkML1b0bo= 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sagernet/sing v0.1.1 h1:wtCGreL9UNtoLcDvSLoZQWf1dtqmLWogbcwRAD9nz4E= -github.com/sagernet/sing v0.1.1/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4= -github.com/sagernet/sing-shadowsocks v0.1.0 h1:cDmmOkA11fzVdhyCZQEeI3ozQz+59rj8+rqPb91xux4= -github.com/sagernet/sing-shadowsocks v0.1.0/go.mod h1:O5LtOs8Ivw686FqLpO0Zu+A0ROVE15VeqEK3yDRRAms= +github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo= +github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= +github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= +github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= @@ -182,46 +157,40 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5k github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M= -github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY= +github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 h1:T+YCYGfFdzyaKTDCdZn/hEiKvsw6yUfd+e4hze0rCUw= +github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.starlark.net v0.0.0-20221205180719-3fd0dac74452 h1:JZtNuL6LPB+scU5yaQ6hqRlJFRiddZm2FwRt2AQqtHA= -go.starlark.net v0.0.0-20221205180719-3fd0dac74452/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= +go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 h1:T8EldfGCcveFMewH5xAYxxoX3PSQMrsechlUGVFlQBU= -golang.org/x/exp v0.0.0-20221211140036-ad323defaf05/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -229,14 +198,12 @@ golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -248,8 +215,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -258,22 +225,16 @@ golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -283,13 +244,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -306,33 +264,21 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= -google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= +google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -343,15 +289,14 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4= -gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= +gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 h1:tE44CyJgxEGzoPtHs9GI7ddKdgEGCREQBP54AmaVM+I= +gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744/go.mod h1:lYEMhXbxgudVhALYsMQrBaUAjM3NMinh8mKL1CJv7rc= h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo= h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= -lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/infra/conf/blackhole.go b/infra/conf/blackhole.go index c1551de1b4c3..5044a8c02205 100644 --- a/infra/conf/blackhole.go +++ b/infra/conf/blackhole.go @@ -3,9 +3,9 @@ package conf import ( "encoding/json" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/blackhole" + "google.golang.org/protobuf/proto" ) type NoneResponse struct{} diff --git a/infra/conf/buildable.go b/infra/conf/buildable.go index 1d01cd66a5bc..967e9740d792 100644 --- a/infra/conf/buildable.go +++ b/infra/conf/buildable.go @@ -1,6 +1,6 @@ package conf -import "github.com/golang/protobuf/proto" +import "google.golang.org/protobuf/proto" type Buildable interface { Build() (proto.Message, error) diff --git a/infra/conf/common.go b/infra/conf/common.go index 486e78246fa7..f8f560562427 100644 --- a/infra/conf/common.go +++ b/infra/conf/common.go @@ -45,6 +45,9 @@ func (v *Address) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &rawStr); err != nil { return newError("invalid address: ", string(data)).Base(err) } + if strings.HasPrefix(rawStr, "env:") { + rawStr = os.Getenv(rawStr[4:]) + } v.Address = net.ParseAddress(rawStr) return nil @@ -115,8 +118,7 @@ func parseIntPort(data []byte) (net.Port, error) { func parseStringPort(s string) (net.Port, net.Port, error) { if strings.HasPrefix(s, "env:") { - s = s[4:] - s = os.Getenv(s) + s = os.Getenv(s[4:]) } pair := strings.SplitN(s, "-", 2) diff --git a/infra/conf/dns_proxy.go b/infra/conf/dns_proxy.go index 90a5d65e3b5c..13a445972b6e 100644 --- a/infra/conf/dns_proxy.go +++ b/infra/conf/dns_proxy.go @@ -1,16 +1,17 @@ package conf import ( - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/proxy/dns" + "google.golang.org/protobuf/proto" ) type DNSOutboundConfig struct { - Network Network `json:"network"` - Address *Address `json:"address"` - Port uint16 `json:"port"` - UserLevel uint32 `json:"userLevel"` + Network Network `json:"network"` + Address *Address `json:"address"` + Port uint16 `json:"port"` + UserLevel uint32 `json:"userLevel"` + NonIPQuery string `json:"nonIPQuery"` } func (c *DNSOutboundConfig) Build() (proto.Message, error) { @@ -24,5 +25,13 @@ func (c *DNSOutboundConfig) Build() (proto.Message, error) { if c.Address != nil { config.Server.Address = c.Address.Build() } + switch c.NonIPQuery { + case "": + c.NonIPQuery = "drop" + case "drop", "skip": + default: + return nil, newError(`unknown "nonIPQuery": `, c.NonIPQuery) + } + config.Non_IPQuery = c.NonIPQuery return config, nil } diff --git a/infra/conf/dns_proxy_test.go b/infra/conf/dns_proxy_test.go index 805ac323a2f9..5c5dfecddd9c 100644 --- a/infra/conf/dns_proxy_test.go +++ b/infra/conf/dns_proxy_test.go @@ -27,6 +27,7 @@ func TestDnsProxyConfig(t *testing.T) { Address: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})), Port: 53, }, + Non_IPQuery: "drop", }, }, }) diff --git a/infra/conf/dns_test.go b/infra/conf/dns_test.go index c97c5bea74d7..1c370cd9a370 100644 --- a/infra/conf/dns_test.go +++ b/infra/conf/dns_test.go @@ -6,7 +6,6 @@ import ( "path/filepath" "testing" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/dns" "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common" @@ -14,6 +13,7 @@ import ( "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform/filesystem" . "github.com/xtls/xray-core/infra/conf" + "google.golang.org/protobuf/proto" ) func init() { diff --git a/infra/conf/dokodemo.go b/infra/conf/dokodemo.go index 03a21d71192f..2a4b74ea4433 100644 --- a/infra/conf/dokodemo.go +++ b/infra/conf/dokodemo.go @@ -1,8 +1,8 @@ package conf import ( - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/proxy/dokodemo" + "google.golang.org/protobuf/proto" ) type DokodemoConfig struct { diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index 60dfd5b87e00..21f0616aacbb 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -2,19 +2,27 @@ package conf import ( "net" + "strconv" "strings" - "github.com/golang/protobuf/proto" v2net "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/proxy/freedom" + "google.golang.org/protobuf/proto" ) type FreedomConfig struct { - DomainStrategy string `json:"domainStrategy"` - Timeout *uint32 `json:"timeout"` - Redirect string `json:"redirect"` - UserLevel uint32 `json:"userLevel"` + DomainStrategy string `json:"domainStrategy"` + Timeout *uint32 `json:"timeout"` + Redirect string `json:"redirect"` + UserLevel uint32 `json:"userLevel"` + Fragment *Fragment `json:"fragment"` +} + +type Fragment struct { + Packets string `json:"packets"` + Length string `json:"length"` + Interval string `json:"interval"` } // Build implements Buildable @@ -30,6 +38,93 @@ func (c *FreedomConfig) Build() (proto.Message, error) { config.DomainStrategy = freedom.Config_USE_IP6 } + if c.Fragment != nil { + config.Fragment = new(freedom.Fragment) + var err, err2 error + + switch strings.ToLower(c.Fragment.Packets) { + case "tlshello": + // TLS Hello Fragmentation (into multiple handshake messages) + config.Fragment.PacketsFrom = 0 + config.Fragment.PacketsTo = 1 + case "": + // TCP Segmentation (all packets) + config.Fragment.PacketsFrom = 0 + config.Fragment.PacketsTo = 0 + default: + // TCP Segmentation (range) + packetsFromTo := strings.Split(c.Fragment.Packets, "-") + if len(packetsFromTo) == 2 { + config.Fragment.PacketsFrom, err = strconv.ParseUint(packetsFromTo[0], 10, 64) + config.Fragment.PacketsTo, err2 = strconv.ParseUint(packetsFromTo[1], 10, 64) + } else { + config.Fragment.PacketsFrom, err = strconv.ParseUint(packetsFromTo[0], 10, 64) + config.Fragment.PacketsTo = config.Fragment.PacketsFrom + } + if err != nil { + return nil, newError("Invalid PacketsFrom").Base(err) + } + if err2 != nil { + return nil, newError("Invalid PacketsTo").Base(err2) + } + if config.Fragment.PacketsFrom > config.Fragment.PacketsTo { + config.Fragment.PacketsFrom, config.Fragment.PacketsTo = config.Fragment.PacketsTo, config.Fragment.PacketsFrom + } + if config.Fragment.PacketsFrom == 0 { + return nil, newError("PacketsFrom can't be 0") + } + } + + { + if c.Fragment.Length == "" { + return nil, newError("Length can't be empty") + } + lengthMinMax := strings.Split(c.Fragment.Length, "-") + if len(lengthMinMax) == 2 { + config.Fragment.LengthMin, err = strconv.ParseUint(lengthMinMax[0], 10, 64) + config.Fragment.LengthMax, err2 = strconv.ParseUint(lengthMinMax[1], 10, 64) + } else { + config.Fragment.LengthMin, err = strconv.ParseUint(lengthMinMax[0], 10, 64) + config.Fragment.LengthMax = config.Fragment.LengthMin + } + if err != nil { + return nil, newError("Invalid LengthMin").Base(err) + } + if err2 != nil { + return nil, newError("Invalid LengthMax").Base(err2) + } + if config.Fragment.LengthMin > config.Fragment.LengthMax { + config.Fragment.LengthMin, config.Fragment.LengthMax = config.Fragment.LengthMax, config.Fragment.LengthMin + } + if config.Fragment.LengthMin == 0 { + return nil, newError("LengthMin can't be 0") + } + } + + { + if c.Fragment.Interval == "" { + return nil, newError("Interval can't be empty") + } + intervalMinMax := strings.Split(c.Fragment.Interval, "-") + if len(intervalMinMax) == 2 { + config.Fragment.IntervalMin, err = strconv.ParseUint(intervalMinMax[0], 10, 64) + config.Fragment.IntervalMax, err2 = strconv.ParseUint(intervalMinMax[1], 10, 64) + } else { + config.Fragment.IntervalMin, err = strconv.ParseUint(intervalMinMax[0], 10, 64) + config.Fragment.IntervalMax = config.Fragment.IntervalMin + } + if err != nil { + return nil, newError("Invalid IntervalMin").Base(err) + } + if err2 != nil { + return nil, newError("Invalid IntervalMax").Base(err2) + } + if config.Fragment.IntervalMin > config.Fragment.IntervalMax { + config.Fragment.IntervalMin, config.Fragment.IntervalMax = config.Fragment.IntervalMax, config.Fragment.IntervalMin + } + } + } + if c.Timeout != nil { config.Timeout = *c.Timeout } diff --git a/infra/conf/general_test.go b/infra/conf/general_test.go index 1de1109c9510..4d23b3b5e508 100644 --- a/infra/conf/general_test.go +++ b/infra/conf/general_test.go @@ -4,9 +4,9 @@ import ( "encoding/json" "testing" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common" . "github.com/xtls/xray-core/infra/conf" + "google.golang.org/protobuf/proto" ) func loadJSON(creator func() Buildable) func(string) (proto.Message, error) { diff --git a/infra/conf/grpc.go b/infra/conf/grpc.go index 3813e40d9bf8..184475129829 100644 --- a/infra/conf/grpc.go +++ b/infra/conf/grpc.go @@ -1,8 +1,8 @@ package conf import ( - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/transport/internet/grpc" + "google.golang.org/protobuf/proto" ) type GRPCConfig struct { @@ -12,6 +12,7 @@ type GRPCConfig struct { HealthCheckTimeout int32 `json:"health_check_timeout"` PermitWithoutStream bool `json:"permit_without_stream"` InitialWindowsSize int32 `json:"initial_windows_size"` + UserAgent string `json:"user_agent"` } func (g *GRPCConfig) Build() (proto.Message, error) { @@ -33,5 +34,6 @@ func (g *GRPCConfig) Build() (proto.Message, error) { HealthCheckTimeout: g.HealthCheckTimeout, PermitWithoutStream: g.PermitWithoutStream, InitialWindowsSize: g.InitialWindowsSize, + UserAgent: g.UserAgent, }, nil } diff --git a/infra/conf/http.go b/infra/conf/http.go index ddeaa69e2237..b391dbf2586b 100644 --- a/infra/conf/http.go +++ b/infra/conf/http.go @@ -3,10 +3,10 @@ package conf import ( "encoding/json" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/http" + "google.golang.org/protobuf/proto" ) type HTTPAccount struct { diff --git a/infra/conf/loopback.go b/infra/conf/loopback.go index b6d83178a014..87d349cee8a7 100644 --- a/infra/conf/loopback.go +++ b/infra/conf/loopback.go @@ -1,8 +1,8 @@ package conf import ( - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/proxy/loopback" + "google.golang.org/protobuf/proto" ) type LoopbackConfig struct { diff --git a/infra/conf/mtproto.go b/infra/conf/mtproto.go deleted file mode 100644 index 88b02af59f59..000000000000 --- a/infra/conf/mtproto.go +++ /dev/null @@ -1,67 +0,0 @@ -package conf - -import ( - "encoding/hex" - "encoding/json" - - "github.com/golang/protobuf/proto" - "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/serial" - "github.com/xtls/xray-core/proxy/mtproto" -) - -type MTProtoAccount struct { - Secret string `json:"secret"` -} - -// Build implements Buildable -func (a *MTProtoAccount) Build() (*mtproto.Account, error) { - if len(a.Secret) != 32 { - return nil, newError("MTProto secret must have 32 chars") - } - secret, err := hex.DecodeString(a.Secret) - if err != nil { - return nil, newError("failed to decode secret: ", a.Secret).Base(err) - } - return &mtproto.Account{ - Secret: secret, - }, nil -} - -type MTProtoServerConfig struct { - Users []json.RawMessage `json:"users"` -} - -func (c *MTProtoServerConfig) Build() (proto.Message, error) { - config := &mtproto.ServerConfig{} - - if len(c.Users) == 0 { - return nil, newError("zero MTProto users configured.") - } - config.User = make([]*protocol.User, len(c.Users)) - for idx, rawData := range c.Users { - user := new(protocol.User) - if err := json.Unmarshal(rawData, user); err != nil { - return nil, newError("invalid MTProto user").Base(err) - } - account := new(MTProtoAccount) - if err := json.Unmarshal(rawData, account); err != nil { - return nil, newError("invalid MTProto user").Base(err) - } - accountProto, err := account.Build() - if err != nil { - return nil, newError("failed to parse MTProto user").Base(err) - } - user.Account = serial.ToTypedMessage(accountProto) - config.User[idx] = user - } - - return config, nil -} - -type MTProtoClientConfig struct{} - -func (c *MTProtoClientConfig) Build() (proto.Message, error) { - config := new(mtproto.ClientConfig) - return config, nil -} diff --git a/infra/conf/mtproto_test.go b/infra/conf/mtproto_test.go deleted file mode 100644 index f44cb19a65ef..000000000000 --- a/infra/conf/mtproto_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package conf_test - -import ( - "testing" - - "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/serial" - . "github.com/xtls/xray-core/infra/conf" - "github.com/xtls/xray-core/proxy/mtproto" -) - -func TestMTProtoServerConfig(t *testing.T) { - creator := func() Buildable { - return new(MTProtoServerConfig) - } - - runMultiTestCase(t, []TestCase{ - { - Input: `{ - "users": [{ - "email": "love@example.com", - "level": 1, - "secret": "b0cbcef5a486d9636472ac27f8e11a9d" - }] - }`, - Parser: loadJSON(creator), - Output: &mtproto.ServerConfig{ - User: []*protocol.User{ - { - Email: "love@example.com", - Level: 1, - Account: serial.ToTypedMessage(&mtproto.Account{ - Secret: []byte{176, 203, 206, 245, 164, 134, 217, 99, 100, 114, 172, 39, 248, 225, 26, 157}, - }), - }, - }, - }, - }, - }) -} diff --git a/infra/conf/observatory.go b/infra/conf/observatory.go index 25a4d52cbe6d..f51787a4cfaf 100644 --- a/infra/conf/observatory.go +++ b/infra/conf/observatory.go @@ -1,9 +1,9 @@ package conf import ( - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/observatory" "github.com/xtls/xray-core/infra/conf/cfgcommon/duration" + "google.golang.org/protobuf/proto" ) type ObservatoryConfig struct { diff --git a/infra/conf/reverse.go b/infra/conf/reverse.go index 1b42a63a097e..f44c99921e64 100644 --- a/infra/conf/reverse.go +++ b/infra/conf/reverse.go @@ -1,8 +1,8 @@ package conf import ( - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/reverse" + "google.golang.org/protobuf/proto" ) type BridgeConfig struct { diff --git a/infra/conf/router.go b/infra/conf/router.go index e10317bd31a4..a9f57cd6395b 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -6,10 +6,10 @@ import ( "strconv" "strings" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform/filesystem" + "google.golang.org/protobuf/proto" ) type RouterRulesConfig struct { @@ -245,6 +245,23 @@ func loadSite(file, code string) ([]*router.Domain, error) { return SiteCache[index].Domain, nil } +func DecodeVarint(buf []byte) (x uint64, n int) { + for shift := uint(0); shift < 64; shift += 7 { + if n >= len(buf) { + return 0, 0 + } + b := uint64(buf[n]) + n++ + x |= (b & 0x7F) << shift + if (b & 0x80) == 0 { + return x, n + } + } + + // The number is too large to represent in a 64-bit value. + return 0, 0 +} + func find(data, code []byte) []byte { codeL := len(code) if codeL == 0 { @@ -255,7 +272,7 @@ func find(data, code []byte) []byte { if dataL < 2 { return nil } - x, y := proto.DecodeVarint(data[1:]) + x, y := DecodeVarint(data[1:]) if x == 0 && y == 0 { return nil } @@ -504,17 +521,17 @@ func ToCidrList(ips StringList) ([]*router.GeoIP, error) { func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { type RawFieldRule struct { RouterRule - Domain *StringList `json:"domain"` - Domains *StringList `json:"domains"` - IP *StringList `json:"ip"` - Port *PortList `json:"port"` - Network *NetworkList `json:"network"` - SourceIP *StringList `json:"source"` - SourcePort *PortList `json:"sourcePort"` - User *StringList `json:"user"` - InboundTag *StringList `json:"inboundTag"` - Protocols *StringList `json:"protocol"` - Attributes string `json:"attrs"` + Domain *StringList `json:"domain"` + Domains *StringList `json:"domains"` + IP *StringList `json:"ip"` + Port *PortList `json:"port"` + Network *NetworkList `json:"network"` + SourceIP *StringList `json:"source"` + SourcePort *PortList `json:"sourcePort"` + User *StringList `json:"user"` + InboundTag *StringList `json:"inboundTag"` + Protocols *StringList `json:"protocol"` + Attributes map[string]string `json:"attrs"` } rawFieldRule := new(RawFieldRule) err := json.Unmarshal(msg, rawFieldRule) diff --git a/infra/conf/router_test.go b/infra/conf/router_test.go index 98e31b3a7423..63dc8e8d385f 100644 --- a/infra/conf/router_test.go +++ b/infra/conf/router_test.go @@ -7,13 +7,13 @@ import ( "testing" _ "unsafe" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform/filesystem" . "github.com/xtls/xray-core/infra/conf" + "google.golang.org/protobuf/proto" ) func init() { diff --git a/infra/conf/shadowsocks.go b/infra/conf/shadowsocks.go index d35aa3ab26e5..3dcbb2f3c11e 100644 --- a/infra/conf/shadowsocks.go +++ b/infra/conf/shadowsocks.go @@ -3,13 +3,13 @@ package conf import ( "strings" - "github.com/golang/protobuf/proto" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" C "github.com/sagernet/sing/common" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/shadowsocks" "github.com/xtls/xray-core/proxy/shadowsocks_2022" + "google.golang.org/protobuf/proto" ) func cipherFromString(c string) shadowsocks.CipherType { @@ -107,7 +107,7 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) { config.Email = v.Email return config, nil } - + if v.Cipher == "" { return nil, newError("shadowsocks 2022 (multi-user): missing server method") } @@ -120,7 +120,7 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) { config.Method = v.Cipher config.Key = v.Password config.Network = v.NetworkList.Build() - + for _, user := range v.Users { if user.Cipher != "" { return nil, newError("shadowsocks 2022 (multi-user): users must have empty method") @@ -145,24 +145,25 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) { return nil, newError("shadowsocks 2022 (relay): all users must have relay address") } config.Destinations = append(config.Destinations, &shadowsocks_2022.RelayDestination{ - Key: user.Password, - Email: user.Email, + Key: user.Password, + Email: user.Email, Address: user.Address.Build(), - Port: uint32(user.Port), + Port: uint32(user.Port), }) } return config, nil } type ShadowsocksServerTarget struct { - Address *Address `json:"address"` - Port uint16 `json:"port"` - Cipher string `json:"method"` - Password string `json:"password"` - Email string `json:"email"` - Level byte `json:"level"` - IVCheck bool `json:"ivCheck"` - UoT bool `json:"uot"` + Address *Address `json:"address"` + Port uint16 `json:"port"` + Cipher string `json:"method"` + Password string `json:"password"` + Email string `json:"email"` + Level byte `json:"level"` + IVCheck bool `json:"ivCheck"` + UoT bool `json:"uot"` + UoTVersion int `json:"uotVersion"` } type ShadowsocksClientConfig struct { @@ -193,6 +194,7 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) { config.Method = server.Cipher config.Key = server.Password config.UdpOverTcp = server.UoT + config.UdpOverTcpVersion = uint32(server.UoTVersion) return config, nil } } diff --git a/infra/conf/socks.go b/infra/conf/socks.go index 490c24bde652..61f6fae25595 100644 --- a/infra/conf/socks.go +++ b/infra/conf/socks.go @@ -4,10 +4,10 @@ import ( "encoding/json" "strings" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/socks" + "google.golang.org/protobuf/proto" ) type SocksAccount struct { diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go index 703a13662cef..db9f4a726e45 100644 --- a/infra/conf/transport_authenticators.go +++ b/infra/conf/transport_authenticators.go @@ -3,7 +3,7 @@ package conf import ( "sort" - "github.com/golang/protobuf/proto" + "github.com/xtls/xray-core/transport/internet/headers/dns" "github.com/xtls/xray-core/transport/internet/headers/http" "github.com/xtls/xray-core/transport/internet/headers/noop" "github.com/xtls/xray-core/transport/internet/headers/srtp" @@ -11,6 +11,7 @@ import ( "github.com/xtls/xray-core/transport/internet/headers/utp" "github.com/xtls/xray-core/transport/internet/headers/wechat" "github.com/xtls/xray-core/transport/internet/headers/wireguard" + "google.golang.org/protobuf/proto" ) type NoOpAuthenticator struct{} @@ -49,6 +50,19 @@ func (WireguardAuthenticator) Build() (proto.Message, error) { return new(wireguard.WireguardConfig), nil } +type DNSAuthenticator struct { + Domain string `json:"domain"` +} + +func (v *DNSAuthenticator) Build() (proto.Message, error) { + config := new(dns.Config) + config.Domain = "www.baidu.com" + if len(v.Domain) > 0 { + config.Domain = v.Domain + } + return config, nil +} + type DTLSAuthenticator struct{} func (DTLSAuthenticator) Build() (proto.Message, error) { diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index efbe40756c59..9e10feed232e 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -2,13 +2,16 @@ package conf import ( "encoding/base64" + "encoding/hex" "encoding/json" "math" "net/url" + "runtime" "strconv" "strings" + "syscall" - "github.com/golang/protobuf/proto" + "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform/filesystem" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" @@ -18,10 +21,11 @@ import ( "github.com/xtls/xray-core/transport/internet/http" "github.com/xtls/xray-core/transport/internet/kcp" "github.com/xtls/xray-core/transport/internet/quic" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/tcp" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/websocket" - "github.com/xtls/xray-core/transport/internet/xtls" + "google.golang.org/protobuf/proto" ) var ( @@ -32,6 +36,7 @@ var ( "wechat-video": func() interface{} { return new(WechatVideoAuthenticator) }, "dtls": func() interface{} { return new(DTLSAuthenticator) }, "wireguard": func() interface{} { return new(WireguardAuthenticator) }, + "dns": func() interface{} { return new(DNSAuthenticator) }, }, "type", "") tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{ @@ -338,19 +343,20 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) { } type TLSConfig struct { - Insecure bool `json:"allowInsecure"` - Certs []*TLSCertConfig `json:"certificates"` - ServerName string `json:"serverName"` - ALPN *StringList `json:"alpn"` - EnableSessionResumption bool `json:"enableSessionResumption"` - DisableSystemRoot bool `json:"disableSystemRoot"` - MinVersion string `json:"minVersion"` - MaxVersion string `json:"maxVersion"` - CipherSuites string `json:"cipherSuites"` - PreferServerCipherSuites bool `json:"preferServerCipherSuites"` - Fingerprint string `json:"fingerprint"` - RejectUnknownSNI bool `json:"rejectUnknownSni"` - PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"` + Insecure bool `json:"allowInsecure"` + Certs []*TLSCertConfig `json:"certificates"` + ServerName string `json:"serverName"` + ALPN *StringList `json:"alpn"` + EnableSessionResumption bool `json:"enableSessionResumption"` + DisableSystemRoot bool `json:"disableSystemRoot"` + MinVersion string `json:"minVersion"` + MaxVersion string `json:"maxVersion"` + CipherSuites string `json:"cipherSuites"` + PreferServerCipherSuites bool `json:"preferServerCipherSuites"` + Fingerprint string `json:"fingerprint"` + RejectUnknownSNI bool `json:"rejectUnknownSni"` + PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"` + PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"` } // Build implements Buildable. @@ -379,6 +385,9 @@ func (c *TLSConfig) Build() (proto.Message, error) { config.CipherSuites = c.CipherSuites config.PreferServerCipherSuites = c.PreferServerCipherSuites config.Fingerprint = strings.ToLower(c.Fingerprint) + if config.Fingerprint != "" && tls.GetFingerprint(config.Fingerprint) == nil { + return nil, newError(`unknown fingerprint: `, config.Fingerprint) + } config.RejectUnknownSni = c.RejectUnknownSNI if c.PinnedPeerCertificateChainSha256 != nil { @@ -392,111 +401,184 @@ func (c *TLSConfig) Build() (proto.Message, error) { } } - return config, nil -} - -type XTLSCertConfig struct { - CertFile string `json:"certificateFile"` - CertStr []string `json:"certificate"` - KeyFile string `json:"keyFile"` - KeyStr []string `json:"key"` - Usage string `json:"usage"` - OcspStapling uint64 `json:"ocspStapling"` - OneTimeLoading bool `json:"oneTimeLoading"` -} - -// Build implements Buildable. -func (c *XTLSCertConfig) Build() (*xtls.Certificate, error) { - certificate := new(xtls.Certificate) - cert, err := readFileOrString(c.CertFile, c.CertStr) - if err != nil { - return nil, newError("failed to parse certificate").Base(err) - } - certificate.Certificate = cert - certificate.CertificatePath = c.CertFile - - if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 { - key, err := readFileOrString(c.KeyFile, c.KeyStr) - if err != nil { - return nil, newError("failed to parse key").Base(err) + if c.PinnedPeerCertificatePublicKeySha256 != nil { + config.PinnedPeerCertificatePublicKeySha256 = [][]byte{} + for _, v := range *c.PinnedPeerCertificatePublicKeySha256 { + hashValue, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return nil, err + } + config.PinnedPeerCertificatePublicKeySha256 = append(config.PinnedPeerCertificatePublicKeySha256, hashValue) } - certificate.Key = key - certificate.KeyPath = c.KeyFile } - switch strings.ToLower(c.Usage) { - case "encipherment": - certificate.Usage = xtls.Certificate_ENCIPHERMENT - case "verify": - certificate.Usage = xtls.Certificate_AUTHORITY_VERIFY - case "issue": - certificate.Usage = xtls.Certificate_AUTHORITY_ISSUE - default: - certificate.Usage = xtls.Certificate_ENCIPHERMENT - } - if certificate.KeyPath == "" && certificate.CertificatePath == "" { - certificate.OneTimeLoading = true - } else { - certificate.OneTimeLoading = c.OneTimeLoading - } - certificate.OcspStapling = c.OcspStapling - - return certificate, nil + return config, nil } -type XTLSConfig struct { - Insecure bool `json:"allowInsecure"` - Certs []*XTLSCertConfig `json:"certificates"` - ServerName string `json:"serverName"` - ALPN *StringList `json:"alpn"` - EnableSessionResumption bool `json:"enableSessionResumption"` - DisableSystemRoot bool `json:"disableSystemRoot"` - MinVersion string `json:"minVersion"` - MaxVersion string `json:"maxVersion"` - CipherSuites string `json:"cipherSuites"` - PreferServerCipherSuites bool `json:"preferServerCipherSuites"` - RejectUnknownSNI bool `json:"rejectUnknownSni"` - PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"` +type REALITYConfig struct { + Show bool `json:"show"` + Dest json.RawMessage `json:"dest"` + Type string `json:"type"` + Xver uint64 `json:"xver"` + ServerNames []string `json:"serverNames"` + PrivateKey string `json:"privateKey"` + MinClientVer string `json:"minClientVer"` + MaxClientVer string `json:"maxClientVer"` + MaxTimeDiff uint64 `json:"maxTimeDiff"` + ShortIds []string `json:"shortIds"` + + Fingerprint string `json:"fingerprint"` + ServerName string `json:"serverName"` + PublicKey string `json:"publicKey"` + ShortId string `json:"shortId"` + SpiderX string `json:"spiderX"` } -// Build implements Buildable. -func (c *XTLSConfig) Build() (proto.Message, error) { - config := new(xtls.Config) - config.Certificate = make([]*xtls.Certificate, len(c.Certs)) - for idx, certConf := range c.Certs { - cert, err := certConf.Build() - if err != nil { - return nil, err +func (c *REALITYConfig) Build() (proto.Message, error) { + config := new(reality.Config) + config.Show = c.Show + var err error + if c.Dest != nil { + var i uint16 + var s string + if err = json.Unmarshal(c.Dest, &i); err == nil { + s = strconv.Itoa(int(i)) + } else { + _ = json.Unmarshal(c.Dest, &s) + } + if c.Type == "" && s != "" { + switch s[0] { + case '@', '/': + c.Type = "unix" + if s[0] == '@' && len(s) > 1 && s[1] == '@' && (runtime.GOOS == "linux" || runtime.GOOS == "android") { + fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy + copy(fullAddr, s[1:]) + s = string(fullAddr) + } + default: + if _, err = strconv.Atoi(s); err == nil { + s = "127.0.0.1:" + s + } + if _, _, err = net.SplitHostPort(s); err == nil { + c.Type = "tcp" + } + } } - config.Certificate[idx] = cert - } - serverName := c.ServerName - config.AllowInsecure = c.Insecure - if len(c.ServerName) > 0 { - config.ServerName = serverName - } - if c.ALPN != nil && len(*c.ALPN) > 0 { - config.NextProtocol = []string(*c.ALPN) - } - config.EnableSessionResumption = c.EnableSessionResumption - config.DisableSystemRoot = c.DisableSystemRoot - config.MinVersion = c.MinVersion - config.MaxVersion = c.MaxVersion - config.CipherSuites = c.CipherSuites - config.PreferServerCipherSuites = c.PreferServerCipherSuites - config.RejectUnknownSni = c.RejectUnknownSNI - - if c.PinnedPeerCertificateChainSha256 != nil { - config.PinnedPeerCertificateChainSha256 = [][]byte{} - for _, v := range *c.PinnedPeerCertificateChainSha256 { - hashValue, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, err + if c.Type == "" { + return nil, newError(`please fill in a valid value for "dest"`) + } + if c.Xver > 2 { + return nil, newError(`invalid PROXY protocol version, "xver" only accepts 0, 1, 2`) + } + if len(c.ServerNames) == 0 { + return nil, newError(`empty "serverNames"`) + } + if c.PrivateKey == "" { + return nil, newError(`empty "privateKey"`) + } + if config.PrivateKey, err = base64.RawURLEncoding.DecodeString(c.PrivateKey); err != nil || len(config.PrivateKey) != 32 { + return nil, newError(`invalid "privateKey": `, c.PrivateKey) + } + if c.MinClientVer != "" { + config.MinClientVer = make([]byte, 3) + var u uint64 + for i, s := range strings.Split(c.MinClientVer, ".") { + if i == 3 { + return nil, newError(`invalid "minClientVer": `, c.MinClientVer) + } + if u, err = strconv.ParseUint(s, 10, 8); err != nil { + return nil, newError(`"minClientVer[`, i, `]" should be lesser than 256`) + } else { + config.MinClientVer[i] = byte(u) + } } - config.PinnedPeerCertificateChainSha256 = append(config.PinnedPeerCertificateChainSha256, hashValue) } + if c.MaxClientVer != "" { + config.MaxClientVer = make([]byte, 3) + var u uint64 + for i, s := range strings.Split(c.MaxClientVer, ".") { + if i == 3 { + return nil, newError(`invalid "maxClientVer": `, c.MaxClientVer) + } + if u, err = strconv.ParseUint(s, 10, 8); err != nil { + return nil, newError(`"maxClientVer[`, i, `]" should be lesser than 256`) + } else { + config.MaxClientVer[i] = byte(u) + } + } + } + if len(c.ShortIds) == 0 { + return nil, newError(`empty "shortIds"`) + } + config.ShortIds = make([][]byte, len(c.ShortIds)) + for i, s := range c.ShortIds { + config.ShortIds[i] = make([]byte, 8) + if _, err = hex.Decode(config.ShortIds[i], []byte(s)); err != nil { + return nil, newError(`invalid "shortIds[`, i, `]": `, s) + } + } + config.Dest = s + config.Type = c.Type + config.Xver = c.Xver + config.ServerNames = c.ServerNames + config.MaxTimeDiff = c.MaxTimeDiff + } else { + if c.Fingerprint == "" { + return nil, newError(`empty "fingerprint"`) + } + if config.Fingerprint = strings.ToLower(c.Fingerprint); tls.GetFingerprint(config.Fingerprint) == nil { + return nil, newError(`unknown "fingerprint": `, config.Fingerprint) + } + if config.Fingerprint == "hellogolang" { + return nil, newError(`invalid "fingerprint": `, config.Fingerprint) + } + if len(c.ServerNames) != 0 { + return nil, newError(`non-empty "serverNames", please use "serverName" instead`) + } + if c.PublicKey == "" { + return nil, newError(`empty "publicKey"`) + } + if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 { + return nil, newError(`invalid "publicKey": `, c.PublicKey) + } + if len(c.ShortIds) != 0 { + return nil, newError(`non-empty "shortIds", please use "shortId" instead`) + } + config.ShortId = make([]byte, 8) + if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil { + return nil, newError(`invalid "shortId": `, c.ShortId) + } + if c.SpiderX == "" { + c.SpiderX = "/" + } + if c.SpiderX[0] != '/' { + return nil, newError(`invalid "spiderX": `, c.SpiderX) + } + config.SpiderY = make([]int64, 10) + u, _ := url.Parse(c.SpiderX) + q := u.Query() + parse := func(param string, index int) { + if q.Get(param) != "" { + s := strings.Split(q.Get(param), "-") + if len(s) == 1 { + config.SpiderY[index], _ = strconv.ParseInt(s[0], 10, 64) + config.SpiderY[index+1], _ = strconv.ParseInt(s[0], 10, 64) + } else { + config.SpiderY[index], _ = strconv.ParseInt(s[0], 10, 64) + config.SpiderY[index+1], _ = strconv.ParseInt(s[1], 10, 64) + } + } + q.Del(param) + } + parse("p", 0) // padding + parse("c", 2) // concurrency + parse("t", 4) // times + parse("i", 6) // interval + parse("r", 8) // return + u.RawQuery = q.Encode() + config.SpiderX = u.String() + config.ServerName = c.ServerName } - return config, nil } @@ -533,7 +615,14 @@ type SocketConfig struct { DialerProxy string `json:"dialerProxy"` TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"` TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"` - TCPCongestion string `json:"tcpCongestion"` + TCPCongestion string `json:"tcpCongestion"` + TCPWindowClamp int32 `json:"tcpWindowClamp"` + TCPMaxSeg int32 `json:"tcpMaxSeg"` + TcpNoDelay bool `json:"tcpNoDelay"` + TCPUserTimeout int32 `json:"tcpUserTimeout"` + V6only bool `json:"v6only"` + Interface string `json:"interface"` + TcpMptcp bool `json:"tcpMptcp"` } // Build implements Buildable. @@ -582,24 +671,31 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) { DialerProxy: c.DialerProxy, TcpKeepAliveInterval: c.TCPKeepAliveInterval, TcpKeepAliveIdle: c.TCPKeepAliveIdle, - TcpCongestion: c.TCPCongestion, + TcpCongestion: c.TCPCongestion, + TcpWindowClamp: c.TCPWindowClamp, + TcpMaxSeg: c.TCPMaxSeg, + TcpNoDelay: c.TcpNoDelay, + TcpUserTimeout: c.TCPUserTimeout, + V6Only: c.V6only, + Interface: c.Interface, + TcpMptcp: c.TcpMptcp, }, nil } type StreamConfig struct { - Network *TransportProtocol `json:"network"` - Security string `json:"security"` - TLSSettings *TLSConfig `json:"tlsSettings"` - XTLSSettings *XTLSConfig `json:"xtlsSettings"` - TCPSettings *TCPConfig `json:"tcpSettings"` - KCPSettings *KCPConfig `json:"kcpSettings"` - WSSettings *WebSocketConfig `json:"wsSettings"` - HTTPSettings *HTTPConfig `json:"httpSettings"` - DSSettings *DomainSocketConfig `json:"dsSettings"` - QUICSettings *QUICConfig `json:"quicSettings"` - SocketSettings *SocketConfig `json:"sockopt"` - GRPCConfig *GRPCConfig `json:"grpcSettings"` - GUNConfig *GRPCConfig `json:"gunSettings"` + Network *TransportProtocol `json:"network"` + Security string `json:"security"` + TLSSettings *TLSConfig `json:"tlsSettings"` + REALITYSettings *REALITYConfig `json:"realitySettings"` + TCPSettings *TCPConfig `json:"tcpSettings"` + KCPSettings *KCPConfig `json:"kcpSettings"` + WSSettings *WebSocketConfig `json:"wsSettings"` + HTTPSettings *HTTPConfig `json:"httpSettings"` + DSSettings *DomainSocketConfig `json:"dsSettings"` + QUICSettings *QUICConfig `json:"quicSettings"` + SocketSettings *SocketConfig `json:"sockopt"` + GRPCConfig *GRPCConfig `json:"grpcSettings"` + GUNConfig *GRPCConfig `json:"gunSettings"` } // Build implements Buildable. @@ -614,12 +710,11 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { } config.ProtocolName = protocol } - if strings.EqualFold(c.Security, "tls") { + switch strings.ToLower(c.Security) { + case "", "none": + case "tls": tlsSettings := c.TLSSettings if tlsSettings == nil { - if c.XTLSSettings != nil { - return nil, newError(`TLS: Please use "tlsSettings" instead of "xtlsSettings".`) - } tlsSettings = &TLSConfig{} } ts, err := tlsSettings.Build() @@ -629,25 +724,24 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { tm := serial.ToTypedMessage(ts) config.SecuritySettings = append(config.SecuritySettings, tm) config.SecurityType = tm.Type - } - if strings.EqualFold(c.Security, "xtls") { - if config.ProtocolName != "tcp" && config.ProtocolName != "mkcp" && config.ProtocolName != "domainsocket" { - return nil, newError("XTLS only supports TCP, mKCP and DomainSocket for now.") + case "reality": + if config.ProtocolName != "tcp" && config.ProtocolName != "http" && config.ProtocolName != "grpc" && config.ProtocolName != "domainsocket" { + return nil, newError("REALITY only supports TCP, H2, gRPC and DomainSocket for now.") } - xtlsSettings := c.XTLSSettings - if xtlsSettings == nil { - if c.TLSSettings != nil { - return nil, newError(`XTLS: Please use "xtlsSettings" instead of "tlsSettings".`) - } - xtlsSettings = &XTLSConfig{} + if c.REALITYSettings == nil { + return nil, newError(`REALITY: Empty "realitySettings".`) } - ts, err := xtlsSettings.Build() + ts, err := c.REALITYSettings.Build() if err != nil { - return nil, newError("Failed to build XTLS config.").Base(err) + return nil, newError("Failed to build REALITY config.").Base(err) } tm := serial.ToTypedMessage(ts) config.SecuritySettings = append(config.SecuritySettings, tm) config.SecurityType = tm.Type + case "xtls": + return nil, newError(`Please use VLESS flow "xtls-rprx-vision" with TLS or REALITY.`) + default: + return nil, newError(`Unknown security "` + c.Security + `".`) } if c.TCPSettings != nil { ts, err := c.TCPSettings.Build() diff --git a/infra/conf/transport_test.go b/infra/conf/transport_test.go index 3afe9729e79d..989c259c70ef 100644 --- a/infra/conf/transport_test.go +++ b/infra/conf/transport_test.go @@ -4,7 +4,6 @@ import ( "encoding/json" "testing" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" . "github.com/xtls/xray-core/infra/conf" @@ -18,6 +17,7 @@ import ( "github.com/xtls/xray-core/transport/internet/quic" "github.com/xtls/xray-core/transport/internet/tcp" "github.com/xtls/xray-core/transport/internet/websocket" + "google.golang.org/protobuf/proto" ) func TestSocketConfig(t *testing.T) { diff --git a/infra/conf/trojan.go b/infra/conf/trojan.go index 80ae7bb65147..2cd1e520b956 100644 --- a/infra/conf/trojan.go +++ b/infra/conf/trojan.go @@ -6,11 +6,11 @@ import ( "strconv" "syscall" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/trojan" + "google.golang.org/protobuf/proto" ) // TrojanServerTarget is configuration of a single trojan server @@ -30,13 +30,14 @@ type TrojanClientConfig struct { // Build implements Buildable func (c *TrojanClientConfig) Build() (proto.Message, error) { - config := new(trojan.ClientConfig) - if len(c.Servers) == 0 { return nil, newError("0 Trojan server configured.") } - serverSpecs := make([]*protocol.ServerEndpoint, len(c.Servers)) + config := &trojan.ClientConfig{ + Server: make([]*protocol.ServerEndpoint, len(c.Servers)), + } + for idx, rec := range c.Servers { if rec.Address == nil { return nil, newError("Trojan server address is not set.") @@ -47,38 +48,25 @@ func (c *TrojanClientConfig) Build() (proto.Message, error) { if rec.Password == "" { return nil, newError("Trojan password is not specified.") } - account := &trojan.Account{ - Password: rec.Password, - Flow: rec.Flow, - } - - switch account.Flow { - case "", "xtls-rprx-origin", "xtls-rprx-origin-udp443", "xtls-rprx-direct", "xtls-rprx-direct-udp443": - case "xtls-rprx-splice", "xtls-rprx-splice-udp443": - if runtime.GOOS != "linux" && runtime.GOOS != "android" { - return nil, newError(`Trojan servers: "` + account.Flow + `" only support linux in this version`) - } - default: - return nil, newError(`Trojan servers: "flow" doesn't support "` + account.Flow + `" in this version`) + if rec.Flow != "" { + return nil, newError(`Trojan doesn't support "flow" anymore.`) } - trojan := &protocol.ServerEndpoint{ + config.Server[idx] = &protocol.ServerEndpoint{ Address: rec.Address.Build(), Port: uint32(rec.Port), User: []*protocol.User{ { - Level: uint32(rec.Level), - Email: rec.Email, - Account: serial.ToTypedMessage(account), + Level: uint32(rec.Level), + Email: rec.Email, + Account: serial.ToTypedMessage(&trojan.Account{ + Password: rec.Password, + }), }, }, } - - serverSpecs[idx] = trojan } - config.Server = serverSpecs - return config, nil } @@ -109,27 +97,22 @@ type TrojanServerConfig struct { // Build implements Buildable func (c *TrojanServerConfig) Build() (proto.Message, error) { - config := new(trojan.ServerConfig) - config.Users = make([]*protocol.User, len(c.Clients)) + config := &trojan.ServerConfig{ + Users: make([]*protocol.User, len(c.Clients)), + } + for idx, rawUser := range c.Clients { - user := new(protocol.User) - account := &trojan.Account{ - Password: rawUser.Password, - Flow: rawUser.Flow, + if rawUser.Flow != "" { + return nil, newError(`Trojan doesn't support "flow" anymore.`) } - switch account.Flow { - case "", "xtls-rprx-origin", "xtls-rprx-direct": - case "xtls-rprx-splice": - return nil, newError(`Trojan clients: inbound doesn't support "xtls-rprx-splice" in this version, please use "xtls-rprx-direct" instead`) - default: - return nil, newError(`Trojan clients: "flow" doesn't support "` + account.Flow + `" in this version`) + config.Users[idx] = &protocol.User{ + Level: uint32(rawUser.Level), + Email: rawUser.Email, + Account: serial.ToTypedMessage(&trojan.Account{ + Password: rawUser.Password, + }), } - - user.Email = rawUser.Email - user.Level = uint32(rawUser.Level) - user.Account = serial.ToTypedMessage(account) - config.Users[idx] = user } if c.Fallback != nil { diff --git a/infra/conf/vless.go b/infra/conf/vless.go index 79c321443ee6..2e5c5d64cf3b 100644 --- a/infra/conf/vless.go +++ b/infra/conf/vless.go @@ -4,10 +4,8 @@ import ( "encoding/json" "runtime" "strconv" - "strings" "syscall" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" @@ -15,6 +13,7 @@ import ( "github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless/inbound" "github.com/xtls/xray-core/proxy/vless/outbound" + "google.golang.org/protobuf/proto" ) type VLessInboundFallback struct { @@ -53,18 +52,8 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { } account.Id = u.String() - accountFlow := account.Flow - flows := strings.Split(account.Flow, ",") - for _, f := range flows { - t := strings.TrimSpace(f) - if t != "none" { - accountFlow = t - } - } - switch accountFlow { - case "", vless.XRO, vless.XRD, vless.XRV: - case vless.XRS: - return nil, newError(`VLESS clients: inbound doesn't support "xtls-rprx-splice" in this version, please use "xtls-rprx-direct" instead`) + switch account.Flow { + case "", vless.XRV: default: return nil, newError(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`) } @@ -191,11 +180,7 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { account.Id = u.String() switch account.Flow { - case "", vless.XRO, vless.XRO + "-udp443", vless.XRD, vless.XRD + "-udp443", vless.XRV, vless.XRV + "-udp443": - case vless.XRS, vless.XRS + "-udp443": - if runtime.GOOS != "linux" && runtime.GOOS != "android" { - return nil, newError(`VLESS users: "` + account.Flow + `" only support linux in this version`) - } + case "", vless.XRV, vless.XRV + "-udp443": default: return nil, newError(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`) } diff --git a/infra/conf/vless_test.go b/infra/conf/vless_test.go index 819ee9421858..0f70243797e1 100644 --- a/infra/conf/vless_test.go +++ b/infra/conf/vless_test.go @@ -26,7 +26,7 @@ func TestVLessOutbound(t *testing.T) { "users": [ { "id": "27848739-7e62-4138-9fd3-098a63964b6b", - "flow": "xtls-rprx-direct-udp443", + "flow": "xtls-rprx-vision-udp443", "encryption": "none", "level": 0 } @@ -47,7 +47,7 @@ func TestVLessOutbound(t *testing.T) { { Account: serial.ToTypedMessage(&vless.Account{ Id: "27848739-7e62-4138-9fd3-098a63964b6b", - Flow: "xtls-rprx-direct-udp443", + Flow: "xtls-rprx-vision-udp443", Encryption: "none", }), Level: 0, @@ -71,7 +71,7 @@ func TestVLessInbound(t *testing.T) { "clients": [ { "id": "27848739-7e62-4138-9fd3-098a63964b6b", - "flow": "xtls-rprx-direct", + "flow": "xtls-rprx-vision", "level": 0, "email": "love@example.com" } @@ -98,7 +98,7 @@ func TestVLessInbound(t *testing.T) { { Account: serial.ToTypedMessage(&vless.Account{ Id: "27848739-7e62-4138-9fd3-098a63964b6b", - Flow: "xtls-rprx-direct", + Flow: "xtls-rprx-vision", }), Level: 0, Email: "love@example.com", diff --git a/infra/conf/vmess.go b/infra/conf/vmess.go index c646bebbd0ed..ba3193563636 100644 --- a/infra/conf/vmess.go +++ b/infra/conf/vmess.go @@ -4,18 +4,17 @@ import ( "encoding/json" "strings" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/proxy/vmess" "github.com/xtls/xray-core/proxy/vmess/inbound" "github.com/xtls/xray-core/proxy/vmess/outbound" + "google.golang.org/protobuf/proto" ) type VMessAccount struct { ID string `json:"id"` - AlterIds uint16 `json:"alterId"` Security string `json:"security"` Experiments string `json:"experiments"` } @@ -38,8 +37,7 @@ func (a *VMessAccount) Build() *vmess.Account { st = protocol.SecurityType_AUTO } return &vmess.Account{ - Id: a.ID, - AlterId: uint32(a.AlterIds), + Id: a.ID, SecuritySettings: &protocol.SecurityConfig{ Type: st, }, @@ -63,14 +61,12 @@ type FeaturesConfig struct { } type VMessDefaultConfig struct { - AlterIDs uint16 `json:"alterId"` - Level byte `json:"level"` + Level byte `json:"level"` } // Build implements Buildable func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig { config := new(inbound.DefaultConfig) - config.AlterId = uint32(c.AlterIDs) config.Level = uint32(c.Level) return config } @@ -80,14 +76,11 @@ type VMessInboundConfig struct { Features *FeaturesConfig `json:"features"` Defaults *VMessDefaultConfig `json:"default"` DetourConfig *VMessDetourConfig `json:"detour"` - SecureOnly bool `json:"disableInsecureEncryption"` } // Build implements Buildable func (c *VMessInboundConfig) Build() (proto.Message, error) { - config := &inbound.Config{ - SecureEncryptionOnly: c.SecureOnly, - } + config := &inbound.Config{} if c.Defaults != nil { config.Default = c.Defaults.Build() diff --git a/infra/conf/vmess_test.go b/infra/conf/vmess_test.go index 17cda04d5053..8adda170542a 100644 --- a/infra/conf/vmess_test.go +++ b/infra/conf/vmess_test.go @@ -105,7 +105,6 @@ func TestVMessInbound(t *testing.T) { Detour: &inbound.DetourConfig{ To: "tag_to_detour", }, - SecureEncryptionOnly: true, }, }, }) diff --git a/infra/conf/wireguard.go b/infra/conf/wireguard.go index c4dec367dfb0..0c79297bd47d 100644 --- a/infra/conf/wireguard.go +++ b/infra/conf/wireguard.go @@ -4,8 +4,8 @@ import ( "encoding/base64" "encoding/hex" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/proxy/wireguard" + "google.golang.org/protobuf/proto" ) type WireGuardPeerConfig struct { @@ -52,6 +52,7 @@ type WireGuardConfig struct { Peers []*WireGuardPeerConfig `json:"peers"` MTU int `json:"mtu"` NumWorkers int `json:"workers"` + Reserved []byte `json:"reserved"` } func (c *WireGuardConfig) Build() (proto.Message, error) { @@ -90,6 +91,11 @@ func (c *WireGuardConfig) Build() (proto.Message, error) { // we don't need to process fallback manually config.NumWorkers = int32(c.NumWorkers) + if len(c.Reserved) != 0 && len(c.Reserved) != 3 { + return nil, newError(`"reserved" should be empty or 3 bytes`) + } + config.Reserved = c.Reserved + return config, nil } diff --git a/infra/conf/xray.go b/infra/conf/xray.go index cda512da9ff0..7bfc53e36325 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -13,7 +13,6 @@ import ( "github.com/xtls/xray-core/common/serial" core "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/transport/internet" - "github.com/xtls/xray-core/transport/internet/xtls" ) var ( @@ -25,7 +24,6 @@ var ( "vless": func() interface{} { return new(VLessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) }, "trojan": func() interface{} { return new(TrojanServerConfig) }, - "mtproto": func() interface{} { return new(MTProtoServerConfig) }, }, "protocol", "settings") outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ @@ -38,7 +36,6 @@ var ( "vless": func() interface{} { return new(VLessOutboundConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) }, "trojan": func() interface{} { return new(TrojanClientConfig) }, - "mtproto": func() interface{} { return new(MTProtoClientConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) }, "wireguard": func() interface{} { return new(WireGuardConfig) }, }, "protocol", "settings") @@ -108,25 +105,27 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { } type MuxConfig struct { - Enabled bool `json:"enabled"` - Concurrency int16 `json:"concurrency"` + Enabled bool `json:"enabled"` + Concurrency int16 `json:"concurrency"` + XudpConcurrency int16 `json:"xudpConcurrency"` + XudpProxyUDP443 string `json:"xudpProxyUDP443"` } // Build creates MultiplexingConfig, Concurrency < 0 completely disables mux. -func (m *MuxConfig) Build() *proxyman.MultiplexingConfig { - if m.Concurrency < 0 { - return nil - } - - var con uint32 = 8 - if m.Concurrency > 0 { - con = uint32(m.Concurrency) +func (m *MuxConfig) Build() (*proxyman.MultiplexingConfig, error) { + switch m.XudpProxyUDP443 { + case "": + m.XudpProxyUDP443 = "reject" + case "reject", "allow", "skip": + default: + return nil, newError(`unknown "xudpProxyUDP443": `, m.XudpProxyUDP443) } - return &proxyman.MultiplexingConfig{ - Enabled: m.Enabled, - Concurrency: con, - } + Enabled: m.Enabled, + Concurrency: int32(m.Concurrency), + XudpConcurrency: int32(m.XudpConcurrency), + XudpProxyUDP443: m.XudpProxyUDP443, + }, nil } type InboundDetourAllocationConfig struct { @@ -236,9 +235,6 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) { if err != nil { return nil, err } - if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) && !strings.EqualFold(c.Protocol, "vless") && !strings.EqualFold(c.Protocol, "trojan") { - return nil, newError("XTLS doesn't supports " + c.Protocol + " for now.") - } receiverSettings.StreamSettings = ss } if c.SniffingConfig != nil { @@ -319,9 +315,6 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { if err != nil { return nil, err } - if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) && !strings.EqualFold(c.Protocol, "vless") && !strings.EqualFold(c.Protocol, "trojan") { - return nil, newError("XTLS doesn't supports " + c.Protocol + " for now.") - } senderSettings.StreamSettings = ss } @@ -346,13 +339,9 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { } if c.MuxSettings != nil { - ms := c.MuxSettings.Build() - if ms != nil && ms.Enabled { - if ss := senderSettings.StreamSettings; ss != nil { - if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) { - return nil, newError("XTLS doesn't support Mux for now.") - } - } + ms, err := c.MuxSettings.Build() + if err != nil { + return nil, newError("failed to build Mux config.").Base(err) } senderSettings.MultiplexSettings = ms } diff --git a/infra/conf/xray_test.go b/infra/conf/xray_test.go index 8c8151de7e64..c7e20ed481e0 100644 --- a/infra/conf/xray_test.go +++ b/infra/conf/xray_test.go @@ -5,7 +5,6 @@ import ( "reflect" "testing" - "github.com/golang/protobuf/proto" "github.com/google/go-cmp/cmp" "github.com/xtls/xray-core/app/dispatcher" "github.com/xtls/xray-core/app/log" @@ -27,6 +26,7 @@ import ( "github.com/xtls/xray-core/transport/internet/http" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/websocket" + "google.golang.org/protobuf/proto" ) func TestXrayConfig(t *testing.T) { @@ -221,7 +221,8 @@ func TestXrayConfig(t *testing.T) { }, }), ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{ - Server: &net.Endpoint{}, + Server: &net.Endpoint{}, + Non_IPQuery: "drop", }), }, }, @@ -340,24 +341,35 @@ func TestMuxConfig_Build(t *testing.T) { want *proxyman.MultiplexingConfig }{ {"default", `{"enabled": true, "concurrency": 16}`, &proxyman.MultiplexingConfig{ - Enabled: true, - Concurrency: 16, + Enabled: true, + Concurrency: 16, + XudpConcurrency: 0, + XudpProxyUDP443: "reject", }}, {"empty def", `{}`, &proxyman.MultiplexingConfig{ - Enabled: false, - Concurrency: 8, + Enabled: false, + Concurrency: 0, + XudpConcurrency: 0, + XudpProxyUDP443: "reject", }}, {"not enable", `{"enabled": false, "concurrency": 4}`, &proxyman.MultiplexingConfig{ - Enabled: false, - Concurrency: 4, + Enabled: false, + Concurrency: 4, + XudpConcurrency: 0, + XudpProxyUDP443: "reject", + }}, + {"forbidden", `{"enabled": false, "concurrency": -1}`, &proxyman.MultiplexingConfig{ + Enabled: false, + Concurrency: -1, + XudpConcurrency: 0, + XudpProxyUDP443: "reject", }}, - {"forbidden", `{"enabled": false, "concurrency": -1}`, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := &MuxConfig{} common.Must(json.Unmarshal([]byte(tt.fields), m)) - if got := m.Build(); !reflect.DeepEqual(got, tt.want) { + if got, _ := m.Build(); !reflect.DeepEqual(got, tt.want) { t.Errorf("MuxConfig.Build() = %v, want %v", got, tt.want) } }) diff --git a/infra/vprotogen/main.go b/infra/vprotogen/main.go index df920751151c..10aaa25445f4 100644 --- a/infra/vprotogen/main.go +++ b/infra/vprotogen/main.go @@ -108,7 +108,7 @@ func getProjectProtocVersion(url string) (string, error) { if err != nil { return "", fmt.Errorf("can not read from body") } - versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v(\d+\.\d+\.\d+)`) + versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v\d+\.(\d+\.\d+)`) matched := versionRegexp.FindStringSubmatch(string(body)) return matched[1], nil } @@ -120,7 +120,7 @@ func getInstalledProtocVersion(protocPath string) (string, error) { if cmdErr != nil { return "", cmdErr } - versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+\.\d+)`) + versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+)`) matched := versionRegexp.FindStringSubmatch(string(output)) return matched[1], nil } diff --git a/main/commands/all/commands.go b/main/commands/all/commands.go index da50a8456c05..9b8b49e0258a 100644 --- a/main/commands/all/commands.go +++ b/main/commands/all/commands.go @@ -15,5 +15,6 @@ func init() { // cmdConvert, tls.CmdTLS, cmdUUID, + cmdX25519, ) } diff --git a/main/commands/all/x25519.go b/main/commands/all/x25519.go new file mode 100644 index 000000000000..e7909d9bff9e --- /dev/null +++ b/main/commands/all/x25519.go @@ -0,0 +1,71 @@ +package all + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + + "github.com/xtls/xray-core/main/commands/base" + "golang.org/x/crypto/curve25519" +) + +var cmdX25519 = &base.Command{ + UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"]`, + Short: `Generate key pair for x25519 key exchange`, + Long: ` +Generate key pair for x25519 key exchange. + +Random: {{.Exec}} x25519 + +From private key: {{.Exec}} x25519 -i "private key (base64.RawURLEncoding)" +`, +} + +func init() { + cmdX25519.Run = executeX25519 // break init loop +} + +var input_base64 = cmdX25519.Flag.String("i", "", "") + +func executeX25519(cmd *base.Command, args []string) { + var output string + var err error + var privateKey []byte + var publicKey []byte + if len(*input_base64) > 0 { + privateKey, err = base64.RawURLEncoding.DecodeString(*input_base64) + if err != nil { + output = err.Error() + goto out + } + if len(privateKey) != curve25519.ScalarSize { + output = "Invalid length of private key." + goto out + } + } + + if privateKey == nil { + privateKey = make([]byte, curve25519.ScalarSize) + if _, err = rand.Read(privateKey); err != nil { + output = err.Error() + goto out + } + } + + // Modify random bytes using algorithm described at: + // https://cr.yp.to/ecdh.html. + privateKey[0] &= 248 + privateKey[31] &= 127 + privateKey[31] |= 64 + + if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil { + output = err.Error() + goto out + } + + output = fmt.Sprintf("Private key: %v\nPublic key: %v", + base64.RawURLEncoding.EncodeToString(privateKey), + base64.RawURLEncoding.EncodeToString(publicKey)) +out: + fmt.Println(output) +} diff --git a/main/distro/all/all.go b/main/distro/all/all.go index f92542d5c54e..d383e043ab02 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -40,7 +40,6 @@ import ( _ "github.com/xtls/xray-core/proxy/freedom" _ "github.com/xtls/xray-core/proxy/http" _ "github.com/xtls/xray-core/proxy/loopback" - _ "github.com/xtls/xray-core/proxy/mtproto" _ "github.com/xtls/xray-core/proxy/shadowsocks" _ "github.com/xtls/xray-core/proxy/socks" _ "github.com/xtls/xray-core/proxy/trojan" @@ -56,11 +55,11 @@ import ( _ "github.com/xtls/xray-core/transport/internet/http" _ "github.com/xtls/xray-core/transport/internet/kcp" _ "github.com/xtls/xray-core/transport/internet/quic" + _ "github.com/xtls/xray-core/transport/internet/reality" _ "github.com/xtls/xray-core/transport/internet/tcp" _ "github.com/xtls/xray-core/transport/internet/tls" _ "github.com/xtls/xray-core/transport/internet/udp" _ "github.com/xtls/xray-core/transport/internet/websocket" - _ "github.com/xtls/xray-core/transport/internet/xtls" // Transport headers _ "github.com/xtls/xray-core/transport/internet/headers/http" diff --git a/main/run.go b/main/run.go index c71db9b6f2f5..b4c323cd02ee 100644 --- a/main/run.go +++ b/main/run.go @@ -120,13 +120,13 @@ func dirExists(file string) bool { func getRegepxByFormat() string { switch strings.ToLower(*format) { case "json": - return `^.+\.json$` + return `^.+\.(json|jsonc)$` case "toml": return `^.+\.toml$` case "yaml", "yml": return `^.+\.(yaml|yml)$` default: - return `^.+\.(json|toml|yaml|yml)$` + return `^.+\.(json|jsonc|toml|yaml|yml)$` } } diff --git a/proxy/blackhole/blackhole.go b/proxy/blackhole/blackhole.go index b17c60c4ea12..4b8194172e70 100644 --- a/proxy/blackhole/blackhole.go +++ b/proxy/blackhole/blackhole.go @@ -8,6 +8,7 @@ import ( "time" "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" ) @@ -30,6 +31,11 @@ func New(ctx context.Context, config *Config) (*Handler, error) { // Process implements OutboundHandler.Dispatch(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + outbound := session.OutboundFromContext(ctx) + if outbound != nil { + outbound.Name = "blackhole" + } + nBytes := h.response.WriteTo(link.Writer) if nBytes > 0 { // Sleep a little here to make sure the response is sent to client. diff --git a/proxy/blackhole/config.pb.go b/proxy/blackhole/config.pb.go index 029876259b7b..2bc838c6dabb 100644 --- a/proxy/blackhole/config.pb.go +++ b/proxy/blackhole/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/blackhole/config.proto package blackhole diff --git a/proxy/dns/config.pb.go b/proxy/dns/config.pb.go index 6fcc0027a6a5..24016460325b 100644 --- a/proxy/dns/config.pb.go +++ b/proxy/dns/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/dns/config.proto package dns @@ -28,8 +28,9 @@ type Config struct { // Server is the DNS server address. If specified, this address overrides the // original one. - Server *net.Endpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` - UserLevel uint32 `protobuf:"varint,2,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` + Server *net.Endpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + UserLevel uint32 `protobuf:"varint,2,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` + Non_IPQuery string `protobuf:"bytes,3,opt,name=non_IP_query,json=nonIPQuery,proto3" json:"non_IP_query,omitempty"` } func (x *Config) Reset() { @@ -78,6 +79,13 @@ func (x *Config) GetUserLevel() uint32 { return 0 } +func (x *Config) GetNon_IPQuery() string { + if x != nil { + return x.Non_IPQuery + } + return "" +} + var File_proxy_dns_config_proto protoreflect.FileDescriptor var file_proxy_dns_config_proto_rawDesc = []byte{ @@ -85,18 +93,20 @@ var file_proxy_dns_config_proto_rawDesc = []byte{ 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7c, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x42, 0x4c, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, - 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, - 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6e, 0x73, 0xaa, - 0x02, 0x0e, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x44, 0x6e, 0x73, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x6f, 0x6e, 0x5f, 0x49, 0x50, 0x5f, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x6f, 0x6e, 0x49, 0x50, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x42, 0x4c, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x23, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, + 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x64, 0x6e, + 0x73, 0xaa, 0x02, 0x0e, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x44, + 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proxy/dns/config.proto b/proxy/dns/config.proto index 0b3247255ed1..e9304a510c7a 100644 --- a/proxy/dns/config.proto +++ b/proxy/dns/config.proto @@ -13,4 +13,5 @@ message Config { // original one. xray.common.net.Endpoint server = 1; uint32 user_level = 2; + string non_IP_query = 3; } diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index ae123c28be49..415fe991fb8f 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -8,6 +8,7 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" dns_proto "github.com/xtls/xray-core/common/protocol/dns" "github.com/xtls/xray-core/common/session" @@ -43,6 +44,7 @@ type Handler struct { ownLinkVerifier ownLinkVerifier server net.Destination timeout time.Duration + nonIPQuery string } func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager policy.Manager) error { @@ -56,6 +58,7 @@ func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager polic if config.Server != nil { h.server = config.Server.AsDestination() } + h.nonIPQuery = config.Non_IPQuery return nil } @@ -93,6 +96,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet. if outbound == nil || !outbound.Target.IsValid() { return newError("invalid outbound") } + outbound.Name = "dns" srcNetwork := outbound.Target.Network @@ -148,6 +152,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet. } } + if session.TimeoutOnlyFromContext(ctx) { + ctx, _ = context.WithCancel(context.Background()) + } + ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, h.timeout) @@ -170,6 +178,9 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet. isIPQuery, domain, id, qType := parseIPQuery(b.Bytes()) if isIPQuery { go h.handleIPQuery(id, qType, domain, writer) + } + if isIPQuery || h.nonIPQuery == "drop" { + b.Release() continue } } @@ -228,7 +239,7 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, } rcode := dns.RCodeFromError(err) - if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse { + if rcode == 0 && len(ips) == 0 && !errors.AllEqual(dns.ErrEmptyResponse, errors.Cause(err)) { newError("ip query").Base(err).WriteToLog() return } diff --git a/proxy/dokodemo/config.pb.go b/proxy/dokodemo/config.pb.go index e71b87a99cbe..6e43def10843 100644 --- a/proxy/dokodemo/config.pb.go +++ b/proxy/dokodemo/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/dokodemo/config.proto package dokodemo @@ -31,11 +31,11 @@ type Config struct { // List of networks that the Dokodemo accepts. // Deprecated. Use networks. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in proxy/dokodemo/config.proto. NetworkList *net.NetworkList `protobuf:"bytes,3,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"` // List of networks that the Dokodemo accepts. Networks []net.Network `protobuf:"varint,7,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"` - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in proxy/dokodemo/config.proto. Timeout uint32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"` FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"` UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` @@ -87,7 +87,7 @@ func (x *Config) GetPort() uint32 { return 0 } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in proxy/dokodemo/config.proto. func (x *Config) GetNetworkList() *net.NetworkList { if x != nil { return x.NetworkList @@ -102,7 +102,7 @@ func (x *Config) GetNetworks() []net.Network { return nil } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in proxy/dokodemo/config.proto. func (x *Config) GetTimeout() uint32 { if x != nil { return x.Timeout diff --git a/proxy/dokodemo/dokodemo.go b/proxy/dokodemo/dokodemo.go index d0fb69f932bb..4a4735e8d7da 100644 --- a/proxy/dokodemo/dokodemo.go +++ b/proxy/dokodemo/dokodemo.go @@ -102,10 +102,10 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st } inbound := session.InboundFromContext(ctx) - if inbound != nil { - inbound.User = &protocol.MemoryUser{ - Level: d.config.UserLevel, - } + inbound.Name = "dokodemo-door" + inbound.SetCanSpliceCopy(1) + inbound.User = &protocol.MemoryUser{ + Level: d.config.UserLevel, } ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ diff --git a/transport/internet/xtls/errors.generated.go b/proxy/errors.generated.go similarity index 93% rename from transport/internet/xtls/errors.generated.go rename to proxy/errors.generated.go index bce26cc1cf2b..1a64389659d1 100644 --- a/transport/internet/xtls/errors.generated.go +++ b/proxy/errors.generated.go @@ -1,4 +1,4 @@ -package xtls +package proxy import "github.com/xtls/xray-core/common/errors" diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index e25bc943feb8..7561f7fd9f10 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/freedom/config.proto package freedom @@ -70,7 +70,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber { // Deprecated: Use Config_DomainStrategy.Descriptor instead. func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) { - return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1, 0} + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{2, 0} } type DestinationOverride struct { @@ -120,22 +120,110 @@ func (x *DestinationOverride) GetServer() *protocol.ServerEndpoint { return nil } +type Fragment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PacketsFrom uint64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"` + PacketsTo uint64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"` + LengthMin uint64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"` + LengthMax uint64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"` + IntervalMin uint64 `protobuf:"varint,5,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` + IntervalMax uint64 `protobuf:"varint,6,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` +} + +func (x *Fragment) Reset() { + *x = Fragment{} + if protoimpl.UnsafeEnabled { + mi := &file_proxy_freedom_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Fragment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Fragment) ProtoMessage() {} + +func (x *Fragment) ProtoReflect() protoreflect.Message { + mi := &file_proxy_freedom_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Fragment.ProtoReflect.Descriptor instead. +func (*Fragment) Descriptor() ([]byte, []int) { + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1} +} + +func (x *Fragment) GetPacketsFrom() uint64 { + if x != nil { + return x.PacketsFrom + } + return 0 +} + +func (x *Fragment) GetPacketsTo() uint64 { + if x != nil { + return x.PacketsTo + } + return 0 +} + +func (x *Fragment) GetLengthMin() uint64 { + if x != nil { + return x.LengthMin + } + return 0 +} + +func (x *Fragment) GetLengthMax() uint64 { + if x != nil { + return x.LengthMax + } + return 0 +} + +func (x *Fragment) GetIntervalMin() uint64 { + if x != nil { + return x.IntervalMin + } + return 0 +} + +func (x *Fragment) GetIntervalMax() uint64 { + if x != nil { + return x.IntervalMax + } + return 0 +} + type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields DomainStrategy Config_DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.freedom.Config_DomainStrategy" json:"domain_strategy,omitempty"` - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in proxy/freedom/config.proto. Timeout uint32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` DestinationOverride *DestinationOverride `protobuf:"bytes,3,opt,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` UserLevel uint32 `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` + Fragment *Fragment `protobuf:"bytes,5,opt,name=fragment,proto3" json:"fragment,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_proxy_freedom_config_proto_msgTypes[1] + mi := &file_proxy_freedom_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -148,7 +236,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_proxy_freedom_config_proto_msgTypes[1] + mi := &file_proxy_freedom_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -161,7 +249,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1} + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{2} } func (x *Config) GetDomainStrategy() Config_DomainStrategy { @@ -171,7 +259,7 @@ func (x *Config) GetDomainStrategy() Config_DomainStrategy { return Config_AS_IS } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in proxy/freedom/config.proto. func (x *Config) GetTimeout() uint32 { if x != nil { return x.Timeout @@ -193,6 +281,13 @@ func (x *Config) GetUserLevel() uint32 { return 0 } +func (x *Config) GetFragment() *Fragment { + if x != nil { + return x.Fragment + } + return nil +} + var File_proxy_freedom_config_proto protoreflect.FileDescriptor var file_proxy_freedom_config_proto_rawDesc = []byte{ @@ -206,33 +301,50 @@ var file_proxy_freedom_config_proto_rawDesc = []byte{ 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xb8, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78, - 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, - 0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69, - 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x5a, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x13, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x22, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, - 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, - 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, - 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, - 0x36, 0x10, 0x03, 0x42, 0x58, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, - 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a, - 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, - 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x2f, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, - 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xd0, 0x01, 0x0a, 0x08, 0x46, 0x72, 0x61, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x70, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x73, 0x5f, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x73, 0x54, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x65, 0x6e, + 0x67, 0x74, 0x68, 0x4d, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x65, 0x6e, 0x67, + 0x74, 0x68, 0x4d, 0x61, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x61, 0x78, 0x22, 0xf2, 0x02, 0x0a, 0x06, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, + 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x5a, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, + 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, + 0x72, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x46, 0x72, 0x61, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x41, 0x0a, + 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, + 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, + 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, + 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, + 0x42, 0x58, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, + 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x66, 0x72, + 0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -248,22 +360,24 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte { } var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_proxy_freedom_config_proto_goTypes = []interface{}{ (Config_DomainStrategy)(0), // 0: xray.proxy.freedom.Config.DomainStrategy (*DestinationOverride)(nil), // 1: xray.proxy.freedom.DestinationOverride - (*Config)(nil), // 2: xray.proxy.freedom.Config - (*protocol.ServerEndpoint)(nil), // 3: xray.common.protocol.ServerEndpoint + (*Fragment)(nil), // 2: xray.proxy.freedom.Fragment + (*Config)(nil), // 3: xray.proxy.freedom.Config + (*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint } var file_proxy_freedom_config_proto_depIdxs = []int32{ - 3, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint + 4, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint 0, // 1: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.proxy.freedom.Config.DomainStrategy 1, // 2: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 3: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_proxy_freedom_config_proto_init() } @@ -285,6 +399,18 @@ func file_proxy_freedom_config_proto_init() { } } file_proxy_freedom_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Fragment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proxy_freedom_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state @@ -303,7 +429,7 @@ func file_proxy_freedom_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proxy_freedom_config_proto_rawDesc, NumEnums: 1, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/proxy/freedom/config.proto b/proxy/freedom/config.proto index 7578a43fba12..53524e197af0 100644 --- a/proxy/freedom/config.proto +++ b/proxy/freedom/config.proto @@ -12,6 +12,15 @@ message DestinationOverride { xray.common.protocol.ServerEndpoint server = 1; } +message Fragment { + uint64 packets_from = 1; + uint64 packets_to = 2; + uint64 length_min = 3; + uint64 length_max = 4; + uint64 interval_min = 5; + uint64 interval_max = 6; +} + message Config { enum DomainStrategy { AS_IS = 0; @@ -23,4 +32,5 @@ message Config { uint32 timeout = 2 [deprecated = true]; DestinationOverride destination_override = 3; uint32 user_level = 4; + Fragment fragment = 5; } diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 15ebc22b659d..808f837faf7b 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -4,12 +4,16 @@ package freedom import ( "context" + "crypto/rand" + "io" + "math/big" "time" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" @@ -18,11 +22,14 @@ import ( "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" ) +var useSplice bool + func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { h := new(Handler) @@ -33,6 +40,12 @@ func init() { } return h, nil })) + const defaultFlagValue = "NOT_DEFINED_AT_ALL" + value := platform.NewEnvFlag("xray.buf.splice").GetValue(func() string { return defaultFlagValue }) + switch value { + case "auto", "enable": + useSplice = true + } } // Handler handles Freedom connections. @@ -104,6 +117,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } + outbound.Name = "freedom" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(1) + } destination := outbound.Target UDPOverride := net.UDPDestination(nil, 0) if h.config.DestinationOverride != nil { @@ -117,7 +135,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte UDPOverride.Port = destination.Port } } - newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx)) input := link.Reader output := link.Writer @@ -148,17 +165,38 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return newError("failed to open connection to ", destination).Base(err) } defer conn.Close() + newError("connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr()).WriteToLog(session.ExportIDToError(ctx)) + + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } plcy := h.policy() ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, plcy.Timeouts.ConnectionIdle) requestDone := func() error { defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) var writer buf.Writer if destination.Network == net.Network_TCP { - writer = buf.NewWriter(conn) + if h.config.Fragment != nil { + newError("FRAGMENT", h.config.Fragment.PacketsFrom, h.config.Fragment.PacketsTo, h.config.Fragment.LengthMin, h.config.Fragment.LengthMax, + h.config.Fragment.IntervalMin, h.config.Fragment.IntervalMax).AtDebug().WriteToLog(session.ExportIDToError(ctx)) + writer = buf.NewWriter(&FragmentWriter{ + fragment: h.config.Fragment, + writer: conn, + }) + } else { + writer = buf.NewWriter(conn) + } } else { writer = NewPacketWriter(conn, h, ctx, UDPOverride) } @@ -172,20 +210,24 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte responseDone := func() error { defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) - - var reader buf.Reader if destination.Network == net.Network_TCP { - reader = buf.NewReader(conn) - } else { - reader = NewPacketReader(conn, UDPOverride) + var writeConn net.Conn + if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil && useSplice { + writeConn = inbound.Conn + } + return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer) } + reader := NewPacketReader(conn, UDPOverride) if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil { return newError("failed to process response").Base(err) } - return nil } + if newCtx != nil { + ctx = newCtx + } + if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil { return newError("connection ends").Base(err) } @@ -309,3 +351,76 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { } return nil } + +type FragmentWriter struct { + fragment *Fragment + writer io.Writer + count uint64 +} + +func (f *FragmentWriter) Write(b []byte) (int, error) { + f.count++ + + if f.fragment.PacketsFrom == 0 && f.fragment.PacketsTo == 1 { + if f.count != 1 || len(b) <= 5 || b[0] != 22 { + return f.writer.Write(b) + } + recordLen := 5 + ((int(b[3]) << 8) | int(b[4])) + data := b[5:recordLen] + buf := make([]byte, 1024) + for from := 0; ; { + to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax))) + if to > len(data) { + to = len(data) + } + copy(buf[:3], b) + copy(buf[5:], data[from:to]) + l := to - from + from = to + buf[3] = byte(l >> 8) + buf[4] = byte(l) + _, err := f.writer.Write(buf[:5+l]) + time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond) + if err != nil { + return 0, err + } + if from == len(data) { + if len(b) > recordLen { + n, err := f.writer.Write(b[recordLen:]) + if err != nil { + return recordLen + n, err + } + } + return len(b), nil + } + } + } + + if f.fragment.PacketsFrom != 0 && (f.count < f.fragment.PacketsFrom || f.count > f.fragment.PacketsTo) { + return f.writer.Write(b) + } + for from := 0; ; { + to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax))) + if to > len(b) { + to = len(b) + } + n, err := f.writer.Write(b[from:to]) + from += n + time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond) + if err != nil { + return from, err + } + if from >= len(b) { + return from, nil + } + } +} + +// stolen from github.com/xtls/xray-core/transport/internet/reality +func randBetween(left int64, right int64) int64 { + if left == right { + return left + } + bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left)) + return left + bigInt.Int64() +} diff --git a/proxy/http/client.go b/proxy/http/client.go index 71a10e699739..302e521dc91b 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -73,6 +73,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } + outbound.Name = "http" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(2) + } target := outbound.Target targetAddr := target.NetAddr() @@ -128,8 +133,19 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } + ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, p.Timeouts.ConnectionIdle) requestFunc := func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) @@ -140,6 +156,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return newError("connection ends").Base(err) @@ -157,6 +177,10 @@ func fillRequestHeader(ctx context.Context, header []*Header) ([]*Header, error) inbound := session.InboundFromContext(ctx) outbound := session.OutboundFromContext(ctx) + if inbound == nil || outbound == nil { + return nil, newError("missing inbound or outbound metadata from context") + } + data := struct { Source net.Destination Target net.Destination diff --git a/proxy/http/config.pb.go b/proxy/http/config.pb.go index e2613cda6fad..986a038a9aa8 100644 --- a/proxy/http/config.pb.go +++ b/proxy/http/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/http/config.proto package http @@ -82,7 +82,7 @@ type ServerConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in proxy/http/config.proto. Timeout uint32 `protobuf:"varint,1,opt,name=timeout,proto3" json:"timeout,omitempty"` Accounts map[string]string `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` AllowTransparent bool `protobuf:"varint,3,opt,name=allow_transparent,json=allowTransparent,proto3" json:"allow_transparent,omitempty"` @@ -121,7 +121,7 @@ func (*ServerConfig) Descriptor() ([]byte, []int) { return file_proxy_http_config_proto_rawDescGZIP(), []int{1} } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in proxy/http/config.proto. func (x *ServerConfig) GetTimeout() uint32 { if x != nil { return x.Timeout diff --git a/proxy/http/server.go b/proxy/http/server.go index cdcf2e3afdbc..511d9b08c3ae 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -84,10 +84,10 @@ type readerOnly struct { func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { inbound := session.InboundFromContext(ctx) - if inbound != nil { - inbound.User = &protocol.MemoryUser{ - Level: s.config.UserLevel, - } + inbound.Name = "http" + inbound.SetCanSpliceCopy(2) + inbound.User = &protocol.MemoryUser{ + Level: s.config.UserLevel, } reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size) diff --git a/proxy/loopback/config.pb.go b/proxy/loopback/config.pb.go index 2fc1234cf3de..e25f8425f0a9 100644 --- a/proxy/loopback/config.pb.go +++ b/proxy/loopback/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/loopback/config.proto package loopback diff --git a/proxy/loopback/loopback.go b/proxy/loopback/loopback.go index 946847f34df1..30c39bd96409 100644 --- a/proxy/loopback/loopback.go +++ b/proxy/loopback/loopback.go @@ -26,6 +26,7 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } + outbound.Name = "loopback" destination := outbound.Target newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx)) diff --git a/proxy/mtproto/auth.go b/proxy/mtproto/auth.go deleted file mode 100644 index cdd66d6f844c..000000000000 --- a/proxy/mtproto/auth.go +++ /dev/null @@ -1,148 +0,0 @@ -package mtproto - -import ( - "context" - "crypto/rand" - "crypto/sha256" - "io" - "sync" - - "github.com/xtls/xray-core/common" -) - -const ( - HeaderSize = 64 -) - -type SessionContext struct { - ConnectionType [4]byte - DataCenterID uint16 -} - -func DefaultSessionContext() SessionContext { - return SessionContext{ - ConnectionType: [4]byte{0xef, 0xef, 0xef, 0xef}, - DataCenterID: 0, - } -} - -type contextKey int32 - -const ( - sessionContextKey contextKey = iota -) - -func ContextWithSessionContext(ctx context.Context, c SessionContext) context.Context { - return context.WithValue(ctx, sessionContextKey, c) -} - -func SessionContextFromContext(ctx context.Context) SessionContext { - if c := ctx.Value(sessionContextKey); c != nil { - return c.(SessionContext) - } - return DefaultSessionContext() -} - -type Authentication struct { - Header [HeaderSize]byte - DecodingKey [32]byte - EncodingKey [32]byte - DecodingNonce [16]byte - EncodingNonce [16]byte -} - -func (a *Authentication) DataCenterID() uint16 { - x := ((int16(a.Header[61]) << 8) | int16(a.Header[60])) - if x < 0 { - x = -x - } - return uint16(x) - 1 -} - -func (a *Authentication) ConnectionType() [4]byte { - var x [4]byte - copy(x[:], a.Header[56:60]) - return x -} - -func (a *Authentication) ApplySecret(b []byte) { - a.DecodingKey = sha256.Sum256(append(a.DecodingKey[:], b...)) - a.EncodingKey = sha256.Sum256(append(a.EncodingKey[:], b...)) -} - -func generateRandomBytes(random []byte, connType [4]byte) { - for { - common.Must2(rand.Read(random)) - - if random[0] == 0xef { - continue - } - - val := (uint32(random[3]) << 24) | (uint32(random[2]) << 16) | (uint32(random[1]) << 8) | uint32(random[0]) - if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { - continue - } - - if (uint32(random[7])<<24)|(uint32(random[6])<<16)|(uint32(random[5])<<8)|uint32(random[4]) == 0x00000000 { - continue - } - - copy(random[56:60], connType[:]) - - return - } -} - -func NewAuthentication(sc SessionContext) *Authentication { - auth := getAuthenticationObject() - random := auth.Header[:] - generateRandomBytes(random, sc.ConnectionType) - copy(auth.EncodingKey[:], random[8:]) - copy(auth.EncodingNonce[:], random[8+32:]) - keyivInverse := Inverse(random[8 : 8+32+16]) - copy(auth.DecodingKey[:], keyivInverse) - copy(auth.DecodingNonce[:], keyivInverse[32:]) - return auth -} - -func ReadAuthentication(reader io.Reader) (*Authentication, error) { - auth := getAuthenticationObject() - - if _, err := io.ReadFull(reader, auth.Header[:]); err != nil { - putAuthenticationObject(auth) - return nil, err - } - - copy(auth.DecodingKey[:], auth.Header[8:]) - copy(auth.DecodingNonce[:], auth.Header[8+32:]) - keyivInverse := Inverse(auth.Header[8 : 8+32+16]) - copy(auth.EncodingKey[:], keyivInverse) - copy(auth.EncodingNonce[:], keyivInverse[32:]) - - return auth, nil -} - -// Inverse returns a new byte array. It is a sequence of bytes when the input is read from end to beginning.Inverse -// Visible for testing only. -func Inverse(b []byte) []byte { - lenb := len(b) - b2 := make([]byte, lenb) - for i, v := range b { - b2[lenb-i-1] = v - } - return b2 -} - -var authPool = sync.Pool{ - New: func() interface{} { - return new(Authentication) - }, -} - -func getAuthenticationObject() *Authentication { - return authPool.Get().(*Authentication) -} - -func putAuthenticationObject(auth *Authentication) { - authPool.Put(auth) -} diff --git a/proxy/mtproto/auth_test.go b/proxy/mtproto/auth_test.go deleted file mode 100644 index a05bc7434496..000000000000 --- a/proxy/mtproto/auth_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package mtproto_test - -import ( - "bytes" - "crypto/rand" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/xtls/xray-core/common" - . "github.com/xtls/xray-core/proxy/mtproto" -) - -func TestInverse(t *testing.T) { - const size = 64 - b := make([]byte, 64) - for b[0] == b[size-1] { - common.Must2(rand.Read(b)) - } - - bi := Inverse(b) - if b[0] == bi[0] { - t.Fatal("seems bytes are not inversed: ", b[0], "vs", bi[0]) - } - - bii := Inverse(bi) - if r := cmp.Diff(bii, b); r != "" { - t.Fatal(r) - } -} - -func TestAuthenticationReadWrite(t *testing.T) { - a := NewAuthentication(DefaultSessionContext()) - b := bytes.NewReader(a.Header[:]) - a2, err := ReadAuthentication(b) - common.Must(err) - - if r := cmp.Diff(a.EncodingKey[:], a2.DecodingKey[:]); r != "" { - t.Error("decoding key: ", r) - } - - if r := cmp.Diff(a.EncodingNonce[:], a2.DecodingNonce[:]); r != "" { - t.Error("decoding nonce: ", r) - } - - if r := cmp.Diff(a.DecodingKey[:], a2.EncodingKey[:]); r != "" { - t.Error("encoding key: ", r) - } - - if r := cmp.Diff(a.DecodingNonce[:], a2.EncodingNonce[:]); r != "" { - t.Error("encoding nonce: ", r) - } -} diff --git a/proxy/mtproto/client.go b/proxy/mtproto/client.go deleted file mode 100644 index 6825fdaeb2f7..000000000000 --- a/proxy/mtproto/client.go +++ /dev/null @@ -1,76 +0,0 @@ -package mtproto - -import ( - "context" - - "github.com/xtls/xray-core/common" - "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/crypto" - "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/session" - "github.com/xtls/xray-core/common/task" - "github.com/xtls/xray-core/transport" - "github.com/xtls/xray-core/transport/internet" -) - -type Client struct{} - -func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { - return &Client{}, nil -} - -func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { - outbound := session.OutboundFromContext(ctx) - if outbound == nil || !outbound.Target.IsValid() { - return newError("unknown destination.") - } - dest := outbound.Target - if dest.Network != net.Network_TCP { - return newError("not TCP traffic", dest) - } - - conn, err := dialer.Dial(ctx, dest) - if err != nil { - return newError("failed to dial to ", dest).Base(err).AtWarning() - } - defer conn.Close() - - sc := SessionContextFromContext(ctx) - auth := NewAuthentication(sc) - defer putAuthenticationObject(auth) - - request := func() error { - encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:]) - - var header [HeaderSize]byte - encryptor.XORKeyStream(header[:], auth.Header[:]) - copy(header[:56], auth.Header[:]) - - if _, err := conn.Write(header[:]); err != nil { - return newError("failed to write auth header").Base(err) - } - - connWriter := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn)) - return buf.Copy(link.Reader, connWriter) - } - - response := func() error { - decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:]) - - connReader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn)) - return buf.Copy(connReader, link.Writer) - } - - responseDoneAndCloseWriter := task.OnSuccess(response, task.Close(link.Writer)) - if err := task.Run(ctx, request, responseDoneAndCloseWriter); err != nil { - return newError("connection ends").Base(err) - } - - return nil -} - -func init() { - common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { - return NewClient(ctx, config.(*ClientConfig)) - })) -} diff --git a/proxy/mtproto/config.go b/proxy/mtproto/config.go deleted file mode 100644 index fcd203d709f7..000000000000 --- a/proxy/mtproto/config.go +++ /dev/null @@ -1,24 +0,0 @@ -package mtproto - -import ( - "github.com/xtls/xray-core/common/protocol" -) - -func (a *Account) Equals(another protocol.Account) bool { - aa, ok := another.(*Account) - if !ok { - return false - } - - if len(a.Secret) != len(aa.Secret) { - return false - } - - for i, v := range a.Secret { - if v != aa.Secret[i] { - return false - } - } - - return true -} diff --git a/proxy/mtproto/config.pb.go b/proxy/mtproto/config.pb.go deleted file mode 100644 index 9f30cc559a64..000000000000 --- a/proxy/mtproto/config.pb.go +++ /dev/null @@ -1,272 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 -// source: proxy/mtproto/config.proto - -package mtproto - -import ( - protocol "github.com/xtls/xray-core/common/protocol" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Account struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Secret []byte `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` -} - -func (x *Account) Reset() { - *x = Account{} - if protoimpl.UnsafeEnabled { - mi := &file_proxy_mtproto_config_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Account) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Account) ProtoMessage() {} - -func (x *Account) ProtoReflect() protoreflect.Message { - mi := &file_proxy_mtproto_config_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Account.ProtoReflect.Descriptor instead. -func (*Account) Descriptor() ([]byte, []int) { - return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{0} -} - -func (x *Account) GetSecret() []byte { - if x != nil { - return x.Secret - } - return nil -} - -type ServerConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // User is a list of users that allowed to connect to this inbound. - // Although this is a repeated field, only the first user is effective for - // now. - User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` -} - -func (x *ServerConfig) Reset() { - *x = ServerConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_proxy_mtproto_config_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ServerConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ServerConfig) ProtoMessage() {} - -func (x *ServerConfig) ProtoReflect() protoreflect.Message { - mi := &file_proxy_mtproto_config_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead. -func (*ServerConfig) Descriptor() ([]byte, []int) { - return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{1} -} - -func (x *ServerConfig) GetUser() []*protocol.User { - if x != nil { - return x.User - } - return nil -} - -type ClientConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *ClientConfig) Reset() { - *x = ClientConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_proxy_mtproto_config_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ClientConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ClientConfig) ProtoMessage() {} - -func (x *ClientConfig) ProtoReflect() protoreflect.Message { - mi := &file_proxy_mtproto_config_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead. -func (*ClientConfig) Descriptor() ([]byte, []int) { - return file_proxy_mtproto_config_proto_rawDescGZIP(), []int{2} -} - -var File_proxy_mtproto_config_proto protoreflect.FileDescriptor - -var file_proxy_mtproto_config_proto_rawDesc = []byte{ - 0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x78, 0x72, - 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x1a, 0x1a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x21, 0x0a, 0x07, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, - 0x3e, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x2e, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, - 0x0e, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, - 0x58, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x2e, 0x6d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, - 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x6d, 0x74, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, - 0x79, 0x2e, 0x4d, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -} - -var ( - file_proxy_mtproto_config_proto_rawDescOnce sync.Once - file_proxy_mtproto_config_proto_rawDescData = file_proxy_mtproto_config_proto_rawDesc -) - -func file_proxy_mtproto_config_proto_rawDescGZIP() []byte { - file_proxy_mtproto_config_proto_rawDescOnce.Do(func() { - file_proxy_mtproto_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_mtproto_config_proto_rawDescData) - }) - return file_proxy_mtproto_config_proto_rawDescData -} - -var file_proxy_mtproto_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_proxy_mtproto_config_proto_goTypes = []interface{}{ - (*Account)(nil), // 0: xray.proxy.mtproto.Account - (*ServerConfig)(nil), // 1: xray.proxy.mtproto.ServerConfig - (*ClientConfig)(nil), // 2: xray.proxy.mtproto.ClientConfig - (*protocol.User)(nil), // 3: xray.common.protocol.User -} -var file_proxy_mtproto_config_proto_depIdxs = []int32{ - 3, // 0: xray.proxy.mtproto.ServerConfig.user:type_name -> xray.common.protocol.User - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_proxy_mtproto_config_proto_init() } -func file_proxy_mtproto_config_proto_init() { - if File_proxy_mtproto_config_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_proxy_mtproto_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Account); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proxy_mtproto_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proxy_mtproto_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClientConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_proxy_mtproto_config_proto_rawDesc, - NumEnums: 0, - NumMessages: 3, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_proxy_mtproto_config_proto_goTypes, - DependencyIndexes: file_proxy_mtproto_config_proto_depIdxs, - MessageInfos: file_proxy_mtproto_config_proto_msgTypes, - }.Build() - File_proxy_mtproto_config_proto = out.File - file_proxy_mtproto_config_proto_rawDesc = nil - file_proxy_mtproto_config_proto_goTypes = nil - file_proxy_mtproto_config_proto_depIdxs = nil -} diff --git a/proxy/mtproto/config.proto b/proxy/mtproto/config.proto deleted file mode 100644 index 65997bc00fda..000000000000 --- a/proxy/mtproto/config.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package xray.proxy.mtproto; -option csharp_namespace = "Xray.Proxy.Mtproto"; -option go_package = "github.com/xtls/xray-core/proxy/mtproto"; -option java_package = "com.xray.proxy.mtproto"; -option java_multiple_files = true; - -import "common/protocol/user.proto"; - -message Account { - bytes secret = 1; -} - -message ServerConfig { - // User is a list of users that allowed to connect to this inbound. - // Although this is a repeated field, only the first user is effective for - // now. - repeated xray.common.protocol.User user = 1; -} - -message ClientConfig {} diff --git a/proxy/mtproto/mtproto.go b/proxy/mtproto/mtproto.go deleted file mode 100644 index af7983a5cf62..000000000000 --- a/proxy/mtproto/mtproto.go +++ /dev/null @@ -1,3 +0,0 @@ -package mtproto - -//go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/proxy/mtproto/server.go b/proxy/mtproto/server.go deleted file mode 100644 index 2079df1fa414..000000000000 --- a/proxy/mtproto/server.go +++ /dev/null @@ -1,160 +0,0 @@ -package mtproto - -import ( - "bytes" - "context" - "time" - - "github.com/xtls/xray-core/common" - "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/crypto" - "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/session" - "github.com/xtls/xray-core/common/signal" - "github.com/xtls/xray-core/common/task" - "github.com/xtls/xray-core/core" - "github.com/xtls/xray-core/features/policy" - "github.com/xtls/xray-core/features/routing" - "github.com/xtls/xray-core/transport/internet/stat" -) - -var dcList = []net.Address{ - net.ParseAddress("149.154.175.50"), - net.ParseAddress("149.154.167.51"), - net.ParseAddress("149.154.175.100"), - net.ParseAddress("149.154.167.91"), - net.ParseAddress("149.154.171.5"), -} - -type Server struct { - user *protocol.User - account *Account - policy policy.Manager -} - -func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { - if len(config.User) == 0 { - return nil, newError("no user configured.") - } - - user := config.User[0] - rawAccount, err := config.User[0].GetTypedAccount() - if err != nil { - return nil, newError("invalid account").Base(err) - } - account, ok := rawAccount.(*Account) - if !ok { - return nil, newError("not a MTProto account") - } - - v := core.MustFromContext(ctx) - - return &Server{ - user: user, - account: account, - policy: v.GetFeature(policy.ManagerType()).(policy.Manager), - }, nil -} - -func (s *Server) Network() []net.Network { - return []net.Network{net.Network_TCP} -} - -var ( - ctype1 = []byte{0xef, 0xef, 0xef, 0xef} - ctype2 = []byte{0xee, 0xee, 0xee, 0xee} -) - -func isValidConnectionType(c [4]byte) bool { - if bytes.Equal(c[:], ctype1) { - return true - } - if bytes.Equal(c[:], ctype2) { - return true - } - return false -} - -func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { - sPolicy := s.policy.ForLevel(s.user.Level) - - if err := conn.SetDeadline(time.Now().Add(sPolicy.Timeouts.Handshake)); err != nil { - newError("failed to set deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - auth, err := ReadAuthentication(conn) - if err != nil { - return newError("failed to read authentication header").Base(err) - } - defer putAuthenticationObject(auth) - - if err := conn.SetDeadline(time.Time{}); err != nil { - newError("failed to clear deadline").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - - auth.ApplySecret(s.account.Secret) - - decryptor := crypto.NewAesCTRStream(auth.DecodingKey[:], auth.DecodingNonce[:]) - decryptor.XORKeyStream(auth.Header[:], auth.Header[:]) - - ct := auth.ConnectionType() - if !isValidConnectionType(ct) { - return newError("invalid connection type: ", ct) - } - - dcID := auth.DataCenterID() - if dcID >= uint16(len(dcList)) { - return newError("invalid datacenter id: ", dcID) - } - - dest := net.Destination{ - Network: net.Network_TCP, - Address: dcList[dcID], - Port: net.Port(443), - } - - ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sPolicy.Timeouts.ConnectionIdle) - ctx = policy.ContextWithBufferPolicy(ctx, sPolicy.Buffer) - - sc := SessionContext{ - ConnectionType: ct, - DataCenterID: dcID, - } - ctx = ContextWithSessionContext(ctx, sc) - - link, err := dispatcher.Dispatch(ctx, dest) - if err != nil { - return newError("failed to dispatch request to: ", dest).Base(err) - } - - request := func() error { - defer timer.SetTimeout(sPolicy.Timeouts.DownlinkOnly) - - reader := buf.NewReader(crypto.NewCryptionReader(decryptor, conn)) - return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) - } - - response := func() error { - defer timer.SetTimeout(sPolicy.Timeouts.UplinkOnly) - - encryptor := crypto.NewAesCTRStream(auth.EncodingKey[:], auth.EncodingNonce[:]) - writer := buf.NewWriter(crypto.NewCryptionWriter(encryptor, conn)) - return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) - } - - responseDoneAndCloseWriter := task.OnSuccess(response, task.Close(link.Writer)) - if err := task.Run(ctx, request, responseDoneAndCloseWriter); err != nil { - common.Interrupt(link.Reader) - common.Interrupt(link.Writer) - return newError("connection ends").Base(err) - } - - return nil -} - -func init() { - common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { - return NewServer(ctx, config.(*ServerConfig)) - })) -} diff --git a/proxy/proxy.go b/proxy/proxy.go index fb52605ca5fa..f07af8c37c6d 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -6,14 +6,53 @@ package proxy import ( + "bytes" "context" + "crypto/rand" + gotls "crypto/tls" + "io" + "math/big" + "runtime" + "strconv" + "github.com/pires/go-proxyproto" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/features/routing" + "github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" + "github.com/xtls/xray-core/transport/internet/tls" +) + +var ( + Tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04} + TlsClientHandShakeStart = []byte{0x16, 0x03} + TlsServerHandShakeStart = []byte{0x16, 0x03, 0x03} + TlsApplicationDataStart = []byte{0x17, 0x03, 0x03} + + Tls13CipherSuiteDic = map[uint16]string{ + 0x1301: "TLS_AES_128_GCM_SHA256", + 0x1302: "TLS_AES_256_GCM_SHA384", + 0x1303: "TLS_CHACHA20_POLY1305_SHA256", + 0x1304: "TLS_AES_128_CCM_SHA256", + 0x1305: "TLS_AES_128_CCM_8_SHA256", + } +) + +const ( + TlsHandshakeTypeClientHello byte = 0x01 + TlsHandshakeTypeServerHello byte = 0x02 + + CommandPaddingContinue byte = 0x00 + CommandPaddingEnd byte = 0x01 + CommandPaddingDirect byte = 0x02 ) // An Inbound processes inbound connections. @@ -47,3 +86,430 @@ type GetInbound interface { type GetOutbound interface { GetOutbound() Outbound } + +// TrafficState is used to track uplink and downlink of one connection +// It is used by XTLS to determine if switch to raw copy mode, It is used by Vision to calculate padding +type TrafficState struct { + UserUUID []byte + NumberOfPacketToFilter int + EnableXtls bool + IsTLS12orAbove bool + IsTLS bool + Cipher uint16 + RemainingServerHello int32 + + // reader link state + WithinPaddingBuffers bool + ReaderSwitchToDirectCopy bool + RemainingCommand int32 + RemainingContent int32 + RemainingPadding int32 + CurrentCommand int + + // write link state + IsPadding bool + WriterSwitchToDirectCopy bool +} + +func NewTrafficState(userUUID []byte) *TrafficState { + return &TrafficState{ + UserUUID: userUUID, + NumberOfPacketToFilter: 8, + EnableXtls: false, + IsTLS12orAbove: false, + IsTLS: false, + Cipher: 0, + RemainingServerHello: -1, + WithinPaddingBuffers: true, + ReaderSwitchToDirectCopy: false, + RemainingCommand: -1, + RemainingContent: -1, + RemainingPadding: -1, + CurrentCommand: 0, + IsPadding: true, + WriterSwitchToDirectCopy: false, + } +} + +// VisionReader is used to read xtls vision protocol +// Note Vision probably only make sense as the inner most layer of reader, since it need assess traffic state from origin proxy traffic +type VisionReader struct { + buf.Reader + trafficState *TrafficState + ctx context.Context +} + +func NewVisionReader(reader buf.Reader, state *TrafficState, context context.Context) *VisionReader { + return &VisionReader{ + Reader: reader, + trafficState: state, + ctx: context, + } +} + +func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { + buffer, err := w.Reader.ReadMultiBuffer() + if !buffer.IsEmpty() { + if w.trafficState.WithinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 { + mb2 := make(buf.MultiBuffer, 0, len(buffer)) + for _, b := range buffer { + newbuffer := XtlsUnpadding(b, w.trafficState, w.ctx) + if newbuffer.Len() > 0 { + mb2 = append(mb2, newbuffer) + } + } + buffer = mb2 + if w.trafficState.RemainingContent > 0 || w.trafficState.RemainingPadding > 0 || w.trafficState.CurrentCommand == 0 { + w.trafficState.WithinPaddingBuffers = true + } else if w.trafficState.CurrentCommand == 1 { + w.trafficState.WithinPaddingBuffers = false + } else if w.trafficState.CurrentCommand == 2 { + w.trafficState.WithinPaddingBuffers = false + w.trafficState.ReaderSwitchToDirectCopy = true + } else { + newError("XtlsRead unknown command ", w.trafficState.CurrentCommand, buffer.Len()).WriteToLog(session.ExportIDToError(w.ctx)) + } + } + if w.trafficState.NumberOfPacketToFilter > 0 { + XtlsFilterTls(buffer, w.trafficState, w.ctx) + } + } + return buffer, err +} + +// VisionWriter is used to write xtls vision protocol +// Note Vision probably only make sense as the inner most layer of writer, since it need assess traffic state from origin proxy traffic +type VisionWriter struct { + buf.Writer + trafficState *TrafficState + ctx context.Context + writeOnceUserUUID []byte +} + +func NewVisionWriter(writer buf.Writer, state *TrafficState, context context.Context) *VisionWriter { + w := make([]byte, len(state.UserUUID)) + copy(w, state.UserUUID) + return &VisionWriter{ + Writer: writer, + trafficState: state, + ctx: context, + writeOnceUserUUID: w, + } +} + +func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + if w.trafficState.NumberOfPacketToFilter > 0 { + XtlsFilterTls(mb, w.trafficState, w.ctx) + } + if w.trafficState.IsPadding { + if len(mb) == 1 && mb[0] == nil { + mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header + return w.Writer.WriteMultiBuffer(mb) + } + mb = ReshapeMultiBuffer(w.ctx, mb) + longPadding := w.trafficState.IsTLS + for i, b := range mb { + if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) { + if w.trafficState.EnableXtls { + w.trafficState.WriterSwitchToDirectCopy = true + } + var command byte = CommandPaddingContinue + if i == len(mb) - 1 { + command = CommandPaddingEnd + if w.trafficState.EnableXtls { + command = CommandPaddingDirect + } + } + mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx) + w.trafficState.IsPadding = false // padding going to end + longPadding = false + continue + } else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early + w.trafficState.IsPadding = false + mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx) + break + } + var command byte = CommandPaddingContinue + if i == len(mb) - 1 && !w.trafficState.IsPadding { + command = CommandPaddingEnd + if w.trafficState.EnableXtls { + command = CommandPaddingDirect + } + } + mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx) + } + } + return w.Writer.WriteMultiBuffer(mb) +} + +// ReshapeMultiBuffer prepare multi buffer for padding stucture (max 21 bytes) +func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer { + needReshape := 0 + for _, b := range buffer { + if b.Len() >= buf.Size-21 { + needReshape += 1 + } + } + if needReshape == 0 { + return buffer + } + mb2 := make(buf.MultiBuffer, 0, len(buffer)+needReshape) + toPrint := "" + for i, buffer1 := range buffer { + if buffer1.Len() >= buf.Size-21 { + index := int32(bytes.LastIndex(buffer1.Bytes(), TlsApplicationDataStart)) + if index <= 0 || index > buf.Size-21 { + index = buf.Size / 2 + } + buffer2 := buf.New() + buffer2.Write(buffer1.BytesFrom(index)) + buffer1.Resize(0, index) + mb2 = append(mb2, buffer1, buffer2) + toPrint += " " + strconv.Itoa(int(buffer1.Len())) + " " + strconv.Itoa(int(buffer2.Len())) + } else { + mb2 = append(mb2, buffer1) + toPrint += " " + strconv.Itoa(int(buffer1.Len())) + } + buffer[i] = nil + } + buffer = buffer[:0] + newError("ReshapeMultiBuffer ", toPrint).WriteToLog(session.ExportIDToError(ctx)) + return mb2 +} + +// XtlsPadding add padding to eliminate length siganature during tls handshake +func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer { + var contentLen int32 = 0 + var paddingLen int32 = 0 + if b != nil { + contentLen = b.Len() + } + if contentLen < 900 && longPadding { + l, err := rand.Int(rand.Reader, big.NewInt(500)) + if err != nil { + newError("failed to generate padding").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + paddingLen = int32(l.Int64()) + 900 - contentLen + } else { + l, err := rand.Int(rand.Reader, big.NewInt(256)) + if err != nil { + newError("failed to generate padding").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + paddingLen = int32(l.Int64()) + } + if paddingLen > buf.Size-21-contentLen { + paddingLen = buf.Size - 21 - contentLen + } + newbuffer := buf.New() + if userUUID != nil { + newbuffer.Write(*userUUID) + *userUUID = nil + } + newbuffer.Write([]byte{command, byte(contentLen >> 8), byte(contentLen), byte(paddingLen >> 8), byte(paddingLen)}) + if b != nil { + newbuffer.Write(b.Bytes()) + b.Release() + b = nil + } + newbuffer.Extend(paddingLen) + newError("XtlsPadding ", contentLen, " ", paddingLen, " ", command).WriteToLog(session.ExportIDToError(ctx)) + return newbuffer +} + +// XtlsUnpadding remove padding and parse command +func XtlsUnpadding(b *buf.Buffer, s *TrafficState, ctx context.Context) *buf.Buffer { + if s.RemainingCommand == -1 && s.RemainingContent == -1 && s.RemainingPadding == -1 { // inital state + if b.Len() >= 21 && bytes.Equal(s.UserUUID, b.BytesTo(16)) { + b.Advance(16) + s.RemainingCommand = 5 + } else { + return b + } + } + newbuffer := buf.New() + for b.Len() > 0 { + if s.RemainingCommand > 0 { + data, err := b.ReadByte() + if err != nil { + return newbuffer + } + switch s.RemainingCommand { + case 5: + s.CurrentCommand = int(data) + case 4: + s.RemainingContent = int32(data)<<8 + case 3: + s.RemainingContent = s.RemainingContent | int32(data) + case 2: + s.RemainingPadding = int32(data)<<8 + case 1: + s.RemainingPadding = s.RemainingPadding | int32(data) + newError("Xtls Unpadding new block, content ", s.RemainingContent, " padding ", s.RemainingPadding, " command ", s.CurrentCommand).WriteToLog(session.ExportIDToError(ctx)) + } + s.RemainingCommand-- + } else if s.RemainingContent > 0 { + len := s.RemainingContent + if b.Len() < len { + len = b.Len() + } + data, err := b.ReadBytes(len) + if err != nil { + return newbuffer + } + newbuffer.Write(data) + s.RemainingContent -= len + } else { // remainingPadding > 0 + len := s.RemainingPadding + if b.Len() < len { + len = b.Len() + } + b.Advance(len) + s.RemainingPadding -= len + } + if s.RemainingCommand <= 0 && s.RemainingContent <= 0 && s.RemainingPadding <= 0 { // this block done + if s.CurrentCommand == 0 { + s.RemainingCommand = 5 + } else { + s.RemainingCommand = -1 // set to initial state + s.RemainingContent = -1 + s.RemainingPadding = -1 + if b.Len() > 0 { // shouldn't happen + newbuffer.Write(b.Bytes()) + } + break + } + } + } + b.Release() + b = nil + return newbuffer +} + +// XtlsFilterTls filter and recognize tls 1.3 and other info +func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx context.Context) { + for _, b := range buffer { + if b == nil { + continue + } + trafficState.NumberOfPacketToFilter-- + if b.Len() >= 6 { + startsBytes := b.BytesTo(6) + if bytes.Equal(TlsServerHandShakeStart, startsBytes[:3]) && startsBytes[5] == TlsHandshakeTypeServerHello { + trafficState.RemainingServerHello = (int32(startsBytes[3])<<8 | int32(startsBytes[4])) + 5 + trafficState.IsTLS12orAbove = true + trafficState.IsTLS = true + if b.Len() >= 79 && trafficState.RemainingServerHello >= 79 { + sessionIdLen := int32(b.Byte(43)) + cipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3) + trafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1]) + } else { + newError("XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello).WriteToLog(session.ExportIDToError(ctx)) + } + } else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello { + trafficState.IsTLS = true + newError("XtlsFilterTls found tls client hello! ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx)) + } + } + if trafficState.RemainingServerHello > 0 { + end := trafficState.RemainingServerHello + if end > b.Len() { + end = b.Len() + } + trafficState.RemainingServerHello -= b.Len() + if bytes.Contains(b.BytesTo(end), Tls13SupportedVersions) { + v, ok := Tls13CipherSuiteDic[trafficState.Cipher] + if !ok { + v = "Old cipher: " + strconv.FormatUint(uint64(trafficState.Cipher), 16) + } else if v != "TLS_AES_128_CCM_8_SHA256" { + trafficState.EnableXtls = true + } + newError("XtlsFilterTls found tls 1.3! ", b.Len(), " ", v).WriteToLog(session.ExportIDToError(ctx)) + trafficState.NumberOfPacketToFilter = 0 + return + } else if trafficState.RemainingServerHello <= 0 { + newError("XtlsFilterTls found tls 1.2! ", b.Len()).WriteToLog(session.ExportIDToError(ctx)) + trafficState.NumberOfPacketToFilter = 0 + return + } + newError("XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello).WriteToLog(session.ExportIDToError(ctx)) + } + if trafficState.NumberOfPacketToFilter <= 0 { + newError("XtlsFilterTls stop filtering", buffer.Len()).WriteToLog(session.ExportIDToError(ctx)) + } + } +} + +// UnwrapRawConn support unwrap stats, tls, utls, reality and proxyproto conn and get raw tcp conn from it +func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { + var readCounter, writerCounter stats.Counter + if conn != nil { + statConn, ok := conn.(*stat.CounterConnection) + if ok { + conn = statConn.Connection + readCounter = statConn.ReadCounter + writerCounter = statConn.WriteCounter + } + if xc, ok := conn.(*gotls.Conn); ok { + conn = xc.NetConn() + } else if utlsConn, ok := conn.(*tls.UConn); ok { + conn = utlsConn.NetConn() + } else if realityConn, ok := conn.(*reality.Conn); ok { + conn = realityConn.NetConn() + } else if realityUConn, ok := conn.(*reality.UConn); ok { + conn = realityUConn.NetConn() + } + if pc, ok := conn.(*proxyproto.Conn); ok { + conn = pc.Raw() + // 8192 > 4096, there is no need to process pc's bufReader + } + } + return conn, readCounter, writerCounter +} + +// CopyRawConnIfExist use the most efficient copy method. +// - If caller don't want to turn on splice, do not pass in both reader conn and writer conn +// - writer are from *transport.Link +func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net.Conn, writer buf.Writer, timer signal.ActivityUpdater) error { + readerConn, readCounter, _ := UnwrapRawConn(readerConn) + writerConn, _, writeCounter := UnwrapRawConn(writerConn) + reader := buf.NewReader(readerConn) + if inbound := session.InboundFromContext(ctx); inbound != nil { + if tc, ok := writerConn.(*net.TCPConn); ok && readerConn != nil && writerConn != nil && (runtime.GOOS == "linux" || runtime.GOOS == "android") { + for inbound.CanSpliceCopy != 3 { + if inbound.CanSpliceCopy == 1 { + newError("CopyRawConn splice").WriteToLog(session.ExportIDToError(ctx)) + runtime.Gosched() // necessary + w, err := tc.ReadFrom(readerConn) + if readCounter != nil { + readCounter.Add(w) + } + if writeCounter != nil { + writeCounter.Add(w) + } + if err != nil && errors.Cause(err) != io.EOF { + return err + } + return nil + } + buffer, err := reader.ReadMultiBuffer() + if !buffer.IsEmpty() { + if readCounter != nil { + readCounter.Add(int64(buffer.Len())) + } + timer.Update() + if werr := writer.WriteMultiBuffer(buffer); werr != nil { + return werr + } + } + if err != nil { + return err + } + } + } + } + newError("CopyRawConn readv").WriteToLog(session.ExportIDToError(ctx)) + if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil { + return newError("failed to process response").Base(err) + } + return nil +} diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index 2d8a4e81a32e..57d8f81c7c6c 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -53,6 +53,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified") } + outbound.Name = "shadowsocks" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(3) + } destination := outbound.Target network := destination.Network @@ -96,9 +101,24 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } request.User = user + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } + sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) + + if newCtx != nil { + ctx = newCtx + } if request.Command == protocol.RequestCommandTCP { requestDone := func() error { diff --git a/proxy/shadowsocks/config.pb.go b/proxy/shadowsocks/config.pb.go index bd44bed7a997..5fc221da6ed3 100644 --- a/proxy/shadowsocks/config.pb.go +++ b/proxy/shadowsocks/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/shadowsocks/config.proto package shadowsocks diff --git a/proxy/shadowsocks/protocol.go b/proxy/shadowsocks/protocol.go index 3176d118c5bb..3a0c7e2222a2 100644 --- a/proxy/shadowsocks/protocol.go +++ b/proxy/shadowsocks/protocol.go @@ -4,6 +4,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/sha256" + "errors" "hash/crc32" "io" @@ -236,37 +237,37 @@ func EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buff } func DecodeUDPPacket(validator *Validator, payload *buf.Buffer) (*protocol.RequestHeader, *buf.Buffer, error) { - bs := payload.Bytes() - if len(bs) <= 32 { - return nil, nil, newError("len(bs) <= 32") - } + rawPayload := payload.Bytes() + user, _, d, _, err := validator.Get(rawPayload, protocol.RequestCommandUDP) - user, _, d, _, err := validator.Get(bs, protocol.RequestCommandUDP) - switch err { - case ErrIVNotUnique: + if errors.Is(err, ErrIVNotUnique) { return nil, nil, newError("failed iv check").Base(err) - case ErrNotFound: + } + + if errors.Is(err, ErrNotFound) { return nil, nil, newError("failed to match an user").Base(err) - default: - account := user.Account.(*MemoryAccount) - if account.Cipher.IsAEAD() { - payload.Clear() - payload.Write(d) - } else { - if account.Cipher.IVSize() > 0 { - iv := make([]byte, account.Cipher.IVSize()) - copy(iv, payload.BytesTo(account.Cipher.IVSize())) - } - if err = account.Cipher.DecodePacket(account.Key, payload); err != nil { - return nil, nil, newError("failed to decrypt UDP payload").Base(err) - } - } } - request := &protocol.RequestHeader{ - Version: Version, - User: user, - Command: protocol.RequestCommandUDP, + if err != nil { + return nil, nil, newError("unexpected error").Base(err) + } + + account, ok := user.Account.(*MemoryAccount) + if !ok { + return nil, nil, newError("expected MemoryAccount returned from validator") + } + + if account.Cipher.IsAEAD() { + payload.Clear() + payload.Write(d) + } else { + if account.Cipher.IVSize() > 0 { + iv := make([]byte, account.Cipher.IVSize()) + copy(iv, payload.BytesTo(account.Cipher.IVSize())) + } + if err = account.Cipher.DecodePacket(account.Key, payload); err != nil { + return nil, nil, newError("failed to decrypt UDP payload").Base(err) + } } payload.SetByte(0, payload.Byte(0)&0x0F) @@ -276,8 +277,13 @@ func DecodeUDPPacket(validator *Validator, payload *buf.Buffer) (*protocol.Reque return nil, nil, newError("failed to parse address").Base(err) } - request.Address = addr - request.Port = port + request := &protocol.RequestHeader{ + Version: Version, + User: user, + Command: protocol.RequestCommandUDP, + Address: addr, + Port: port, + } return request, payload, nil } diff --git a/proxy/shadowsocks/protocol_test.go b/proxy/shadowsocks/protocol_test.go index e1b6495ef107..4083905d97fa 100644 --- a/proxy/shadowsocks/protocol_test.go +++ b/proxy/shadowsocks/protocol_test.go @@ -23,37 +23,80 @@ func equalRequestHeader(x, y *protocol.RequestHeader) bool { })) } -func TestUDPEncoding(t *testing.T) { - request := &protocol.RequestHeader{ - Version: Version, - Command: protocol.RequestCommandUDP, - Address: net.LocalHostIP, - Port: 1234, - User: &protocol.MemoryUser{ - Email: "love@example.com", - Account: toAccount(&Account{ - Password: "password", - CipherType: CipherType_AES_128_GCM, - }), +func TestUDPEncodingDecoding(t *testing.T) { + testRequests := []protocol.RequestHeader{ + { + Version: Version, + Command: protocol.RequestCommandUDP, + Address: net.LocalHostIP, + Port: 1234, + User: &protocol.MemoryUser{ + Email: "love@example.com", + Account: toAccount(&Account{ + Password: "password", + CipherType: CipherType_AES_128_GCM, + }), + }, + }, + { + Version: Version, + Command: protocol.RequestCommandUDP, + Address: net.LocalHostIP, + Port: 1234, + User: &protocol.MemoryUser{ + Email: "love@example.com", + Account: toAccount(&Account{ + Password: "123", + CipherType: CipherType_NONE, + }), + }, }, } - data := buf.New() - common.Must2(data.WriteString("test string")) - encodedData, err := EncodeUDPPacket(request, data.Bytes()) - common.Must(err) + for _, request := range testRequests { + data := buf.New() + common.Must2(data.WriteString("test string")) + encodedData, err := EncodeUDPPacket(&request, data.Bytes()) + common.Must(err) - validator := new(Validator) - validator.Add(request.User) - decodedRequest, decodedData, err := DecodeUDPPacket(validator, encodedData) - common.Must(err) + validator := new(Validator) + validator.Add(request.User) + decodedRequest, decodedData, err := DecodeUDPPacket(validator, encodedData) + common.Must(err) - if r := cmp.Diff(decodedData.Bytes(), data.Bytes()); r != "" { - t.Error("data: ", r) + if r := cmp.Diff(decodedData.Bytes(), data.Bytes()); r != "" { + t.Error("data: ", r) + } + + if equalRequestHeader(decodedRequest, &request) == false { + t.Error("different request") + } } +} - if equalRequestHeader(decodedRequest, request) == false { - t.Error("different request") +func TestUDPDecodingWithPayloadTooShort(t *testing.T) { + testAccounts := []protocol.Account{ + toAccount(&Account{ + Password: "password", + CipherType: CipherType_AES_128_GCM, + }), + toAccount(&Account{ + Password: "password", + CipherType: CipherType_NONE, + }), + } + + for _, account := range testAccounts { + data := buf.New() + data.WriteString("short payload") + validator := new(Validator) + validator.Add(&protocol.MemoryUser{ + Account: account, + }) + _, _, err := DecodeUDPPacket(validator, data) + if err == nil { + t.Fatal("expected error") + } } } diff --git a/proxy/shadowsocks/server.go b/proxy/shadowsocks/server.go index 140c6704a3fb..2975ba70aec0 100644 --- a/proxy/shadowsocks/server.go +++ b/proxy/shadowsocks/server.go @@ -71,6 +71,10 @@ func (s *Server) Network() []net.Network { } func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { + inbound := session.InboundFromContext(ctx) + inbound.Name = "shadowsocks" + inbound.SetCanSpliceCopy(3) + switch network { case net.Network_TCP: return s.handleConnection(ctx, conn, dispatcher) @@ -110,12 +114,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis }) inbound := session.InboundFromContext(ctx) - if inbound == nil { - panic("no inbound metadata") - } - var dest *net.Destination - reader := buf.NewPacketReader(conn) for { mpayload, err := reader.ReadMultiBuffer() diff --git a/proxy/shadowsocks/validator.go b/proxy/shadowsocks/validator.go index 2aa62e068d37..8888a1c084b4 100644 --- a/proxy/shadowsocks/validator.go +++ b/proxy/shadowsocks/validator.go @@ -80,6 +80,11 @@ func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol for _, user := range v.users { if account := user.Account.(*MemoryAccount); account.Cipher.IsAEAD() { + // AEAD payload decoding requires the payload to be over 32 bytes + if len(bs) < 32 { + continue + } + aeadCipher := account.Cipher.(*AEADCipher) ivLen = aeadCipher.IVSize() iv := bs[:ivLen] diff --git a/proxy/shadowsocks_2022/config.pb.go b/proxy/shadowsocks_2022/config.pb.go index ff3fae7d7f65..8ccb848a946a 100644 --- a/proxy/shadowsocks_2022/config.pb.go +++ b/proxy/shadowsocks_2022/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/shadowsocks_2022/config.proto package shadowsocks_2022 @@ -389,11 +389,12 @@ type ClientConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` - Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` - Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` - UdpOverTcp bool `protobuf:"varint,5,opt,name=udp_over_tcp,json=udpOverTcp,proto3" json:"udp_over_tcp,omitempty"` + Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` + Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` + UdpOverTcp bool `protobuf:"varint,5,opt,name=udp_over_tcp,json=udpOverTcp,proto3" json:"udp_over_tcp,omitempty"` + UdpOverTcpVersion uint32 `protobuf:"varint,6,opt,name=udp_over_tcp_version,json=udpOverTcpVersion,proto3" json:"udp_over_tcp_version,omitempty"` } func (x *ClientConfig) Reset() { @@ -463,6 +464,13 @@ func (x *ClientConfig) GetUdpOverTcp() bool { return false } +func (x *ClientConfig) GetUdpOverTcpVersion() uint32 { + if x != nil { + return x.UdpOverTcpVersion + } + return 0 +} + var File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{ @@ -520,7 +528,7 @@ var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{ 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xa5, 0x01, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xd6, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, @@ -531,15 +539,18 @@ var file_proxy_shadowsocks_2022_config_proto_rawDesc = []byte{ 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x64, 0x70, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x63, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x64, 0x70, 0x4f, 0x76, - 0x65, 0x72, 0x54, 0x63, 0x70, 0x42, 0x72, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, - 0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, - 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, - 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0xaa, 0x02, 0x1a, 0x58, - 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, - 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x32, 0x30, 0x32, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x65, 0x72, 0x54, 0x63, 0x70, 0x12, 0x2f, 0x0a, 0x14, 0x75, 0x64, 0x70, 0x5f, 0x6f, 0x76, 0x65, + 0x72, 0x5f, 0x74, 0x63, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x11, 0x75, 0x64, 0x70, 0x4f, 0x76, 0x65, 0x72, 0x54, 0x63, 0x70, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, + 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, + 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, + 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x32, 0x30, 0x32, 0x32, 0xaa, 0x02, 0x1a, + 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, + 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x32, 0x30, 0x32, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/proxy/shadowsocks_2022/config.proto b/proxy/shadowsocks_2022/config.proto index 2c4690e45ffc..605409913cb2 100644 --- a/proxy/shadowsocks_2022/config.proto +++ b/proxy/shadowsocks_2022/config.proto @@ -51,4 +51,5 @@ message ClientConfig { string method = 3; string key = 4; bool udp_over_tcp = 5; + uint32 udp_over_tcp_version = 6; } diff --git a/proxy/shadowsocks_2022/inbound.go b/proxy/shadowsocks_2022/inbound.go index 52b0a798e050..246fc7f16ea2 100644 --- a/proxy/shadowsocks_2022/inbound.go +++ b/proxy/shadowsocks_2022/inbound.go @@ -3,7 +3,7 @@ package shadowsocks_2022 import ( "context" - "github.com/sagernet/sing-shadowsocks" + shadowsocks "github.com/sagernet/sing-shadowsocks" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" C "github.com/sagernet/sing/common" B "github.com/sagernet/sing/common/buf" @@ -11,13 +11,13 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" ) @@ -51,7 +51,7 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Inbound, error) { if !C.Contains(shadowaead_2022.List, config.Method) { return nil, newError("unsupported method ", config.Method) } - service, err := shadowaead_2022.NewServiceWithPassword(config.Method, config.Key, 500, inbound) + service, err := shadowaead_2022.NewServiceWithPassword(config.Method, config.Key, 500, inbound, nil) if err != nil { return nil, newError("create service").Base(err) } @@ -65,6 +65,8 @@ func (i *Inbound) Network() []net.Network { func (i *Inbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { inbound := session.InboundFromContext(ctx) + inbound.Name = "shadowsocks-2022" + inbound.SetCanSpliceCopy(3) var metadata M.Metadata if inbound.Source.IsValid() { @@ -74,7 +76,7 @@ func (i *Inbound) Process(ctx context.Context, network net.Network, connection s ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return returnError(i.service.NewConnection(ctx, connection, metadata)) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} @@ -82,7 +84,7 @@ func (i *Inbound) Process(ctx context.Context, network net.Network, connection s mb, err := reader.ReadMultiBuffer() if err != nil { buf.ReleaseMulti(mb) - return returnError(err) + return singbridge.ReturnError(err) } for _, buffer := range mb { packet := B.As(buffer.Bytes()).ToOwned() @@ -112,16 +114,11 @@ func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.M }) newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP)) + link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) if err != nil { return err } - outConn := &pipeConnWrapper{ - &buf.BufferedReader{Reader: link.Reader}, - link.Writer, - conn, - } - return bufio.CopyConn(ctx, conn, outConn) + return singbridge.CopyConn(ctx, nil, link, conn) } func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { @@ -138,12 +135,12 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me }) newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - destination := toDestination(metadata.Destination, net.Network_UDP) + destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err } - outConn := &packetConnWrapper{ + outConn := &singbridge.PacketConnWrapper{ Reader: link.Reader, Writer: link.Writer, Dest: destination, diff --git a/proxy/shadowsocks_2022/inbound_multi.go b/proxy/shadowsocks_2022/inbound_multi.go index 91342c764678..c3832a91d14b 100644 --- a/proxy/shadowsocks_2022/inbound_multi.go +++ b/proxy/shadowsocks_2022/inbound_multi.go @@ -15,13 +15,13 @@ import ( E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" @@ -59,7 +59,7 @@ func NewMultiServer(ctx context.Context, config *MultiUserServerConfig) (*MultiU if err != nil { return nil, newError("parse config").Base(err) } - service, err := shadowaead_2022.NewMultiService[int](config.Method, psk, 500, inbound) + service, err := shadowaead_2022.NewMultiService[int](config.Method, psk, 500, inbound, nil) if err != nil { return nil, newError("create service").Base(err) } @@ -154,6 +154,8 @@ func (i *MultiUserInbound) Network() []net.Network { func (i *MultiUserInbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { inbound := session.InboundFromContext(ctx) + inbound.Name = "shadowsocks-2022-multi" + inbound.SetCanSpliceCopy(3) var metadata M.Metadata if inbound.Source.IsValid() { @@ -163,7 +165,7 @@ func (i *MultiUserInbound) Process(ctx context.Context, network net.Network, con ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return returnError(i.service.NewConnection(ctx, connection, metadata)) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} @@ -171,7 +173,7 @@ func (i *MultiUserInbound) Process(ctx context.Context, network net.Network, con mb, err := reader.ReadMultiBuffer() if err != nil { buf.ReleaseMulti(mb) - return returnError(err) + return singbridge.ReturnError(err) } for _, buffer := range mb { packet := B.As(buffer.Bytes()).ToOwned() @@ -203,16 +205,16 @@ func (i *MultiUserInbound) NewConnection(ctx context.Context, conn net.Conn, met }) newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP)) + destination := singbridge.ToDestination(metadata.Destination, net.Network_TCP) + if !destination.IsValid() { + return newError("invalid destination") + } + + link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err } - outConn := &pipeConnWrapper{ - &buf.BufferedReader{Reader: link.Reader}, - link.Writer, - conn, - } - return bufio.CopyConn(ctx, conn, outConn) + return singbridge.CopyConn(ctx, conn, link, conn) } func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { @@ -231,12 +233,12 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe }) newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - destination := toDestination(metadata.Destination, net.Network_UDP) + destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err } - outConn := &packetConnWrapper{ + outConn := &singbridge.PacketConnWrapper{ Reader: link.Reader, Writer: link.Writer, Dest: destination, diff --git a/proxy/shadowsocks_2022/inbound_relay.go b/proxy/shadowsocks_2022/inbound_relay.go index 3e0043ee3e94..e2cb7d508134 100644 --- a/proxy/shadowsocks_2022/inbound_relay.go +++ b/proxy/shadowsocks_2022/inbound_relay.go @@ -19,6 +19,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" @@ -66,7 +67,7 @@ func NewRelayServer(ctx context.Context, config *RelayServerConfig) (*RelayInbou C.MapIndexed(config.Destinations, func(index int, it *RelayDestination) int { return index }), C.Map(config.Destinations, func(it *RelayDestination) string { return it.Key }), C.Map(config.Destinations, func(it *RelayDestination) M.Socksaddr { - return toSocksaddr(net.Destination{ + return singbridge.ToSocksaddr(net.Destination{ Address: it.Address.AsAddress(), Port: net.Port(it.Port), }) @@ -85,6 +86,8 @@ func (i *RelayInbound) Network() []net.Network { func (i *RelayInbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { inbound := session.InboundFromContext(ctx) + inbound.Name = "shadowsocks-2022-relay" + inbound.SetCanSpliceCopy(3) var metadata M.Metadata if inbound.Source.IsValid() { @@ -94,7 +97,7 @@ func (i *RelayInbound) Process(ctx context.Context, network net.Network, connect ctx = session.ContextWithDispatcher(ctx, dispatcher) if network == net.Network_TCP { - return returnError(i.service.NewConnection(ctx, connection, metadata)) + return singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata)) } else { reader := buf.NewReader(connection) pc := &natPacketConn{connection} @@ -102,7 +105,7 @@ func (i *RelayInbound) Process(ctx context.Context, network net.Network, connect mb, err := reader.ReadMultiBuffer() if err != nil { buf.ReleaseMulti(mb) - return returnError(err) + return singbridge.ReturnError(err) } for _, buffer := range mb { packet := B.As(buffer.Bytes()).ToOwned() @@ -134,16 +137,11 @@ func (i *RelayInbound) NewConnection(ctx context.Context, conn net.Conn, metadat }) newError("tunnelling request to tcp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - link, err := dispatcher.Dispatch(ctx, toDestination(metadata.Destination, net.Network_TCP)) + link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) if err != nil { return err } - outConn := &pipeConnWrapper{ - &buf.BufferedReader{Reader: link.Reader}, - link.Writer, - conn, - } - return bufio.CopyConn(ctx, conn, outConn) + return singbridge.CopyConn(ctx, nil, link, conn) } func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { @@ -162,12 +160,12 @@ func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketCon }) newError("tunnelling request to udp:", metadata.Destination).WriteToLog(session.ExportIDToError(ctx)) dispatcher := session.DispatcherFromContext(ctx) - destination := toDestination(metadata.Destination, net.Network_UDP) + destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) link, err := dispatcher.Dispatch(ctx, destination) if err != nil { return err } - outConn := &packetConnWrapper{ + outConn := &singbridge.PacketConnWrapper{ Reader: link.Reader, Writer: link.Writer, Dest: destination, diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index cc23f737f8fe..a06daac70133 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -2,22 +2,21 @@ package shadowsocks_2022 import ( "context" - "io" "runtime" "time" - "github.com/sagernet/sing-shadowsocks" + shadowsocks "github.com/sagernet/sing-shadowsocks" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" C "github.com/sagernet/sing/common" B "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" - M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" ) @@ -29,10 +28,10 @@ func init() { } type Outbound struct { - ctx context.Context - server net.Destination - method shadowsocks.Method - uot bool + ctx context.Context + server net.Destination + method shadowsocks.Method + uotClient *uot.Client } func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { @@ -43,13 +42,12 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { Port: net.Port(config.Port), Network: net.Network_TCP, }, - uot: config.UdpOverTcp, } if C.Contains(shadowaead_2022.List, config.Method) { if config.Key == "" { return nil, newError("missing psk") } - method, err := shadowaead_2022.NewWithPassword(config.Method, config.Key) + method, err := shadowaead_2022.NewWithPassword(config.Method, config.Key, nil) if err != nil { return nil, newError("create method").Base(err) } @@ -57,6 +55,9 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { } else { return nil, newError("unknown method ", config.Method) } + if config.UdpOverTcp { + o.uotClient = &uot.Client{Version: uint8(config.UdpOverTcpVersion)} + } return o, nil } @@ -65,19 +66,21 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int inbound := session.InboundFromContext(ctx) if inbound != nil { inboundConn = inbound.Conn + inbound.SetCanSpliceCopy(3) } outbound := session.OutboundFromContext(ctx) if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified") } + outbound.Name = "shadowsocks-2022" destination := outbound.Target network := destination.Network newError("tunneling request to ", destination, " via ", o.server.NetAddr()).WriteToLog(session.ExportIDToError(ctx)) serverDestination := o.server - if o.uot { + if o.uotClient != nil { serverDestination.Network = net.Network_TCP } else { serverDestination.Network = network @@ -87,8 +90,12 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int return newError("failed to connect to server").Base(err) } + if session.TimeoutOnlyFromContext(ctx) { + ctx, _ = context.WithCancel(context.Background()) + } + if network == net.Network_TCP { - serverConn := o.method.DialEarlyConn(connection, toSocksaddr(destination)) + serverConn := o.method.DialEarlyConn(connection, singbridge.ToSocksaddr(destination)) var handshake bool if timeoutReader, isTimeoutReader := link.Reader.(buf.TimeoutReader); isTimeoutReader { mb, err := timeoutReader.ReadMultiBufferTimeout(time.Millisecond * 100) @@ -123,17 +130,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int return newError("client handshake").Base(err) } } - conn := &pipeConnWrapper{ - W: link.Writer, - Conn: inboundConn, - } - if ir, ok := link.Reader.(io.Reader); ok { - conn.R = ir - } else { - conn.R = &buf.BufferedReader{Reader: link.Reader} - } - - return returnError(bufio.CopyConn(ctx, conn, serverConn)) + return singbridge.CopyConn(ctx, inboundConn, link, serverConn) } else { var packetConn N.PacketConn if pc, isPacketConn := inboundConn.(N.PacketConn); isPacketConn { @@ -141,7 +138,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int } else if nc, isNetPacket := inboundConn.(net.PacketConn); isNetPacket { packetConn = bufio.NewPacketConn(nc) } else { - packetConn = &packetConnWrapper{ + packetConn = &singbridge.PacketConnWrapper{ Reader: link.Reader, Writer: link.Writer, Conn: inboundConn, @@ -149,12 +146,15 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int } } - if o.uot { - serverConn := o.method.DialEarlyConn(connection, M.Socksaddr{Fqdn: uot.UOTMagicAddress}) - return returnError(bufio.CopyPacketConn(ctx, packetConn, uot.NewClientConn(serverConn))) + if o.uotClient != nil { + uConn, err := o.uotClient.DialEarlyConn(o.method.DialEarlyConn(connection, uot.RequestDestination(o.uotClient.Version)), false, singbridge.ToSocksaddr(destination)) + if err != nil { + return err + } + return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, uConn)) } else { serverConn := o.method.DialPacketConn(connection) - return returnError(bufio.CopyPacketConn(ctx, packetConn, serverConn)) + return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn)) } } } diff --git a/proxy/shadowsocks_2022/shadowsocks_2022.go b/proxy/shadowsocks_2022/shadowsocks_2022.go index 945c44994e75..4f8d88ab0b4f 100644 --- a/proxy/shadowsocks_2022/shadowsocks_2022.go +++ b/proxy/shadowsocks_2022/shadowsocks_2022.go @@ -1,145 +1,3 @@ package shadowsocks_2022 -import ( - "io" - - B "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/net" -) - //go:generate go run github.com/xtls/xray-core/common/errors/errorgen - -func toDestination(socksaddr M.Socksaddr, network net.Network) net.Destination { - if socksaddr.IsFqdn() { - return net.Destination{ - Network: network, - Address: net.DomainAddress(socksaddr.Fqdn), - Port: net.Port(socksaddr.Port), - } - } else { - return net.Destination{ - Network: network, - Address: net.IPAddress(socksaddr.Addr.AsSlice()), - Port: net.Port(socksaddr.Port), - } - } -} - -func toSocksaddr(destination net.Destination) M.Socksaddr { - var addr M.Socksaddr - switch destination.Address.Family() { - case net.AddressFamilyDomain: - addr.Fqdn = destination.Address.Domain() - default: - addr.Addr = M.AddrFromIP(destination.Address.IP()) - } - addr.Port = uint16(destination.Port) - return addr -} - -type pipeConnWrapper struct { - R io.Reader - W buf.Writer - net.Conn -} - -func (w *pipeConnWrapper) Close() error { - return nil -} - -func (w *pipeConnWrapper) Read(b []byte) (n int, err error) { - return w.R.Read(b) -} - -func (w *pipeConnWrapper) Write(p []byte) (n int, err error) { - n = len(p) - var mb buf.MultiBuffer - pLen := len(p) - for pLen > 0 { - buffer := buf.New() - if pLen > buf.Size { - _, err = buffer.Write(p[:buf.Size]) - p = p[buf.Size:] - } else { - buffer.Write(p) - } - pLen -= int(buffer.Len()) - mb = append(mb, buffer) - } - err = w.W.WriteMultiBuffer(mb) - if err != nil { - n = 0 - buf.ReleaseMulti(mb) - } - return -} - -type packetConnWrapper struct { - buf.Reader - buf.Writer - net.Conn - Dest net.Destination - cached buf.MultiBuffer -} - -func (w *packetConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) { - if w.cached != nil { - mb, bb := buf.SplitFirst(w.cached) - if bb == nil { - w.cached = nil - } else { - buffer.Write(bb.Bytes()) - w.cached = mb - var destination net.Destination - if bb.UDP != nil { - destination = *bb.UDP - } else { - destination = w.Dest - } - bb.Release() - return toSocksaddr(destination), nil - } - } - mb, err := w.ReadMultiBuffer() - if err != nil { - return M.Socksaddr{}, err - } - nb, bb := buf.SplitFirst(mb) - if bb == nil { - return M.Socksaddr{}, nil - } else { - buffer.Write(bb.Bytes()) - w.cached = nb - var destination net.Destination - if bb.UDP != nil { - destination = *bb.UDP - } else { - destination = w.Dest - } - bb.Release() - return toSocksaddr(destination), nil - } -} - -func (w *packetConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error { - vBuf := buf.New() - vBuf.Write(buffer.Bytes()) - endpoint := toDestination(destination, net.Network_UDP) - vBuf.UDP = &endpoint - return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf}) -} - -func (w *packetConnWrapper) Close() error { - buf.ReleaseMulti(w.cached) - return nil -} - -func returnError(err error) error { - if E.IsClosed(err) { - return nil - } - return err -} diff --git a/proxy/socks/client.go b/proxy/socks/client.go index f1690bec8757..82591be4321f 100644 --- a/proxy/socks/client.go +++ b/proxy/socks/client.go @@ -61,6 +61,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified.") } + outbound.Name = "socks" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(2) + } // Destination of the inner request. destination := outbound.Target @@ -151,8 +156,19 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter newError("failed to clear deadline after handshake").Base(err).WriteToLog(session.ExportIDToError(ctx)) } + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } + ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, p.Timeouts.ConnectionIdle) var requestFunc func() error var responseFunc func() error @@ -183,6 +199,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return newError("connection ends").Base(err) diff --git a/proxy/socks/config.pb.go b/proxy/socks/config.pb.go index 8ca080837ff7..a1daa2760d21 100644 --- a/proxy/socks/config.pb.go +++ b/proxy/socks/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/socks/config.proto package socks @@ -186,7 +186,7 @@ type ServerConfig struct { Accounts map[string]string `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Address *net.IPOrDomain `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` UdpEnabled bool `protobuf:"varint,4,opt,name=udp_enabled,json=udpEnabled,proto3" json:"udp_enabled,omitempty"` - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in proxy/socks/config.proto. Timeout uint32 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` } @@ -251,7 +251,7 @@ func (x *ServerConfig) GetUdpEnabled() bool { return false } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in proxy/socks/config.proto. func (x *ServerConfig) GetTimeout() uint32 { if x != nil { return x.Timeout diff --git a/proxy/socks/server.go b/proxy/socks/server.go index ce15163c5b2e..6964fdf2b078 100644 --- a/proxy/socks/server.go +++ b/proxy/socks/server.go @@ -63,10 +63,11 @@ func (s *Server) Network() []net.Network { // Process implements proxy.Inbound. func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { - if inbound := session.InboundFromContext(ctx); inbound != nil { - inbound.User = &protocol.MemoryUser{ - Level: s.config.UserLevel, - } + inbound := session.InboundFromContext(ctx) + inbound.Name = "socks" + inbound.SetCanSpliceCopy(2) + inbound.User = &protocol.MemoryUser{ + Level: s.config.UserLevel, } switch network { diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go index 353b326e28d3..d6b95fc0b154 100644 --- a/proxy/trojan/client.go +++ b/proxy/trojan/client.go @@ -2,14 +2,12 @@ package trojan import ( "context" - "syscall" "time" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/session" @@ -17,11 +15,9 @@ import ( "github.com/xtls/xray-core/common/task" core "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" - "github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" - "github.com/xtls/xray-core/transport/internet/xtls" ) // Client is a inbound handler for trojan protocol @@ -58,6 +54,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter if outbound == nil || !outbound.Target.IsValid() { return newError("target not specified") } + outbound.Name = "trojan" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(3) + } destination := outbound.Target network := destination.Network @@ -81,77 +82,37 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter defer conn.Close() - iConn := conn - statConn, ok := iConn.(*stat.CounterConnection) - if ok { - iConn = statConn.Connection - } - user := server.PickUser() account, ok := user.Account.(*MemoryAccount) if !ok { return newError("user account is not valid") } - connWriter := &ConnWriter{ - Flow: account.Flow, - } - - var rawConn syscall.RawConn - var sctx context.Context - - allowUDP443 := false - switch connWriter.Flow { - case XRO + "-udp443", XRD + "-udp443", XRS + "-udp443": - allowUDP443 = true - connWriter.Flow = connWriter.Flow[:16] - fallthrough - case XRO, XRD, XRS: - if destination.Address.Family().IsDomain() && destination.Address.Domain() == muxCoolAddress { - return newError(connWriter.Flow + " doesn't support Mux").AtWarning() - } - if destination.Network == net.Network_UDP { - if !allowUDP443 && destination.Port == 443 { - return newError(connWriter.Flow + " stopped UDP/443").AtInfo() - } - connWriter.Flow = "" - } else { // enable XTLS only if making TCP request - if xtlsConn, ok := iConn.(*xtls.Conn); ok { - xtlsConn.RPRX = true - xtlsConn.SHOW = xtls_show - xtlsConn.MARK = "XTLS" - if connWriter.Flow == XRS { - sctx = ctx - connWriter.Flow = XRD - } - if connWriter.Flow == XRD { - xtlsConn.DirectMode = true - if sc, ok := xtlsConn.NetConn().(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } - } - } else { - return newError(`failed to use ` + connWriter.Flow + `, maybe "security" is not "xtls"`).AtWarning() - } - } - default: - if _, ok := iConn.(*xtls.Conn); ok { - panic(`To avoid misunderstanding, you must fill in Trojan "flow" when using XTLS.`) - } + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) } sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn)) - connWriter.Writer = bufferWriter - connWriter.Target = destination - connWriter.Account = account + connWriter := &ConnWriter{ + Writer: bufferWriter, + Target: destination, + Account: account, + } var bodyWriter buf.Writer if destination.Network == net.Network_UDP { @@ -193,16 +154,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } else { reader = buf.NewReader(conn) } - if rawConn != nil { - var counter stats.Counter - if statConn != nil { - counter = statConn.ReadCounter - } - return ReadV(reader, link.Writer, timer, iConn.(*xtls.Conn), rawConn, counter, sctx) - } return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) } + if newCtx != nil { + ctx = newCtx + } + responseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer)) if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil { return newError("connection ends").Base(err) @@ -215,11 +173,4 @@ func init() { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewClient(ctx, config.(*ClientConfig)) })) - - const defaultFlagValue = "NOT_DEFINED_AT_ALL" - - xtlsShow := platform.NewEnvFlag("xray.trojan.xtls.show").GetValue(func() string { return defaultFlagValue }) - if xtlsShow == "true" { - xtls_show = true - } } diff --git a/proxy/trojan/config.go b/proxy/trojan/config.go index ffac7854c529..a02dfe98afc8 100644 --- a/proxy/trojan/config.go +++ b/proxy/trojan/config.go @@ -13,7 +13,6 @@ import ( type MemoryAccount struct { Password string Key []byte - Flow string } // AsAccount implements protocol.AsAccount. @@ -23,7 +22,6 @@ func (a *Account) AsAccount() (protocol.Account, error) { return &MemoryAccount{ Password: password, Key: key, - Flow: a.Flow, }, nil } diff --git a/proxy/trojan/config.pb.go b/proxy/trojan/config.pb.go index eaa71eff8f9a..493740019171 100644 --- a/proxy/trojan/config.pb.go +++ b/proxy/trojan/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/trojan/config.proto package trojan @@ -27,7 +27,6 @@ type Account struct { unknownFields protoimpl.UnknownFields Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` - Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` } func (x *Account) Reset() { @@ -69,13 +68,6 @@ func (x *Account) GetPassword() string { return "" } -func (x *Account) GetFlow() string { - if x != nil { - return x.Flow - } - return "" -} - type Fallback struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -216,7 +208,7 @@ type ServerConfig struct { unknownFields protoimpl.UnknownFields Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` - Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` + Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` } func (x *ServerConfig) Reset() { @@ -274,38 +266,37 @@ var file_proxy_trojan_config_proto_rawDesc = []byte{ 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x39, 0x0a, + 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x25, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x82, 0x01, 0x0a, 0x08, 0x46, 0x61, 0x6c, - 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, - 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, - 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, - 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x4c, 0x0a, - 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, - 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x7b, 0x0a, 0x0c, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x75, - 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, - 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, - 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, - 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, - 0x6e, 0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, - 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, - 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x77, 0x6f, 0x72, 0x64, 0x22, 0x82, 0x01, 0x0a, 0x08, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, + 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x6c, 0x70, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x4c, 0x0a, 0x0c, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x7b, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x09, 0x66, 0x61, 0x6c, + 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, + 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x73, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x50, 0x01, 0x5a, + 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, + 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/proxy/trojan/config.proto b/proxy/trojan/config.proto index 6ebed36b0d1a..4229e2edcfa9 100644 --- a/proxy/trojan/config.proto +++ b/proxy/trojan/config.proto @@ -11,7 +11,6 @@ import "common/protocol/server_spec.proto"; message Account { string password = 1; - string flow = 2; } message Fallback { @@ -29,5 +28,5 @@ message ClientConfig { message ServerConfig { repeated xray.common.protocol.User users = 1; - repeated Fallback fallbacks = 3; + repeated Fallback fallbacks = 2; } diff --git a/proxy/trojan/protocol.go b/proxy/trojan/protocol.go index 38f0b85478dc..6fcfb4d4ae8c 100644 --- a/proxy/trojan/protocol.go +++ b/proxy/trojan/protocol.go @@ -1,22 +1,12 @@ package trojan import ( - "context" "encoding/binary" - fmt "fmt" "io" - "runtime" - "syscall" "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/session" - "github.com/xtls/xray-core/common/signal" - "github.com/xtls/xray-core/features/stats" - "github.com/xtls/xray-core/transport/internet/stat" - "github.com/xtls/xray-core/transport/internet/xtls" ) var ( @@ -27,25 +17,13 @@ var ( protocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6), protocol.AddressFamilyByte(0x03, net.AddressFamilyDomain), ) - - xtls_show = false ) const ( maxLength = 8192 - // XRS is constant for XTLS splice mode - XRS = "xtls-rprx-splice" - // XRD is constant for XTLS direct mode - XRD = "xtls-rprx-direct" - // XRO is constant for XTLS origin mode - XRO = "xtls-rprx-origin" commandTCP byte = 1 commandUDP byte = 3 - - // for XTLS - commandXRD byte = 0xf0 // XTLS direct mode - commandXRO byte = 0xf1 // XTLS origin mode ) // ConnWriter is TCP Connection Writer Wrapper for trojan protocol @@ -53,7 +31,6 @@ type ConnWriter struct { io.Writer Target net.Destination Account *MemoryAccount - Flow string headerSent bool } @@ -90,10 +67,6 @@ func (c *ConnWriter) writeHeader() error { command := commandTCP if c.Target.Network == net.Network_UDP { command = commandUDP - } else if c.Flow == XRD { - command = commandXRD - } else if c.Flow == XRO { - command = commandXRO } if _, err := buffer.Write(c.Account.Key); err != nil { @@ -201,10 +174,6 @@ func (c *ConnReader) ParseHeader() error { network := net.Network_TCP if command[0] == commandUDP { network = net.Network_UDP - } else if command[0] == commandXRD { - c.Flow = XRD - } else if command[0] == commandXRO { - c.Flow = XRO } addr, port, err := addrParser.ReadAddressPort(nil, c.Reader) @@ -288,66 +257,3 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) { return mb, nil } - -func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *xtls.Conn, rawConn syscall.RawConn, counter stats.Counter, sctx context.Context) error { - err := func() error { - var ct stats.Counter - for { - if conn.DirectIn { - conn.DirectIn = false - if sctx != nil { - if inbound := session.InboundFromContext(sctx); inbound != nil && inbound.Conn != nil { - iConn := inbound.Conn - statConn, ok := iConn.(*stat.CounterConnection) - if ok { - iConn = statConn.Connection - } - if xc, ok := iConn.(*xtls.Conn); ok { - iConn = xc.NetConn() - } - if tc, ok := iConn.(*net.TCPConn); ok { - if conn.SHOW { - fmt.Println(conn.MARK, "Splice") - } - runtime.Gosched() // necessary - w, err := tc.ReadFrom(conn.NetConn()) - if counter != nil { - counter.Add(w) - } - if statConn != nil && statConn.WriteCounter != nil { - statConn.WriteCounter.Add(w) - } - return err - } else { - panic("XTLS Splice: not TCP inbound") - } - } else { - // panic("XTLS Splice: nil inbound or nil inbound.Conn") - } - } - reader = buf.NewReadVReader(conn.NetConn(), rawConn, nil) - ct = counter - if conn.SHOW { - fmt.Println(conn.MARK, "ReadV") - } - } - buffer, err := reader.ReadMultiBuffer() - if !buffer.IsEmpty() { - if ct != nil { - ct.Add(int64(buffer.Len())) - } - timer.Update() - if werr := writer.WriteMultiBuffer(buffer); werr != nil { - return werr - } - } - if err != nil { - return err - } - } - }() - if err != nil && errors.Cause(err) != io.EOF { - return err - } - return nil -} diff --git a/proxy/trojan/server.go b/proxy/trojan/server.go index 618fbe10ca82..5c3fcd9113f5 100644 --- a/proxy/trojan/server.go +++ b/proxy/trojan/server.go @@ -5,7 +5,6 @@ import ( "io" "strconv" "strings" - "syscall" "time" "github.com/xtls/xray-core/common" @@ -13,7 +12,6 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/protocol" udp_proto "github.com/xtls/xray-core/common/protocol/udp" "github.com/xtls/xray-core/common/retry" @@ -23,24 +21,16 @@ import ( "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" - "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/udp" - "github.com/xtls/xray-core/transport/internet/xtls" ) func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return NewServer(ctx, config.(*ServerConfig)) })) - - const defaultFlagValue = "NOT_DEFINED_AT_ALL" - - xtlsShow := platform.NewEnvFlag("xray.trojan.xtls.show").GetValue(func() string { return defaultFlagValue }) - if xtlsShow == "true" { - xtls_show = true - } } // Server is an inbound connection handler that handles messages in trojan protocol. @@ -155,9 +145,8 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con return newError("unable to set read deadline").Base(err).AtWarning() } - first := buf.New() - defer first.Release() - + first := buf.FromBytes(make([]byte, buf.Size)) + first.Clear() firstLen, err := first.ReadFrom(conn) if err != nil { return newError("failed to read first request").Base(err) @@ -225,9 +214,8 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con } inbound := session.InboundFromContext(ctx) - if inbound == nil { - panic("no inbound metadata") - } + inbound.Name = "trojan" + inbound.SetCanSpliceCopy(3) inbound.User = user sessionPolicy = s.policyManager.ForLevel(user.Level) @@ -235,39 +223,6 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con return s.handleUDPPayload(ctx, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher) } - // handle tcp request - account, ok := user.Account.(*MemoryAccount) - if !ok { - return newError("user account is not valid") - } - - var rawConn syscall.RawConn - - switch clientReader.Flow { - case XRO, XRD: - if account.Flow == clientReader.Flow { - if destination.Address.Family().IsDomain() && destination.Address.Domain() == muxCoolAddress { - return newError(clientReader.Flow + " doesn't support Mux").AtWarning() - } - if xtlsConn, ok := iConn.(*xtls.Conn); ok { - xtlsConn.RPRX = true - xtlsConn.SHOW = xtls_show - xtlsConn.MARK = "XTLS" - if clientReader.Flow == XRD { - xtlsConn.DirectMode = true - if sc, ok := xtlsConn.NetConn().(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } - } - } else { - return newError(`failed to use ` + clientReader.Flow + `, maybe "security" is not "xtls"`).AtWarning() - } - } else { - return newError(account.Password + " is not able to use " + clientReader.Flow).AtWarning() - } - case "": - } - ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: conn.RemoteAddr(), To: destination, @@ -277,7 +232,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con }) newError("received request for ", destination).WriteToLog(sid) - return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher, iConn, rawConn, statConn) + return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher) } func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error { @@ -343,7 +298,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReade func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session, destination net.Destination, clientReader buf.Reader, - clientWriter buf.Writer, dispatcher routing.Dispatcher, iConn stat.Connection, rawConn syscall.RawConn, statConn *stat.CounterConnection, + clientWriter buf.Writer, dispatcher routing.Dispatcher, ) error { ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) @@ -356,18 +311,7 @@ func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Sess requestDone := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) - - var err error - if rawConn != nil { - var counter stats.Counter - if statConn != nil { - counter = statConn.ReadCounter - } - err = ReadV(clientReader, link.Writer, timer, iConn.(*xtls.Conn), rawConn, counter, nil) - } else { - err = buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)) - } - if err != nil { + if buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)) != nil { return newError("failed to transfer request").Base(err) } return nil @@ -406,8 +350,8 @@ func (s *Server) fallback(ctx context.Context, sid errors.ExportOption, err erro alpn = cs.NegotiatedProtocol newError("realName = " + name).AtInfo().WriteToLog(sid) newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) - } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { - cs := xtlsConn.ConnectionState() + } else if realityConn, ok := iConn.(*reality.Conn); ok { + cs := realityConn.ConnectionState() name = cs.ServerName alpn = cs.NegotiatedProtocol newError("realName = " + name).AtInfo().WriteToLog(sid) diff --git a/proxy/trojan/trojan.go b/proxy/trojan/trojan.go index 4639b7d9b593..d0cf5de40233 100644 --- a/proxy/trojan/trojan.go +++ b/proxy/trojan/trojan.go @@ -1,5 +1,3 @@ package trojan -const ( - muxCoolAddress = "v1.mux.cool" -) +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/proxy/vless/account.go b/proxy/vless/account.go index 40443424da3a..b20a9539b3a5 100644 --- a/proxy/vless/account.go +++ b/proxy/vless/account.go @@ -22,7 +22,7 @@ func (a *Account) AsAccount() (protocol.Account, error) { type MemoryAccount struct { // ID of the account. ID *protocol.ID - // Flow of the account. May be "xtls-rprx-direct". + // Flow of the account. May be "xtls-rprx-vision". Flow string // Encryption of the account. Used for client connections, and only accepts "none" for now. Encryption string diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go index 50ce9378110d..1d0551853439 100644 --- a/proxy/vless/account.pb.go +++ b/proxy/vless/account.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vless/account.proto package vless @@ -27,7 +27,7 @@ type Account struct { // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // Flow settings. May be "xtls-rprx-direct". + // Flow settings. May be "xtls-rprx-vision". Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` // Encryption settings. Only applies to client side, and only accepts "none" for now. Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` diff --git a/proxy/vless/account.proto b/proxy/vless/account.proto index 38bd614485b4..51d2cb7deaaf 100644 --- a/proxy/vless/account.proto +++ b/proxy/vless/account.proto @@ -9,7 +9,7 @@ option java_multiple_files = true; message Account { // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". string id = 1; - // Flow settings. May be "xtls-rprx-direct". + // Flow settings. May be "xtls-rprx-vision". string flow = 2; // Encryption settings. Only applies to client side, and only accepts "none" for now. string encryption = 3; diff --git a/proxy/vless/encoding/addons.go b/proxy/vless/encoding/addons.go index fb457412ecd1..e3e5071b66a9 100644 --- a/proxy/vless/encoding/addons.go +++ b/proxy/vless/encoding/addons.go @@ -1,17 +1,19 @@ package encoding import ( + "context" "io" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" + "google.golang.org/protobuf/proto" ) func EncodeHeaderAddons(buffer *buf.Buffer, addons *Addons) error { switch addons.Flow { - case vless.XRO, vless.XRD, vless.XRV: + case vless.XRV: bytes, err := proto.Marshal(addons) if err != nil { return newError("failed to marshal addons protobuf value").Base(err) @@ -58,14 +60,19 @@ func DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) { } // EncodeBodyAddons returns a Writer that auto-encrypt content written by caller. -func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, addons *Addons) buf.Writer { - switch addons.Flow { - default: - if request.Command == protocol.RequestCommandUDP { - return NewMultiLengthPacketWriter(writer.(buf.Writer)) +func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons, state *proxy.TrafficState, context context.Context) buf.Writer { + if request.Command == protocol.RequestCommandUDP { + w := writer.(buf.Writer) + if requestAddons.Flow == vless.XRV { + w = proxy.NewVisionWriter(w, state, context) } + return NewMultiLengthPacketWriter(w) + } + w := buf.NewWriter(writer) + if requestAddons.Flow == vless.XRV { + w = proxy.NewVisionWriter(w, state, context) } - return buf.NewWriter(writer) + return w } // DecodeBodyAddons returns a Reader from which caller can fetch decrypted body. diff --git a/proxy/vless/encoding/addons.pb.go b/proxy/vless/encoding/addons.pb.go index 170cf13559e2..b78c878cd338 100644 --- a/proxy/vless/encoding/addons.pb.go +++ b/proxy/vless/encoding/addons.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vless/encoding/addons.proto package encoding diff --git a/proxy/vless/encoding/encoding.go b/proxy/vless/encoding/encoding.go index e1987d981aba..b7fb66f55a58 100644 --- a/proxy/vless/encoding/encoding.go +++ b/proxy/vless/encoding/encoding.go @@ -5,14 +5,7 @@ package encoding import ( "bytes" "context" - "crypto/rand" - "fmt" "io" - "math/big" - "runtime" - "strconv" - "syscall" - "time" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" @@ -21,21 +14,14 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" - "github.com/xtls/xray-core/transport/internet/stat" - "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) const ( Version = byte(0) ) -var tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04} -var tlsClientHandShakeStart = []byte{0x16, 0x03} -var tlsServerHandShakeStart = []byte{0x16, 0x03, 0x03} -var tlsApplicationDataStart = []byte{0x17, 0x03, 0x03} - var addrParser = protocol.NewAddressParser( protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4), protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain), @@ -187,129 +173,37 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A return responseAddons, nil } -func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *xtls.Conn, rawConn syscall.RawConn, counter stats.Counter, ctx context.Context) error { +// XtlsRead filter and read xtls protocol +func XtlsRead(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ctx context.Context) error { err := func() error { - var ct stats.Counter + visionReader := proxy.NewVisionReader(reader, trafficState, ctx) for { - if conn.DirectIn { - conn.DirectIn = false + if trafficState.ReaderSwitchToDirectCopy { + var writerConn net.Conn if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { - iConn := inbound.Conn - statConn, ok := iConn.(*stat.CounterConnection) - if ok { - iConn = statConn.Connection - } - if xc, ok := iConn.(*xtls.Conn); ok { - iConn = xc.NetConn() + writerConn = inbound.Conn + if inbound.CanSpliceCopy == 2 { + inbound.CanSpliceCopy = 1 // force the value to 1, don't use setter } - if tc, ok := iConn.(*net.TCPConn); ok { - if conn.SHOW { - fmt.Println(conn.MARK, "Splice") - } - runtime.Gosched() // necessary - w, err := tc.ReadFrom(conn.NetConn()) - if counter != nil { - counter.Add(w) - } - if statConn != nil && statConn.WriteCounter != nil { - statConn.WriteCounter.Add(w) - } - return err - } else { - panic("XTLS Splice: not TCP inbound") - } - } - reader = buf.NewReadVReader(conn.NetConn(), rawConn, nil) - ct = counter - if conn.SHOW { - fmt.Println(conn.MARK, "ReadV") } + return proxy.CopyRawConnIfExist(ctx, conn, writerConn, writer, timer) } - buffer, err := reader.ReadMultiBuffer() + buffer, err := visionReader.ReadMultiBuffer() if !buffer.IsEmpty() { - if ct != nil { - ct.Add(int64(buffer.Len())) - } timer.Update() - if werr := writer.WriteMultiBuffer(buffer); werr != nil { - return werr - } - } - if err != nil { - return err - } - } - }() - if err != nil && errors.Cause(err) != io.EOF { - return err - } - return nil -} - -// XtlsRead filter and read xtls protocol -func XtlsRead(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn net.Conn, rawConn syscall.RawConn, - counter stats.Counter, ctx context.Context, userUUID []byte, numberOfPacketToFilter *int, enableXtls *bool, - isTLS12orAbove *bool, isTLS *bool, cipher *uint16, remainingServerHello *int32) error { - err := func() error { - var ct stats.Counter - filterUUID := true - shouldSwitchToDirectCopy := false - var remainingContent int32 = -1 - var remainingPadding int32 = -1 - currentCommand := 0 - for { - if shouldSwitchToDirectCopy { - shouldSwitchToDirectCopy = false - if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil && (runtime.GOOS == "linux" || runtime.GOOS == "android") { - if _, ok := inbound.User.Account.(*vless.MemoryAccount); inbound.User.Account == nil || ok { - iConn := inbound.Conn - statConn, ok := iConn.(*stat.CounterConnection) - if ok { - iConn = statConn.Connection - } - if xc, ok := iConn.(*tls.Conn); ok { - iConn = xc.NetConn() - } - if tc, ok := iConn.(*net.TCPConn); ok { - newError("XtlsRead splice").WriteToLog(session.ExportIDToError(ctx)) - runtime.Gosched() // necessary - w, err := tc.ReadFrom(conn) - if counter != nil { - counter.Add(w) - } - if statConn != nil && statConn.WriteCounter != nil { - statConn.WriteCounter.Add(w) - } - return err + if trafficState.ReaderSwitchToDirectCopy { + // XTLS Vision processes struct TLS Conn's input and rawInput + if inputBuffer, err := buf.ReadFrom(input); err == nil { + if !inputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, inputBuffer) } } - } - reader = buf.NewReadVReader(conn, rawConn, nil) - ct = counter - newError("XtlsRead readV").WriteToLog(session.ExportIDToError(ctx)) - } - buffer, err := reader.ReadMultiBuffer() - if !buffer.IsEmpty() { - if filterUUID && (*isTLS || *numberOfPacketToFilter > 0) { - buffer = XtlsUnpadding(ctx, buffer, userUUID, &remainingContent, &remainingPadding, ¤tCommand) - if remainingContent == 0 && remainingPadding == 0 { - if currentCommand == 1 { - filterUUID = false - } else if currentCommand == 2 { - filterUUID = false - shouldSwitchToDirectCopy = true - } else if currentCommand != 0 { - newError("XtlsRead unknown command ", currentCommand, buffer.Len()).WriteToLog(session.ExportIDToError(ctx)) + if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil { + if !rawInputBuffer.IsEmpty() { + buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) } } } - if *numberOfPacketToFilter > 0 { - XtlsFilterTls(buffer, numberOfPacketToFilter, enableXtls, isTLS12orAbove, isTLS, cipher, remainingServerHello, ctx) - } - if ct != nil { - ct.Add(int64(buffer.Len())) - } - timer.Update() if werr := writer.WriteMultiBuffer(buffer); werr != nil { return werr } @@ -326,65 +220,27 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater } // XtlsWrite filter and write xtls protocol -func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn net.Conn, counter stats.Counter, - ctx context.Context, userUUID *[]byte, numberOfPacketToFilter *int, enableXtls *bool, isTLS12orAbove *bool, isTLS *bool, - cipher *uint16, remainingServerHello *int32) error { +func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn net.Conn, trafficState *proxy.TrafficState, ctx context.Context) error { err := func() error { var ct stats.Counter - filterTlsApplicationData := true - shouldSwitchToDirectCopy := false for { buffer, err := reader.ReadMultiBuffer() - if !buffer.IsEmpty() { - if *numberOfPacketToFilter > 0 { - XtlsFilterTls(buffer, numberOfPacketToFilter, enableXtls, isTLS12orAbove, isTLS, cipher, remainingServerHello, ctx) + if trafficState.WriterSwitchToDirectCopy { + if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.CanSpliceCopy == 2 { + inbound.CanSpliceCopy = 1 // force the value to 1, don't use setter } - if filterTlsApplicationData && *isTLS { - buffer = ReshapeMultiBuffer(ctx, buffer) - var xtlsSpecIndex int - for i, b := range buffer { - if b.Len() >= 6 && bytes.Equal(tlsApplicationDataStart, b.BytesTo(3)) { - var command byte = 0x01 - if *enableXtls { - shouldSwitchToDirectCopy = true - xtlsSpecIndex = i - command = 0x02 - } - filterTlsApplicationData = false - buffer[i] = XtlsPadding(b, command, userUUID, ctx) - break - } else if !*isTLS12orAbove && *numberOfPacketToFilter <= 0 { - //maybe tls 1.1 or 1.0 - filterTlsApplicationData = false - buffer[i] = XtlsPadding(b, 0x01, userUUID, ctx) - break - } - buffer[i] = XtlsPadding(b, 0x00, userUUID, ctx) - } - if shouldSwitchToDirectCopy { - encryptBuffer, directBuffer := buf.SplitMulti(buffer, xtlsSpecIndex+1) - length := encryptBuffer.Len() - if !encryptBuffer.IsEmpty() { - timer.Update() - if werr := writer.WriteMultiBuffer(encryptBuffer); werr != nil { - return werr - } - } - buffer = directBuffer - writer = buf.NewWriter(conn) - ct = counter - newError("XtlsWrite writeV ", xtlsSpecIndex, " ", length, " ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx)) - time.Sleep(5 * time.Millisecond) // for some device, the first xtls direct packet fails without this delay - } + rawConn, _, writerCounter := proxy.UnwrapRawConn(conn) + writer = buf.NewWriter(rawConn) + ct = writerCounter + trafficState.WriterSwitchToDirectCopy = false + } + if !buffer.IsEmpty() { + if ct != nil { + ct.Add(int64(buffer.Len())) } - if !buffer.IsEmpty() { - if ct != nil { - ct.Add(int64(buffer.Len())) - } - timer.Update() - if werr := writer.WriteMultiBuffer(buffer); werr != nil { - return werr - } + timer.Update() + if werr := writer.WriteMultiBuffer(buffer); werr != nil { + return werr } } if err != nil { @@ -397,194 +253,3 @@ func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdate } return nil } - -// XtlsFilterTls filter and recognize tls 1.3 and other info -func XtlsFilterTls(buffer buf.MultiBuffer, numberOfPacketToFilter *int, enableXtls *bool, isTLS12orAbove *bool, isTLS *bool, - cipher *uint16, remainingServerHello *int32, ctx context.Context) { - for _, b := range buffer { - *numberOfPacketToFilter-- - if b.Len() >= 6 { - startsBytes := b.BytesTo(6) - if bytes.Equal(tlsServerHandShakeStart, startsBytes[:3]) && startsBytes[5] == 0x02 { - *remainingServerHello = (int32(startsBytes[3])<<8 | int32(startsBytes[4])) + 5 - *isTLS12orAbove = true - *isTLS = true - if b.Len() >= 79 && *remainingServerHello >= 79 { - sessionIdLen := int32(b.Byte(43)) - cipherSuite := b.BytesRange(43 + sessionIdLen + 1, 43 + sessionIdLen + 3) - *cipher = uint16(cipherSuite[0]) << 8 | uint16(cipherSuite[1]) - } else { - newError("XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", *remainingServerHello).WriteToLog(session.ExportIDToError(ctx)) - } - } else if bytes.Equal(tlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == 0x01 { - *isTLS = true - newError("XtlsFilterTls found tls client hello! ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx)) - } - } - if *remainingServerHello > 0 { - end := *remainingServerHello - if end > b.Len() { - end = b.Len() - } - *remainingServerHello -= b.Len() - if bytes.Contains(b.BytesTo(end), tls13SupportedVersions) { - v, ok := Tls13CipherSuiteDic[*cipher] - if !ok { - v = "Old cipher: " + strconv.FormatUint(uint64(*cipher), 16) - } else if (v != "TLS_AES_128_CCM_8_SHA256") { - *enableXtls = true - } - newError("XtlsFilterTls found tls 1.3! ", b.Len(), " ", v).WriteToLog(session.ExportIDToError(ctx)) - *numberOfPacketToFilter = 0 - return - } else if *remainingServerHello <= 0 { - newError("XtlsFilterTls found tls 1.2! ", b.Len()).WriteToLog(session.ExportIDToError(ctx)) - *numberOfPacketToFilter = 0 - return - } - newError("XtlsFilterTls inconclusive server hello ", b.Len(), " ", *remainingServerHello).WriteToLog(session.ExportIDToError(ctx)) - } - if *numberOfPacketToFilter <= 0 { - newError("XtlsFilterTls stop filtering", buffer.Len()).WriteToLog(session.ExportIDToError(ctx)) - } - } -} - -// ReshapeMultiBuffer prepare multi buffer for padding stucture (max 21 bytes) -func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer { - needReshape := false - for _, b := range buffer { - if b.Len() >= buf.Size-21 { - needReshape = true - } - } - if !needReshape { - return buffer - } - mb2 := make(buf.MultiBuffer, 0, len(buffer)) - print := "" - for _, b := range buffer { - if b.Len() >= buf.Size-21 { - index := int32(bytes.LastIndex(b.Bytes(), tlsApplicationDataStart)) - if index <= 0 { - index = buf.Size / 2 - } - buffer1 := buf.New() - buffer2 := buf.New() - buffer1.Write(b.BytesTo(index)) - buffer2.Write(b.BytesFrom(index)) - mb2 = append(mb2, buffer1, buffer2) - print += " " + strconv.Itoa(int(buffer1.Len())) + " " + strconv.Itoa(int(buffer2.Len())) - } else { - newbuffer := buf.New() - newbuffer.Write(b.Bytes()) - mb2 = append(mb2, newbuffer) - print += " " + strconv.Itoa(int(b.Len())) - } - } - buf.ReleaseMulti(buffer) - newError("ReshapeMultiBuffer ", print).WriteToLog(session.ExportIDToError(ctx)) - return mb2 -} - -// XtlsPadding add padding to eliminate length siganature during tls handshake -func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, ctx context.Context) *buf.Buffer { - var length int32 = 0 - if b.Len() < 900 { - l, err := rand.Int(rand.Reader, big.NewInt(500)) - if err != nil { - newError("failed to generate padding").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - length = int32(l.Int64()) + 900 - b.Len() - } - newbuffer := buf.New() - if userUUID != nil { - newbuffer.Write(*userUUID) - *userUUID = nil - } - newbuffer.Write([]byte{command, byte(b.Len() >> 8), byte(b.Len()), byte(length >> 8), byte(length)}) - newbuffer.Write(b.Bytes()) - newbuffer.Extend(length) - newError("XtlsPadding ", b.Len(), " ", length, " ", command).WriteToLog(session.ExportIDToError(ctx)) - b.Release() - b = nil - return newbuffer -} - -// XtlsUnpadding remove padding and parse command -func XtlsUnpadding(ctx context.Context, buffer buf.MultiBuffer, userUUID []byte, remainingContent *int32, remainingPadding *int32, currentCommand *int) buf.MultiBuffer { - posindex := 0 - var posByte int32 = 0 - if *remainingContent == -1 && *remainingPadding == -1 { - for i, b := range buffer { - if b.Len() >= 21 && bytes.Equal(userUUID, b.BytesTo(16)) { - posindex = i - posByte = 16 - *remainingContent = 0 - *remainingPadding = 0 - break - } - } - } - if *remainingContent == -1 && *remainingPadding == -1 { - return buffer - } - mb2 := make(buf.MultiBuffer, 0, len(buffer)) - for i := 0; i < posindex; i++ { - newbuffer := buf.New() - newbuffer.Write(buffer[i].Bytes()) - mb2 = append(mb2, newbuffer) - } - for i := posindex; i < len(buffer); i++ { - b := buffer[i] - for posByte < b.Len() { - if *remainingContent <= 0 && *remainingPadding <= 0 { - if *currentCommand == 1 { // possible buffer after padding, no need to worry about xtls (command 2) - len := b.Len() - posByte - newbuffer := buf.New() - newbuffer.Write(b.BytesRange(posByte, posByte+len)) - mb2 = append(mb2, newbuffer) - posByte += len - } else { - paddingInfo := b.BytesRange(posByte, posByte+5) - *currentCommand = int(paddingInfo[0]) - *remainingContent = int32(paddingInfo[1])<<8 | int32(paddingInfo[2]) - *remainingPadding = int32(paddingInfo[3])<<8 | int32(paddingInfo[4]) - newError("Xtls Unpadding new block", i, " ", posByte, " content ", *remainingContent, " padding ", *remainingPadding, " ", paddingInfo[0]).WriteToLog(session.ExportIDToError(ctx)) - posByte += 5 - } - } else if *remainingContent > 0 { - len := *remainingContent - if b.Len() < posByte+*remainingContent { - len = b.Len() - posByte - } - newbuffer := buf.New() - newbuffer.Write(b.BytesRange(posByte, posByte+len)) - mb2 = append(mb2, newbuffer) - *remainingContent -= len - posByte += len - } else { // remainingPadding > 0 - len := *remainingPadding - if b.Len() < posByte+*remainingPadding { - len = b.Len() - posByte - } - *remainingPadding -= len - posByte += len - } - if posByte == b.Len() { - posByte = 0 - break - } - } - } - buf.ReleaseMulti(buffer) - return mb2 -} - -var Tls13CipherSuiteDic = map[uint16]string{ - 0x1301 : "TLS_AES_128_GCM_SHA256", - 0x1302 : "TLS_AES_256_GCM_SHA384", - 0x1303 : "TLS_CHACHA20_POLY1305_SHA256", - 0x1304 : "TLS_AES_128_CCM_SHA256", - 0x1305 : "TLS_AES_128_CCM_8_SHA256", -} diff --git a/proxy/vless/inbound/config.pb.go b/proxy/vless/inbound/config.pb.go index e69d7bf1fbad..f15f91144e79 100644 --- a/proxy/vless/inbound/config.pb.go +++ b/proxy/vless/inbound/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vless/inbound/config.proto package inbound diff --git a/proxy/vless/inbound/inbound.go b/proxy/vless/inbound/inbound.go index 8aa337c30904..4cd3fcb1b4b0 100644 --- a/proxy/vless/inbound/inbound.go +++ b/proxy/vless/inbound/inbound.go @@ -3,39 +3,39 @@ package inbound //go:generate go run github.com/xtls/xray-core/common/errors/errorgen import ( + "bytes" "context" + gotls "crypto/tls" "io" + "reflect" "strconv" "strings" - "syscall" "time" + "unsafe" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/task" - core "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/dns" feature_inbound "github.com/xtls/xray-core/features/inbound" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" - "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless/encoding" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) -var xtls_show = false - func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { var dc dns.Client @@ -47,13 +47,6 @@ func init() { } return New(ctx, config.(*Config), dc) })) - - const defaultFlagValue = "NOT_DEFINED_AT_ALL" - - xtlsShow := platform.NewEnvFlag("xray.vless.xtls.show").GetValue(func() string { return defaultFlagValue }) - if xtlsShow == "true" { - xtls_show = true - } } // Handler is an inbound connection handler that handles messages in VLess protocol. @@ -149,6 +142,19 @@ func New(ctx context.Context, config *Config, dc dns.Client) (*Handler, error) { return handler, nil } +func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool { + if request.Command != protocol.RequestCommandMux { + return false + } + if first.Len() < 7 { + return true + } + firstBytes := first.Bytes() + return !(firstBytes[2] == 0 && // ID high + firstBytes[3] == 0 && // ID low + firstBytes[6] == 2) // Network type: UDP +} + // Close implements common.Closable.Close(). func (h *Handler) Close() error { return errors.Combine(common.Close(h.validator)) @@ -174,8 +180,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s sid := session.ExportIDToError(ctx) iConn := connection - statConn, ok := iConn.(*stat.CounterConnection) - if ok { + if statConn, ok := iConn.(*stat.CounterConnection); ok { iConn = statConn.Connection } @@ -184,9 +189,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s return newError("unable to set read deadline").Base(err).AtWarning() } - first := buf.New() - defer first.Release() - + first := buf.FromBytes(make([]byte, buf.Size)) + first.Clear() firstLen, _ := first.ReadFrom(connection) newError("firstLen = ", firstLen).AtInfo().WriteToLog(sid) @@ -223,8 +227,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s alpn = cs.NegotiatedProtocol newError("realName = " + name).AtInfo().WriteToLog(sid) newError("realAlpn = " + alpn).AtInfo().WriteToLog(sid) - } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { - cs := xtlsConn.ConnectionState() + } else if realityConn, ok := iConn.(*reality.Conn); ok { + cs := realityConn.ConnectionState() name = cs.ServerName alpn = cs.NegotiatedProtocol newError("realName = " + name).AtInfo().WriteToLog(sid) @@ -431,6 +435,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s if inbound == nil { panic("no inbound metadata") } + inbound.Name = "vless" inbound.User = request.User account := request.User.Account.(*vless.MemoryAccount) @@ -439,62 +444,44 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s // Flow: requestAddons.Flow, } - var netConn net.Conn - var rawConn syscall.RawConn - allowNoneFlow := false - accountFlow := account.Flow - flows := strings.Split(account.Flow, ",") - for _, f := range flows { - t := strings.TrimSpace(f) - if t == "none" { - allowNoneFlow = true - } else { - accountFlow = t - } - } + var input *bytes.Reader + var rawInput *bytes.Buffer switch requestAddons.Flow { - case vless.XRO, vless.XRD, vless.XRV: - if accountFlow == requestAddons.Flow { + case vless.XRV: + if account.Flow == requestAddons.Flow { + inbound.SetCanSpliceCopy(2) switch request.Command { - case protocol.RequestCommandMux: - return newError(requestAddons.Flow + " doesn't support Mux").AtWarning() case protocol.RequestCommandUDP: return newError(requestAddons.Flow + " doesn't support UDP").AtWarning() + case protocol.RequestCommandMux: + fallthrough // we will break Mux connections that contain TCP requests case protocol.RequestCommandTCP: - if requestAddons.Flow == vless.XRV { - if tlsConn, ok := iConn.(*tls.Conn); ok { - netConn = tlsConn.NetConn() - if sc, ok := netConn.(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } - } else if _, ok := iConn.(*tls.UConn); ok { - return newError("XTLS only supports UTLS fingerprint for the outbound.").AtWarning() - } else if _, ok := iConn.(*xtls.Conn); ok { - return newError(`failed to use ` + requestAddons.Flow + `, vision "security" must be "tls"`).AtWarning() - } else { - return newError("XTLS only supports TCP, mKCP and DomainSocket for now.").AtWarning() - } - } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { - xtlsConn.RPRX = true - xtlsConn.SHOW = xtls_show - xtlsConn.MARK = "XTLS" - if requestAddons.Flow == vless.XRD { - xtlsConn.DirectMode = true - if sc, ok := xtlsConn.NetConn().(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } + var t reflect.Type + var p uintptr + if tlsConn, ok := iConn.(*tls.Conn); ok { + if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { + return newError(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() } + t = reflect.TypeOf(tlsConn.Conn).Elem() + p = uintptr(unsafe.Pointer(tlsConn.Conn)) + } else if realityConn, ok := iConn.(*reality.Conn); ok { + t = reflect.TypeOf(realityConn.Conn).Elem() + p = uintptr(unsafe.Pointer(realityConn.Conn)) } else { - return newError(`failed to use ` + requestAddons.Flow + `, maybe "security" is not "xtls"`).AtWarning() + return newError("XTLS only supports TLS and REALITY directly for now.").AtWarning() } + i, _ := t.FieldByName("input") + r, _ := t.FieldByName("rawInput") + input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset)) + rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset)) } } else { return newError(account.ID.String() + " is not able to use " + requestAddons.Flow).AtWarning() } - case "", "none": - if accountFlow == vless.XRV && !allowNoneFlow && request.Command == protocol.RequestCommandTCP { - return newError(account.ID.String() + " is not able to use " + vless.XRV + - ". Note the pure tls proxy has certain tls in tls characters. Append \",none\" in flow to suppress").AtWarning() + case "": + inbound.SetCanSpliceCopy(3) + if account.Flow == vless.XRV && (request.Command == protocol.RequestCommandTCP || isMuxAndNotXUDP(request, first)) { + return newError(account.ID.String() + " is not able to use \"\". Note that the pure TLS proxy has certain TLS in TLS characters.").AtWarning() } default: return newError("unknown request flow " + requestAddons.Flow).AtWarning() @@ -508,6 +495,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s Reason: "", Email: request.User.Email, }) + } else if account.Flow == vless.XRV { + ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP) } sessionPolicy = h.policyManager.ForLevel(request.User.Level) @@ -522,13 +511,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s serverReader := link.Reader // .(*pipe.Reader) serverWriter := link.Writer // .(*pipe.Writer) - enableXtls := false - isTLS12orAbove := false - isTLS := false - var cipher uint16 = 0 - var remainingServerHello int32 = -1 - numberOfPacketToFilter := 8 - + trafficState := proxy.NewTrafficState(account.ID.Bytes()) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) @@ -537,19 +520,9 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s var err error - if rawConn != nil { - var counter stats.Counter - if statConn != nil { - counter = statConn.ReadCounter - } - //TODO enable splice - ctx = session.ContextWithInbound(ctx, nil) - if requestAddons.Flow == vless.XRV { - err = encoding.XtlsRead(clientReader, serverWriter, timer, netConn, rawConn, counter, ctx, account.ID.Bytes(), - &numberOfPacketToFilter, &enableXtls, &isTLS12orAbove, &isTLS, &cipher, &remainingServerHello) - } else { - err = encoding.ReadV(clientReader, serverWriter, timer, iConn.(*xtls.Conn), rawConn, counter, ctx) - } + if requestAddons.Flow == vless.XRV { + ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice + err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, ctx1) } else { // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) @@ -571,21 +544,11 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s } // default: clientWriter := bufferWriter - clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, responseAddons) - userUUID := account.ID.Bytes() + clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, ctx) multiBuffer, err1 := serverReader.ReadMultiBuffer() if err1 != nil { return err1 // ... } - if requestAddons.Flow == vless.XRV { - encoding.XtlsFilterTls(multiBuffer, &numberOfPacketToFilter, &enableXtls, &isTLS12orAbove, &isTLS, &cipher, &remainingServerHello, ctx) - if isTLS { - multiBuffer = encoding.ReshapeMultiBuffer(ctx, multiBuffer) - for i, b := range multiBuffer { - multiBuffer[i] = encoding.XtlsPadding(b, 0x00, &userUUID, ctx) - } - } - } if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil { return err // ... } @@ -595,13 +558,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s } var err error - if rawConn != nil && requestAddons.Flow == vless.XRV { - var counter stats.Counter - if statConn != nil { - counter = statConn.WriteCounter - } - err = encoding.XtlsWrite(serverReader, clientWriter, timer, netConn, counter, ctx, &userUUID, &numberOfPacketToFilter, - &enableXtls, &isTLS12orAbove, &isTLS, &cipher, &remainingServerHello) + if requestAddons.Flow == vless.XRV { + err = encoding.XtlsWrite(serverReader, clientWriter, timer, connection, trafficState, ctx) } else { // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)) diff --git a/proxy/vless/outbound/config.pb.go b/proxy/vless/outbound/config.pb.go index 14839ba74771..5bd8912a5cf7 100644 --- a/proxy/vless/outbound/config.pb.go +++ b/proxy/vless/outbound/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vless/outbound/config.proto package outbound diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index 6dde2736d2a5..cd30617c613b 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -3,45 +3,39 @@ package outbound //go:generate go run github.com/xtls/xray-core/common/errors/errorgen import ( + "bytes" "context" - "syscall" + gotls "crypto/tls" + "reflect" "time" + "unsafe" + utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/xudp" - core "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/policy" - "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless/encoding" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) -var xtls_show = false - func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) - - const defaultFlagValue = "NOT_DEFINED_AT_ALL" - - xtlsShow := platform.NewEnvFlag("xray.vless.xtls.show").GetValue(func() string { return defaultFlagValue }) - if xtlsShow == "true" { - xtls_show = true - } } // Handler is an outbound connection handler for VLess protocol. @@ -76,9 +70,15 @@ func New(ctx context.Context, config *Config) (*Handler, error) { // Process implements proxy.Outbound.Process(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified").AtError() + } + outbound.Name = "vless" + inbound := session.InboundFromContext(ctx) + var rec *protocol.ServerSpec var conn stat.Connection - if err := retry.ExponentialBackoff(5, 200).On(func() error { rec = h.serverPicker.PickServer() var err error @@ -93,16 +93,9 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte defer conn.Close() iConn := conn - statConn, ok := iConn.(*stat.CounterConnection) - if ok { + if statConn, ok := iConn.(*stat.CounterConnection); ok { iConn = statConn.Connection } - - outbound := session.OutboundFromContext(ctx) - if outbound == nil || !outbound.Target.IsValid() { - return newError("target not specified").AtError() - } - target := outbound.Target newError("tunneling request to ", target, " via ", rec.Destination().NetAddr()).AtInfo().WriteToLog(session.ExportIDToError(ctx)) @@ -128,76 +121,70 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte Flow: account.Flow, } - var netConn net.Conn - var rawConn syscall.RawConn + var input *bytes.Reader + var rawInput *bytes.Buffer allowUDP443 := false switch requestAddons.Flow { - case vless.XRO + "-udp443", vless.XRD + "-udp443", vless.XRS + "-udp443", vless.XRV + "-udp443": + case vless.XRV + "-udp443": allowUDP443 = true requestAddons.Flow = requestAddons.Flow[:16] fallthrough - case vless.XRO, vless.XRD, vless.XRS, vless.XRV: + case vless.XRV: + if inbound != nil { + inbound.SetCanSpliceCopy(2) + } switch request.Command { - case protocol.RequestCommandMux: - return newError(requestAddons.Flow + " doesn't support Mux").AtWarning() case protocol.RequestCommandUDP: if !allowUDP443 && request.Port == 443 { - return newError(requestAddons.Flow + " stopped UDP/443").AtInfo() + return newError("XTLS rejected UDP/443 traffic").AtInfo() } requestAddons.Flow = "" + case protocol.RequestCommandMux: + fallthrough // let server break Mux connections that contain TCP requests case protocol.RequestCommandTCP: - if requestAddons.Flow == vless.XRV { - if tlsConn, ok := iConn.(*tls.Conn); ok { - netConn = tlsConn.NetConn() - if sc, ok := netConn.(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } - } else if utlsConn, ok := iConn.(*tls.UConn); ok { - netConn = utlsConn.Conn.NetConn() - if sc, ok := netConn.(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } - } else if _, ok := iConn.(*xtls.Conn); ok { - return newError(`failed to use ` + requestAddons.Flow + `, vision "security" must be "tls"`).AtWarning() - } else { - return newError("XTLS only supports TCP, mKCP and DomainSocket for now.").AtWarning() - } - } else if xtlsConn, ok := iConn.(*xtls.Conn); ok { - xtlsConn.RPRX = true - xtlsConn.SHOW = xtls_show - xtlsConn.MARK = "XTLS" - if requestAddons.Flow == vless.XRS { - requestAddons.Flow = vless.XRD - } - if requestAddons.Flow == vless.XRD { - xtlsConn.DirectMode = true - if sc, ok := xtlsConn.NetConn().(syscall.Conn); ok { - rawConn, _ = sc.SyscallConn() - } - } + var t reflect.Type + var p uintptr + if tlsConn, ok := iConn.(*tls.Conn); ok { + t = reflect.TypeOf(tlsConn.Conn).Elem() + p = uintptr(unsafe.Pointer(tlsConn.Conn)) + } else if utlsConn, ok := iConn.(*tls.UConn); ok { + t = reflect.TypeOf(utlsConn.Conn).Elem() + p = uintptr(unsafe.Pointer(utlsConn.Conn)) + } else if realityConn, ok := iConn.(*reality.UConn); ok { + t = reflect.TypeOf(realityConn.Conn).Elem() + p = uintptr(unsafe.Pointer(realityConn.Conn)) } else { - return newError(`failed to use ` + requestAddons.Flow + `, maybe "security" is not "xtls"`).AtWarning() + return newError("XTLS only supports TLS and REALITY directly for now.").AtWarning() } + i, _ := t.FieldByName("input") + r, _ := t.FieldByName("rawInput") + input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset)) + rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset)) } default: - if _, ok := iConn.(*xtls.Conn); ok { - panic(`To avoid misunderstanding, you must fill in VLESS "flow" when using XTLS.`) + if inbound != nil { + inbound.SetCanSpliceCopy(3) } } + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } + sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) clientReader := link.Reader // .(*pipe.Reader) clientWriter := link.Writer // .(*pipe.Writer) - enableXtls := false - isTLS12orAbove := false - isTLS := false - var cipher uint16 = 0 - var remainingServerHello int32 = -1 - numberOfPacketToFilter := 8 - + trafficState := proxy.NewTrafficState(account.ID.Bytes()) if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 { request.Command = protocol.RequestCommandMux request.Address = net.DomainAddress("v1.mux.cool") @@ -213,28 +200,25 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } // default: serverWriter := bufferWriter - serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons) + serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, ctx) if request.Command == protocol.RequestCommandMux && request.Port == 666 { - serverWriter = xudp.NewPacketWriter(serverWriter, target) + serverWriter = xudp.NewPacketWriter(serverWriter, target, xudp.GetGlobalID(ctx)) } - userUUID := account.ID.Bytes() timeoutReader, ok := clientReader.(buf.TimeoutReader) if ok { - multiBuffer, err1 := timeoutReader.ReadMultiBufferTimeout(time.Millisecond*500) + multiBuffer, err1 := timeoutReader.ReadMultiBufferTimeout(time.Millisecond * 500) if err1 == nil { - if requestAddons.Flow == vless.XRV { - encoding.XtlsFilterTls(multiBuffer, &numberOfPacketToFilter, &enableXtls, &isTLS12orAbove, &isTLS, &cipher, &remainingServerHello, ctx) - if isTLS { - for i, b := range multiBuffer { - multiBuffer[i] = encoding.XtlsPadding(b, 0x00, &userUUID, ctx) - } - } - } if err := serverWriter.WriteMultiBuffer(multiBuffer); err != nil { return err // ... } } else if err1 != buf.ErrReadTimeout { return err1 + } else if requestAddons.Flow == vless.XRV { + mb := make(buf.MultiBuffer, 1) + newError("Insert padding with empty content to camouflage VLESS header ", mb.Len()).WriteToLog(session.ExportIDToError(ctx)) + if err := serverWriter.WriteMultiBuffer(mb); err != nil { + return err // ... + } } } else { newError("Reader is not timeout reader, will send out vless header separately from first payload").AtDebug().WriteToLog(session.ExportIDToError(ctx)) @@ -245,13 +229,18 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } var err error - if rawConn != nil && requestAddons.Flow == vless.XRV { - var counter stats.Counter - if statConn != nil { - counter = statConn.WriteCounter + if requestAddons.Flow == vless.XRV { + if tlsConn, ok := iConn.(*tls.Conn); ok { + if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { + return newError(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() + } + } else if utlsConn, ok := iConn.(*tls.UConn); ok { + if utlsConn.ConnectionState().Version != utls.VersionTLS13 { + return newError(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning() + } } - err = encoding.XtlsWrite(clientReader, serverWriter, timer, netConn, counter, ctx, &userUUID, &numberOfPacketToFilter, - &enableXtls, &isTLS12orAbove, &isTLS, &cipher, &remainingServerHello) + ctx1 := session.ContextWithOutbound(ctx, nil) // TODO enable splice + err = encoding.XtlsWrite(clientReader, serverWriter, timer, conn, trafficState, ctx1) } else { // from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)) @@ -281,20 +270,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte serverReader = xudp.NewPacketReader(conn) } - if rawConn != nil { - var counter stats.Counter - if statConn != nil { - counter = statConn.ReadCounter - } - if requestAddons.Flow == vless.XRV { - err = encoding.XtlsRead(serverReader, clientWriter, timer, netConn, rawConn, counter, ctx, account.ID.Bytes(), - &numberOfPacketToFilter, &enableXtls, &isTLS12orAbove, &isTLS, &cipher, &remainingServerHello) - } else { - if requestAddons.Flow != vless.XRS { - ctx = session.ContextWithInbound(ctx, nil) - } - err = encoding.ReadV(serverReader, clientWriter, timer, iConn.(*xtls.Conn), rawConn, counter, ctx) - } + if requestAddons.Flow == vless.XRV { + err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ctx) } else { // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)) @@ -307,6 +284,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return nil } + if newCtx != nil { + ctx = newCtx + } + if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil { return newError("connection ends").Base(err).AtInfo() } diff --git a/proxy/vless/vless.go b/proxy/vless/vless.go index 1e78b8afbadd..09827a540eba 100644 --- a/proxy/vless/vless.go +++ b/proxy/vless/vless.go @@ -8,8 +8,5 @@ package vless //go:generate go run github.com/xtls/xray-core/common/errors/errorgen const ( - XRO = "xtls-rprx-origin" - XRD = "xtls-rprx-direct" - XRS = "xtls-rprx-splice" XRV = "xtls-rprx-vision" ) diff --git a/proxy/vmess/account.go b/proxy/vmess/account.go index 809bca213f4c..d0a776e6d3c7 100644 --- a/proxy/vmess/account.go +++ b/proxy/vmess/account.go @@ -3,7 +3,6 @@ package vmess import ( "strings" - "github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/uuid" ) @@ -12,8 +11,6 @@ import ( type MemoryAccount struct { // ID is the main ID of the account. ID *protocol.ID - // AlterIDs are the alternative IDs of the account. - AlterIDs []*protocol.ID // Security type of the account. Used for client connections. Security protocol.SecurityType @@ -21,21 +18,12 @@ type MemoryAccount struct { NoTerminationSignal bool } -// AnyValidID returns an ID that is either the main ID or one of the alternative IDs if any. -func (a *MemoryAccount) AnyValidID() *protocol.ID { - if len(a.AlterIDs) == 0 { - return a.ID - } - return a.AlterIDs[dice.Roll(len(a.AlterIDs))] -} - // Equals implements protocol.Account. func (a *MemoryAccount) Equals(account protocol.Account) bool { vmessAccount, ok := account.(*MemoryAccount) if !ok { return false } - // TODO: handle AlterIds difference return a.ID.Equals(vmessAccount.ID) } @@ -55,7 +43,6 @@ func (a *Account) AsAccount() (protocol.Account, error) { } return &MemoryAccount{ ID: protoID, - AlterIDs: protocol.NewAlterIDs(protoID, uint16(a.AlterId)), Security: a.SecuritySettings.GetSecurityType(), AuthenticatedLengthExperiment: AuthenticatedLength, NoTerminationSignal: NoTerminationSignal, diff --git a/proxy/vmess/account.pb.go b/proxy/vmess/account.pb.go index bd3ad96e7861..9938cfb11b7a 100644 --- a/proxy/vmess/account.pb.go +++ b/proxy/vmess/account.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vmess/account.proto package vmess @@ -29,8 +29,6 @@ type Account struct { // ID of the account, in the form of a UUID, e.g., // "66ad4540-b58c-4ad2-9926-ea63445a9b57". Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // Number of alternative IDs. Client and server must share the same number. - AlterId uint32 `protobuf:"varint,2,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"` // Security settings. Only applies to client side. SecuritySettings *protocol.SecurityConfig `protobuf:"bytes,3,opt,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"` // Define tests enabled for this account @@ -76,13 +74,6 @@ func (x *Account) GetId() string { return "" } -func (x *Account) GetAlterId() uint32 { - if x != nil { - return x.AlterId - } - return 0 -} - func (x *Account) GetSecuritySettings() *protocol.SecurityConfig { if x != nil { return x.SecuritySettings @@ -104,24 +95,22 @@ var file_proxy_vmess_account_proto_rawDesc = []byte{ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x1a, 0x1d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x68, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xac, 0x01, 0x0a, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x91, 0x01, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x74, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6c, 0x74, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x51, 0x0a, 0x11, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, - 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x73, 0x5f, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, - 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x52, 0x0a, 0x14, 0x63, - 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, - 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, - 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, - 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x51, 0x0a, 0x11, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, + 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x65, 0x63, 0x75, 0x72, + 0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, + 0x65, 0x73, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, + 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, + 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, + 0x6d, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proxy/vmess/account.proto b/proxy/vmess/account.proto index 98dc681ea7d2..3fac639909c4 100644 --- a/proxy/vmess/account.proto +++ b/proxy/vmess/account.proto @@ -12,8 +12,6 @@ message Account { // ID of the account, in the form of a UUID, e.g., // "66ad4540-b58c-4ad2-9926-ea63445a9b57". string id = 1; - // Number of alternative IDs. Client and server must share the same number. - uint32 alter_id = 2; // Security settings. Only applies to client side. xray.common.protocol.SecurityConfig security_settings = 3; // Define tests enabled for this account diff --git a/proxy/vmess/encoding/auth.go b/proxy/vmess/encoding/auth.go index 09689cea895f..99bdaa49c82f 100644 --- a/proxy/vmess/encoding/auth.go +++ b/proxy/vmess/encoding/auth.go @@ -17,6 +17,7 @@ func Authenticate(b []byte) uint32 { return fnv1hash.Sum32() } +// [DEPRECATED 2023-06] type NoOpAuthenticator struct{} func (NoOpAuthenticator) NonceSize() int { @@ -37,34 +38,6 @@ func (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([] return append(dst[:0], ciphertext...), nil } -// FnvAuthenticator is an AEAD based on Fnv hash. -type FnvAuthenticator struct{} - -// NonceSize implements AEAD.NonceSize(). -func (*FnvAuthenticator) NonceSize() int { - return 0 -} - -// Overhead impelements AEAD.Overhead(). -func (*FnvAuthenticator) Overhead() int { - return 4 -} - -// Seal implements AEAD.Seal(). -func (*FnvAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte { - dst = append(dst, 0, 0, 0, 0) - binary.BigEndian.PutUint32(dst, Authenticate(plaintext)) - return append(dst, plaintext...) -} - -// Open implements AEAD.Open(). -func (*FnvAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { - if binary.BigEndian.Uint32(ciphertext[:4]) != Authenticate(ciphertext[4:]) { - return dst, newError("invalid authentication") - } - return append(dst, ciphertext[4:]...), nil -} - // GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array. func GenerateChacha20Poly1305Key(b []byte) []byte { key := make([]byte, 32) diff --git a/proxy/vmess/encoding/auth_test.go b/proxy/vmess/encoding/auth_test.go deleted file mode 100644 index ae83076f619d..000000000000 --- a/proxy/vmess/encoding/auth_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package encoding_test - -import ( - "crypto/rand" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/xtls/xray-core/common" - . "github.com/xtls/xray-core/proxy/vmess/encoding" -) - -func TestFnvAuth(t *testing.T) { - fnvAuth := new(FnvAuthenticator) - - expectedText := make([]byte, 256) - _, err := rand.Read(expectedText) - common.Must(err) - - buffer := make([]byte, 512) - b := fnvAuth.Seal(buffer[:0], nil, expectedText, nil) - b, err = fnvAuth.Open(buffer[:0], nil, b, nil) - common.Must(err) - if r := cmp.Diff(b, expectedText); r != "" { - t.Error(r) - } -} diff --git a/proxy/vmess/encoding/client.go b/proxy/vmess/encoding/client.go index ba08843e4d3e..0b7a00981463 100644 --- a/proxy/vmess/encoding/client.go +++ b/proxy/vmess/encoding/client.go @@ -5,11 +5,9 @@ import ( "context" "crypto/aes" "crypto/cipher" - "crypto/md5" "crypto/rand" "crypto/sha256" "encoding/binary" - "hash" "hash/fnv" "io" @@ -20,24 +18,13 @@ import ( "github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/drain" "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/vmess" vmessaead "github.com/xtls/xray-core/proxy/vmess/aead" "golang.org/x/crypto/chacha20poly1305" ) -func hashTimestamp(h hash.Hash, t protocol.Timestamp) []byte { - common.Must2(serial.WriteUint64(h, uint64(t))) - common.Must2(serial.WriteUint64(h, uint64(t))) - common.Must2(serial.WriteUint64(h, uint64(t))) - common.Must2(serial.WriteUint64(h, uint64(t))) - return h.Sum(nil) -} - // ClientSession stores connection session info for VMess client. type ClientSession struct { - isAEAD bool - idHash protocol.IDHash requestBodyKey [16]byte requestBodyIV [16]byte responseBodyKey [16]byte @@ -49,11 +36,8 @@ type ClientSession struct { } // NewClientSession creates a new ClientSession. -func NewClientSession(ctx context.Context, isAEAD bool, idHash protocol.IDHash, behaviorSeed int64) *ClientSession { - session := &ClientSession{ - isAEAD: isAEAD, - idHash: idHash, - } +func NewClientSession(ctx context.Context, behaviorSeed int64) *ClientSession { + session := &ClientSession{} randomBytes := make([]byte, 33) // 16 + 16 + 1 common.Must2(rand.Read(randomBytes)) @@ -61,15 +45,10 @@ func NewClientSession(ctx context.Context, isAEAD bool, idHash protocol.IDHash, copy(session.requestBodyIV[:], randomBytes[16:32]) session.responseHeader = randomBytes[32] - if !session.isAEAD { - session.responseBodyKey = md5.Sum(session.requestBodyKey[:]) - session.responseBodyIV = md5.Sum(session.requestBodyIV[:]) - } else { - BodyKey := sha256.Sum256(session.requestBodyKey[:]) - copy(session.responseBodyKey[:], BodyKey[:16]) - BodyIV := sha256.Sum256(session.requestBodyIV[:]) - copy(session.responseBodyIV[:], BodyIV[:16]) - } + BodyKey := sha256.Sum256(session.requestBodyKey[:]) + copy(session.responseBodyKey[:], BodyKey[:16]) + BodyIV := sha256.Sum256(session.requestBodyIV[:]) + copy(session.responseBodyIV[:], BodyIV[:16]) { var err error session.readDrainer, err = drain.NewBehaviorSeedLimitedDrainer(behaviorSeed, 18, 3266, 64) @@ -83,13 +62,7 @@ func NewClientSession(ctx context.Context, isAEAD bool, idHash protocol.IDHash, } func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) error { - timestamp := protocol.NewTimestampGenerator(protocol.NowTime(), 30)() account := header.User.Account.(*vmess.MemoryAccount) - if !c.isAEAD { - idHash := c.idHash(account.AnyValidID().Bytes()) - common.Must2(serial.WriteUint64(idHash, uint64(timestamp))) - common.Must2(writer.Write(idHash.Sum(nil))) - } buffer := buf.New() defer buffer.Release() @@ -121,17 +94,10 @@ func (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ fnv1a.Sum(hashBytes[:0]) } - if !c.isAEAD { - iv := hashTimestamp(md5.New(), timestamp) - aesStream := crypto.NewAesEncryptionStream(account.ID.CmdKey(), iv) - aesStream.XORKeyStream(buffer.Bytes(), buffer.Bytes()) - common.Must2(writer.Write(buffer.Bytes())) - } else { - var fixedLengthCmdKey [16]byte - copy(fixedLengthCmdKey[:], account.ID.CmdKey()) - vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes()) - common.Must2(io.Copy(writer, bytes.NewReader(vmessout))) - } + var fixedLengthCmdKey [16]byte + copy(fixedLengthCmdKey[:], account.ID.CmdKey()) + vmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes()) + common.Must2(io.Copy(writer, bytes.NewReader(vmessout))) return nil } @@ -165,19 +131,6 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write } return buf.NewWriter(writer), nil - case protocol.SecurityType_LEGACY: - aesStream := crypto.NewAesEncryptionStream(c.requestBodyKey[:], c.requestBodyIV[:]) - cryptionWriter := crypto.NewCryptionWriter(aesStream, writer) - if request.Option.Has(protocol.RequestOptionChunkStream) { - auth := &crypto.AEADAuthenticator{ - AEAD: new(FnvAuthenticator), - NonceGenerator: crypto.GenerateEmptyBytes(), - AdditionalDataGenerator: crypto.GenerateEmptyBytes(), - } - return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding), nil - } - - return &buf.SequentialWriter{Writer: cryptionWriter}, nil case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(c.requestBodyKey[:]) auth := &crypto.AEADAuthenticator{ @@ -225,53 +178,48 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write } func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) { - if !c.isAEAD { - aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:]) - c.responseReader = crypto.NewCryptionReader(aesStream, reader) - } else { - aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey) - aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12] - - aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block) - aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD) - - var aeadEncryptedResponseHeaderLength [18]byte - var decryptedResponseHeaderLength int - var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16 - - if n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil { - c.readDrainer.AcknowledgeReceive(n) - return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Len").Base(err)) - } else { // nolint: golint - c.readDrainer.AcknowledgeReceive(n) - } - if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil { - return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Length").Base(err)) - } else { // nolint: golint - common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer)) - decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer) - } + aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey) + aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12] - aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey) - aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12] + aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block) + aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD) - aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block) - aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD) + var aeadEncryptedResponseHeaderLength [18]byte + var decryptedResponseHeaderLength int + var decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16 - encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16) + if n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil { + c.readDrainer.AcknowledgeReceive(n) + return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Len").Base(err)) + } else { // nolint: golint + c.readDrainer.AcknowledgeReceive(n) + } + if decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil { + return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Length").Base(err)) + } else { // nolint: golint + common.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer)) + decryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer) + } - if n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil { - c.readDrainer.AcknowledgeReceive(n) - return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Data").Base(err)) - } else { // nolint: golint - c.readDrainer.AcknowledgeReceive(n) - } + aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey) + aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12] - if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil { - return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Payload").Base(err)) - } else { // nolint: golint - c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer) - } + aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block) + aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD) + + encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16) + + if n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil { + c.readDrainer.AcknowledgeReceive(n) + return nil, drain.WithError(c.readDrainer, reader, newError("Unable to Read Header Data").Base(err)) + } else { // nolint: golint + c.readDrainer.AcknowledgeReceive(n) + } + + if decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil { + return nil, drain.WithError(c.readDrainer, reader, newError("Failed To Decrypt Payload").Base(err)) + } else { // nolint: golint + c.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer) } buffer := buf.StackNew() @@ -302,10 +250,8 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon header.Command = command } } - if c.isAEAD { - aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:]) - c.responseReader = crypto.NewCryptionReader(aesStream, reader) - } + aesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:]) + c.responseReader = crypto.NewCryptionReader(aesStream, reader) return header, nil } @@ -340,17 +286,6 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read } return buf.NewReader(reader), nil - case protocol.SecurityType_LEGACY: - if request.Option.Has(protocol.RequestOptionChunkStream) { - auth := &crypto.AEADAuthenticator{ - AEAD: new(FnvAuthenticator), - NonceGenerator: crypto.GenerateEmptyBytes(), - AdditionalDataGenerator: crypto.GenerateEmptyBytes(), - } - return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding), nil - } - - return buf.NewReader(c.responseReader), nil case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(c.responseBodyKey[:]) diff --git a/proxy/vmess/encoding/commands.go b/proxy/vmess/encoding/commands.go index 1b42002141cb..5a4d3708828a 100644 --- a/proxy/vmess/encoding/commands.go +++ b/proxy/vmess/encoding/commands.go @@ -101,7 +101,7 @@ func (f *CommandSwitchAccountFactory) Marshal(command interface{}, writer io.Wri idBytes := cmd.ID.Bytes() common.Must2(writer.Write(idBytes)) - common.Must2(serial.WriteUint16(writer, cmd.AlterIds)) + common.Must2(serial.WriteUint16(writer, 0)) // compatible with legacy alterId common.Must2(writer.Write([]byte{byte(cmd.Level)})) common.Must2(writer.Write([]byte{cmd.ValidMin})) @@ -130,12 +130,7 @@ func (f *CommandSwitchAccountFactory) Unmarshal(data []byte) (interface{}, error return nil, ErrInsufficientLength } cmd.ID, _ = uuid.ParseBytes(data[idStart : idStart+16]) - alterIDStart := idStart + 16 - if len(data) < alterIDStart+2 { - return nil, ErrInsufficientLength - } - cmd.AlterIds = binary.BigEndian.Uint16(data[alterIDStart : alterIDStart+2]) - levelStart := alterIDStart + 2 + levelStart := idStart + 16 + 2 if len(data) < levelStart+1 { return nil, ErrInsufficientLength } diff --git a/proxy/vmess/encoding/commands_test.go b/proxy/vmess/encoding/commands_test.go index 17892bb638f5..c54159596193 100644 --- a/proxy/vmess/encoding/commands_test.go +++ b/proxy/vmess/encoding/commands_test.go @@ -16,7 +16,6 @@ func TestSwitchAccount(t *testing.T) { sa := &protocol.CommandSwitchAccount{ Port: 1234, ID: uuid.New(), - AlterIds: 1024, Level: 128, ValidMin: 16, } @@ -40,7 +39,6 @@ func TestSwitchAccountBugOffByOne(t *testing.T) { sa := &protocol.CommandSwitchAccount{ Port: 1234, ID: uuid.New(), - AlterIds: 1024, Level: 128, ValidMin: 16, } diff --git a/proxy/vmess/encoding/encoding_test.go b/proxy/vmess/encoding/encoding_test.go index a8fd8f3af396..ddae29e15e20 100644 --- a/proxy/vmess/encoding/encoding_test.go +++ b/proxy/vmess/encoding/encoding_test.go @@ -41,7 +41,7 @@ func TestRequestSerialization(t *testing.T) { } buffer := buf.New() - client := NewClientSession(context.TODO(), true, protocol.DefaultIDHash, 0) + client := NewClientSession(context.TODO(), 0) common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) buffer2 := buf.New() @@ -50,7 +50,7 @@ func TestRequestSerialization(t *testing.T) { sessionHistory := NewSessionHistory() defer common.Close(sessionHistory) - userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash) + userValidator := vmess.NewTimedUserValidator() userValidator.Add(user) defer common.Close(userValidator) @@ -90,7 +90,7 @@ func TestInvalidRequest(t *testing.T) { } buffer := buf.New() - client := NewClientSession(context.TODO(), true, protocol.DefaultIDHash, 0) + client := NewClientSession(context.TODO(), 0) common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) buffer2 := buf.New() @@ -99,7 +99,7 @@ func TestInvalidRequest(t *testing.T) { sessionHistory := NewSessionHistory() defer common.Close(sessionHistory) - userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash) + userValidator := vmess.NewTimedUserValidator() userValidator.Add(user) defer common.Close(userValidator) @@ -130,7 +130,7 @@ func TestMuxRequest(t *testing.T) { } buffer := buf.New() - client := NewClientSession(context.TODO(), true, protocol.DefaultIDHash, 0) + client := NewClientSession(context.TODO(), 0) common.Must(client.EncodeRequestHeader(expectedRequest, buffer)) buffer2 := buf.New() @@ -139,7 +139,7 @@ func TestMuxRequest(t *testing.T) { sessionHistory := NewSessionHistory() defer common.Close(sessionHistory) - userValidator := vmess.NewTimedUserValidator(protocol.DefaultIDHash) + userValidator := vmess.NewTimedUserValidator() userValidator.Add(user) defer common.Close(userValidator) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index 4bdb6fb7bc24..371bfdc0a00e 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/md5" "crypto/sha256" "encoding/binary" "hash/fnv" @@ -102,10 +101,6 @@ type ServerSession struct { responseBodyIV [16]byte responseWriter io.Writer responseHeader byte - - isAEADRequest bool - - isAEADForced bool } // NewServerSession creates a new ServerSession, using the given UserValidator. @@ -117,17 +112,12 @@ func NewServerSession(validator *vmess.TimedUserValidator, sessionHistory *Sessi } } -// SetAEADForced sets isAEADForced for a ServerSession. -func (s *ServerSession) SetAEADForced(isAEADForced bool) { - s.isAEADForced = isAEADForced -} - func parseSecurityType(b byte) protocol.SecurityType { if _, f := protocol.SecurityType_name[int32(b)]; f { st := protocol.SecurityType(b) // For backward compatibility. if st == protocol.SecurityType_UNKNOWN { - st = protocol.SecurityType_LEGACY + st = protocol.SecurityType_AUTO } return st } @@ -183,26 +173,6 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr } } decryptor = bytes.NewReader(aeadData) - s.isAEADRequest = true - - case errorAEAD == vmessaead.ErrNotFound: - userLegacy, timestamp, valid, userValidationError := s.userValidator.Get(buffer.Bytes()) - if !valid || userValidationError != nil { - return nil, drainConnection(newError("invalid user").Base(userValidationError)) - } - if s.isAEADForced { - return nil, drainConnection(newError("invalid user: VMessAEAD is enforced and a non VMessAEAD connection is received. You can still disable this security feature with environment variable xray.vmess.aead.forced = false . You will not be able to enable legacy header workaround in the future.")) - } - if s.userValidator.ShouldShowLegacyWarn() { - newError("Critical Warning: potentially invalid user: a non VMessAEAD connection is received. From 2022 Jan 1st, this kind of connection will be rejected by default. You should update or replace your client software now. This message will not be shown for further violation on this inbound.").AtWarning().WriteToLog() - } - user = userLegacy - iv := hashTimestamp(md5.New(), timestamp) - vmessAccount = userLegacy.Account.(*vmess.MemoryAccount) - - aesStream := crypto.NewAesDecryptionStream(vmessAccount.ID.CmdKey(), iv) - decryptor = crypto.NewCryptionReader(aesStream, reader) - default: return nil, drainConnection(newError("invalid user").Base(errorAEAD)) } @@ -225,15 +195,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr sid.key = s.requestBodyKey sid.nonce = s.requestBodyIV if !s.sessionHistory.addIfNotExits(sid) { - if !s.isAEADRequest { - drainErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) - if drainErr != nil { - return nil, drainConnection(newError("duplicated session id, possibly under replay attack, and failed to taint userHash").Base(drainErr)) - } - return nil, drainConnection(newError("duplicated session id, possibly under replay attack, userHash tainted")) - } else { - return nil, newError("duplicated session id, possibly under replay attack, but this is a AEAD request") - } + return nil, newError("duplicated session id, possibly under replay attack, but this is a AEAD request") } s.responseHeader = buffer.Byte(33) // 1 byte @@ -257,25 +219,11 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr if paddingLen > 0 { if _, err := buffer.ReadFullFrom(decryptor, int32(paddingLen)); err != nil { - if !s.isAEADRequest { - burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) - if burnErr != nil { - return nil, newError("failed to read padding, failed to taint userHash").Base(burnErr).Base(err) - } - return nil, newError("failed to read padding, userHash tainted").Base(err) - } return nil, newError("failed to read padding").Base(err) } } if _, err := buffer.ReadFullFrom(decryptor, 4); err != nil { - if !s.isAEADRequest { - burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) - if burnErr != nil { - return nil, newError("failed to read checksum, failed to taint userHash").Base(burnErr).Base(err) - } - return nil, newError("failed to read checksum, userHash tainted").Base(err) - } return nil, newError("failed to read checksum").Base(err) } @@ -285,17 +233,7 @@ func (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*pr expectedHash := binary.BigEndian.Uint32(buffer.BytesFrom(-4)) if actualHash != expectedHash { - if !s.isAEADRequest { - Autherr := newError("invalid auth, legacy userHash tainted") - burnErr := s.userValidator.BurnTaintFuse(fixedSizeAuthID[:]) - if burnErr != nil { - Autherr = newError("invalid auth, can't taint legacy userHash").Base(burnErr) - } - // It is possible that we are under attack described in https://github.com/xray/xray-core/issues/2523 - return nil, drainConnection(Autherr) - } else { - return nil, newError("invalid auth, but this is a AEAD request") - } + return nil, newError("invalid auth, but this is a AEAD request") } if request.Address == nil { @@ -340,19 +278,6 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade } return buf.NewReader(reader), nil - case protocol.SecurityType_LEGACY: - aesStream := crypto.NewAesDecryptionStream(s.requestBodyKey[:], s.requestBodyIV[:]) - cryptionReader := crypto.NewCryptionReader(aesStream, reader) - if request.Option.Has(protocol.RequestOptionChunkStream) { - auth := &crypto.AEADAuthenticator{ - AEAD: new(FnvAuthenticator), - NonceGenerator: crypto.GenerateEmptyBytes(), - AdditionalDataGenerator: crypto.GenerateEmptyBytes(), - } - return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding), nil - } - return buf.NewReader(cryptionReader), nil - case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(s.requestBodyKey[:]) auth := &crypto.AEADAuthenticator{ @@ -403,25 +328,17 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade // EncodeResponseHeader writes encoded response header into the given writer. func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, writer io.Writer) { var encryptionWriter io.Writer - if !s.isAEADRequest { - s.responseBodyKey = md5.Sum(s.requestBodyKey[:]) - s.responseBodyIV = md5.Sum(s.requestBodyIV[:]) - } else { - BodyKey := sha256.Sum256(s.requestBodyKey[:]) - copy(s.responseBodyKey[:], BodyKey[:16]) - BodyIV := sha256.Sum256(s.requestBodyIV[:]) - copy(s.responseBodyIV[:], BodyIV[:16]) - } + BodyKey := sha256.Sum256(s.requestBodyKey[:]) + copy(s.responseBodyKey[:], BodyKey[:16]) + BodyIV := sha256.Sum256(s.requestBodyIV[:]) + copy(s.responseBodyIV[:], BodyIV[:16]) aesStream := crypto.NewAesEncryptionStream(s.responseBodyKey[:], s.responseBodyIV[:]) encryptionWriter = crypto.NewCryptionWriter(aesStream, writer) s.responseWriter = encryptionWriter aeadEncryptedHeaderBuffer := bytes.NewBuffer(nil) - - if s.isAEADRequest { - encryptionWriter = aeadEncryptedHeaderBuffer - } + encryptionWriter = aeadEncryptedHeaderBuffer common.Must2(encryptionWriter.Write([]byte{s.responseHeader, byte(header.Option)})) err := MarshalCommand(header.Command, encryptionWriter) @@ -429,31 +346,29 @@ func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr common.Must2(encryptionWriter.Write([]byte{0x00, 0x00})) } - if s.isAEADRequest { - aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey) - aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12] + aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey) + aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12] - aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block) - aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD) + aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block) + aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD) - aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil) + aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil) - decryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len()) + decryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len()) - common.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer)) + common.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer)) - AEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil) - common.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength))) + AEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil) + common.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength))) - aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey) - aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12] + aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey) + aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12] - aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block) - aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD) + aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block) + aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD) - aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil) - common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload))) - } + aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil) + common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload))) } // EncodeResponseBody returns a Writer that auto-encrypt content written by caller. @@ -487,17 +402,6 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ } return buf.NewWriter(writer), nil - case protocol.SecurityType_LEGACY: - if request.Option.Has(protocol.RequestOptionChunkStream) { - auth := &crypto.AEADAuthenticator{ - AEAD: new(FnvAuthenticator), - NonceGenerator: crypto.GenerateEmptyBytes(), - AdditionalDataGenerator: crypto.GenerateEmptyBytes(), - } - return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding), nil - } - return &buf.SequentialWriter{Writer: s.responseWriter}, nil - case protocol.SecurityType_AES128_GCM: aead := crypto.NewAesGcm(s.responseBodyKey[:]) auth := &crypto.AEADAuthenticator{ diff --git a/proxy/vmess/inbound/config.pb.go b/proxy/vmess/inbound/config.pb.go index 0b381be0e21f..663256aac057 100644 --- a/proxy/vmess/inbound/config.pb.go +++ b/proxy/vmess/inbound/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vmess/inbound/config.proto package inbound @@ -73,8 +73,7 @@ type DefaultConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - AlterId uint32 `protobuf:"varint,1,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"` - Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + Level uint32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` } func (x *DefaultConfig) Reset() { @@ -109,13 +108,6 @@ func (*DefaultConfig) Descriptor() ([]byte, []int) { return file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{1} } -func (x *DefaultConfig) GetAlterId() uint32 { - if x != nil { - return x.AlterId - } - return 0 -} - func (x *DefaultConfig) GetLevel() uint32 { if x != nil { return x.Level @@ -128,10 +120,9 @@ type Config struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` - Default *DefaultConfig `protobuf:"bytes,2,opt,name=default,proto3" json:"default,omitempty"` - Detour *DetourConfig `protobuf:"bytes,3,opt,name=detour,proto3" json:"detour,omitempty"` - SecureEncryptionOnly bool `protobuf:"varint,4,opt,name=secure_encryption_only,json=secureEncryptionOnly,proto3" json:"secure_encryption_only,omitempty"` + User []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` + Default *DefaultConfig `protobuf:"bytes,2,opt,name=default,proto3" json:"default,omitempty"` + Detour *DetourConfig `protobuf:"bytes,3,opt,name=detour,proto3" json:"detour,omitempty"` // 4 is for legacy setting } func (x *Config) Reset() { @@ -187,13 +178,6 @@ func (x *Config) GetDetour() *DetourConfig { return nil } -func (x *Config) GetSecureEncryptionOnly() bool { - if x != nil { - return x.SecureEncryptionOnly - } - return false -} - var File_proxy_vmess_inbound_config_proto protoreflect.FileDescriptor var file_proxy_vmess_inbound_config_proto_rawDesc = []byte{ @@ -204,34 +188,29 @@ var file_proxy_vmess_inbound_config_proto_rawDesc = []byte{ 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1e, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x74, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x6c, 0x74, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xf1, 0x01, 0x0a, 0x06, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, - 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x6f, - 0x75, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, - 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x6e, - 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, - 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x6e, 0x6c, 0x79, 0x42, 0x6a, - 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, - 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, - 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, - 0x79, 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, - 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, - 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x25, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, + 0xbb, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, + 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x3e, 0x0a, + 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, + 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x42, 0x6a, 0x0a, + 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, + 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, + 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, + 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, + 0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, + 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73, + 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/proxy/vmess/inbound/config.proto b/proxy/vmess/inbound/config.proto index 9fc7e36f6076..7da1d5810415 100644 --- a/proxy/vmess/inbound/config.proto +++ b/proxy/vmess/inbound/config.proto @@ -13,7 +13,6 @@ message DetourConfig { } message DefaultConfig { - uint32 alter_id = 1; uint32 level = 2; } @@ -21,5 +20,5 @@ message Config { repeated xray.common.protocol.User user = 1; DefaultConfig default = 2; DetourConfig detour = 3; - bool secure_encryption_only = 4; + // 4 is for legacy setting } diff --git a/proxy/vmess/inbound/inbound.go b/proxy/vmess/inbound/inbound.go index 00b07f14c8d7..679ea5da688e 100644 --- a/proxy/vmess/inbound/inbound.go +++ b/proxy/vmess/inbound/inbound.go @@ -14,7 +14,6 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal" @@ -29,23 +28,16 @@ import ( "github.com/xtls/xray-core/transport/internet/stat" ) -var ( - aeadForced = false - aeadForced2022 = false -) - type userByEmail struct { sync.Mutex - cache map[string]*protocol.MemoryUser - defaultLevel uint32 - defaultAlterIDs uint16 + cache map[string]*protocol.MemoryUser + defaultLevel uint32 } func newUserByEmail(config *DefaultConfig) *userByEmail { return &userByEmail{ - cache: make(map[string]*protocol.MemoryUser), - defaultLevel: config.Level, - defaultAlterIDs: uint16(config.AlterId), + cache: make(map[string]*protocol.MemoryUser), + defaultLevel: config.Level, } } @@ -76,8 +68,7 @@ func (v *userByEmail) Get(email string) (*protocol.MemoryUser, bool) { if !found { id := uuid.New() rawAccount := &vmess.Account{ - Id: id.String(), - AlterId: uint32(v.defaultAlterIDs), + Id: id.String(), } account, err := rawAccount.AsAccount() common.Must(err) @@ -112,7 +103,6 @@ type Handler struct { usersByEmail *userByEmail detours *DetourConfig sessionHistory *encoding.SessionHistory - secure bool } // New creates a new VMess inbound handler. @@ -121,11 +111,10 @@ func New(ctx context.Context, config *Config) (*Handler, error) { handler := &Handler{ policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager), - clients: vmess.NewTimedUserValidator(protocol.DefaultIDHash), + clients: vmess.NewTimedUserValidator(), detours: config.Detour, usersByEmail: newUserByEmail(config.GetDefaultValue()), sessionHistory: encoding.NewSessionHistory(), - secure: config.SecureEncryptionOnly, } for _, user := range config.User { @@ -145,7 +134,6 @@ func New(ctx context.Context, config *Config) (*Handler, error) { // Close implements common.Closable. func (h *Handler) Close() error { return errors.Combine( - h.clients.Close(), h.sessionHistory.Close(), common.Close(h.usersByEmail)) } @@ -219,10 +207,6 @@ func transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSess return nil } -func isInsecureEncryption(s protocol.SecurityType) bool { - return s == protocol.SecurityType_NONE || s == protocol.SecurityType_LEGACY || s == protocol.SecurityType_UNKNOWN -} - // Process implements proxy.Inbound.Process(). func (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { sessionPolicy := h.policyManager.ForLevel(0) @@ -241,7 +225,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s reader := &buf.BufferedReader{Reader: buf.NewReader(connection)} svrSession := encoding.NewServerSession(h.clients, h.sessionHistory) - svrSession.SetAEADForced(aeadForced) request, err := svrSession.DecodeRequestHeader(reader, isDrain) if err != nil { if errors.Cause(err) != io.EOF { @@ -256,17 +239,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s return err } - if h.secure && isInsecureEncryption(request.Security) { - log.Record(&log.AccessMessage{ - From: connection.RemoteAddr(), - To: "", - Status: log.AccessRejected, - Reason: "Insecure encryption", - Email: request.User.Email, - }) - return newError("client is using insecure encryption: ", request.Security) - } - if request.Command != protocol.RequestCommandMux { ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ From: connection.RemoteAddr(), @@ -284,9 +256,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s } inbound := session.InboundFromContext(ctx) - if inbound == nil { - panic("no inbound metadata") - } + inbound.Name = "vmess" + inbound.SetCanSpliceCopy(3) inbound.User = request.User sessionPolicy = h.policyManager.ForLevel(request.User.Level) @@ -360,7 +331,6 @@ func (h *Handler) generateCommand(ctx context.Context, request *protocol.Request return &protocol.CommandSwitchAccount{ Port: port, ID: account.ID.UUID(), - AlterIds: uint16(len(account.AlterIDs)), Level: user.Level, ValidMin: byte(availableMin), } @@ -375,18 +345,4 @@ func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { return New(ctx, config.(*Config)) })) - - defaultFlagValue := "NOT_DEFINED_AT_ALL" - - if time.Now().Year() >= 2022 { - defaultFlagValue = "true_by_default_2022" - } - - isAeadForced := platform.NewEnvFlag("xray.vmess.aead.forced").GetValue(func() string { return defaultFlagValue }) - aeadForced = (isAeadForced == "true") - - if isAeadForced == "true_by_default_2022" { - aeadForced = true - aeadForced2022 = true - } } diff --git a/proxy/vmess/outbound/command.go b/proxy/vmess/outbound/command.go index 00c6fac5b66f..2d4747dc288b 100644 --- a/proxy/vmess/outbound/command.go +++ b/proxy/vmess/outbound/command.go @@ -11,10 +11,9 @@ import ( func (h *Handler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) { rawAccount := &vmess.Account{ - Id: cmd.ID.String(), - AlterId: uint32(cmd.AlterIds), + Id: cmd.ID.String(), SecuritySettings: &protocol.SecurityConfig{ - Type: protocol.SecurityType_LEGACY, + Type: protocol.SecurityType_AUTO, }, } diff --git a/proxy/vmess/outbound/config.pb.go b/proxy/vmess/outbound/config.pb.go index 6e2d00a792e1..b68cbffa42db 100644 --- a/proxy/vmess/outbound/config.pb.go +++ b/proxy/vmess/outbound/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/vmess/outbound/config.proto package outbound diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index e7c6466efdef..5e228d681eb8 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -60,9 +60,18 @@ func New(ctx context.Context, config *Config) (*Handler, error) { // Process implements proxy.Outbound.Process(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified").AtError() + } + outbound.Name = "vmess" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(3) + } + var rec *protocol.ServerSpec var conn stat.Connection - err := retry.ExponentialBackoff(5, 200).On(func() error { rec = h.serverPicker.PickServer() rawConn, err := dialer.Dial(ctx, rec.Destination()) @@ -78,11 +87,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } defer conn.Close() - outbound := session.OutboundFromContext(ctx) - if outbound == nil || !outbound.Target.IsValid() { - return newError("target not specified").AtError() - } - target := outbound.Target newError("tunneling request to ", target, " via ", rec.Destination().NetAddr()).WriteToLog(session.ExportIDToError(ctx)) @@ -128,21 +132,27 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte input := link.Reader output := link.Writer - isAEAD := false - if !aeadDisabled && len(account.AlterIDs) == 0 { - isAEAD = true - } - hashkdf := hmac.New(sha256.New, []byte("VMessBF")) hashkdf.Write(account.ID.Bytes()) behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO)) - session := encoding.NewClientSession(ctx, isAEAD, protocol.DefaultIDHash, int64(behaviorSeed)) + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } + + session := encoding.NewClientSession(ctx, int64(behaviorSeed)) sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 { request.Command = protocol.RequestCommandMux @@ -164,7 +174,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } bodyWriter2 := bodyWriter if request.Command == protocol.RequestCommandMux && request.Port == 666 { - bodyWriter = xudp.NewPacketWriter(bodyWriter, target) + bodyWriter = xudp.NewPacketWriter(bodyWriter, target, xudp.GetGlobalID(ctx)) } if err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout { return newError("failed to write first payload").Base(err) @@ -208,6 +218,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseDone, task.Close(output)) if err := task.Run(ctx, requestDone, responseDonePost); err != nil { return newError("connection ends").Base(err) @@ -218,7 +232,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte var ( enablePadding = false - aeadDisabled = false ) func shouldEnablePadding(s protocol.SecurityType) bool { @@ -236,9 +249,4 @@ func init() { if paddingValue != defaultFlagValue { enablePadding = true } - - isAeadDisabled := platform.NewEnvFlag("xray.vmess.aead.disabled").GetValue(func() string { return defaultFlagValue }) - if isAeadDisabled == "true" { - aeadDisabled = true - } } diff --git a/proxy/vmess/validator.go b/proxy/vmess/validator.go index 30c44af7b279..bc844061b94a 100644 --- a/proxy/vmess/validator.go +++ b/proxy/vmess/validator.go @@ -6,146 +6,39 @@ import ( "hash/crc64" "strings" "sync" - "sync/atomic" - "time" - "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/serial" - "github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/proxy/vmess/aead" ) -const ( - updateInterval = 10 * time.Second - cacheDurationSec = 120 -) - -type user struct { - user protocol.MemoryUser - lastSec protocol.Timestamp -} - // TimedUserValidator is a user Validator based on time. type TimedUserValidator struct { sync.RWMutex - users []*user - userHash map[[16]byte]indexTimePair - hasher protocol.IDHash - baseTime protocol.Timestamp - task *task.Periodic + users []*protocol.MemoryUser behaviorSeed uint64 behaviorFused bool aeadDecoderHolder *aead.AuthIDDecoderHolder - - legacyWarnShown bool -} - -type indexTimePair struct { - user *user - timeInc uint32 - - taintedFuse *uint32 } // NewTimedUserValidator creates a new TimedUserValidator. -func NewTimedUserValidator(hasher protocol.IDHash) *TimedUserValidator { +func NewTimedUserValidator() *TimedUserValidator { tuv := &TimedUserValidator{ - users: make([]*user, 0, 16), - userHash: make(map[[16]byte]indexTimePair, 1024), - hasher: hasher, - baseTime: protocol.Timestamp(time.Now().Unix() - cacheDurationSec*2), + users: make([]*protocol.MemoryUser, 0, 16), aeadDecoderHolder: aead.NewAuthIDDecoderHolder(), } - tuv.task = &task.Periodic{ - Interval: updateInterval, - Execute: func() error { - tuv.updateUserHash() - return nil - }, - } - common.Must(tuv.task.Start()) return tuv } -// visible for testing -func (v *TimedUserValidator) GetBaseTime() protocol.Timestamp { - return v.baseTime -} - -func (v *TimedUserValidator) generateNewHashes(nowSec protocol.Timestamp, user *user) { - var hashValue [16]byte - genEndSec := nowSec + cacheDurationSec - genHashForID := func(id *protocol.ID) { - idHash := v.hasher(id.Bytes()) - genBeginSec := user.lastSec - if genBeginSec < nowSec-cacheDurationSec { - genBeginSec = nowSec - cacheDurationSec - } - for ts := genBeginSec; ts <= genEndSec; ts++ { - common.Must2(serial.WriteUint64(idHash, uint64(ts))) - idHash.Sum(hashValue[:0]) - idHash.Reset() - - v.userHash[hashValue] = indexTimePair{ - user: user, - timeInc: uint32(ts - v.baseTime), - taintedFuse: new(uint32), - } - } - } - - account := user.user.Account.(*MemoryAccount) - - genHashForID(account.ID) - for _, id := range account.AlterIDs { - genHashForID(id) - } - user.lastSec = genEndSec -} - -func (v *TimedUserValidator) removeExpiredHashes(expire uint32) { - for key, pair := range v.userHash { - if pair.timeInc < expire { - delete(v.userHash, key) - } - } -} - -func (v *TimedUserValidator) updateUserHash() { - now := time.Now() - nowSec := protocol.Timestamp(now.Unix()) - - v.Lock() - defer v.Unlock() - - for _, user := range v.users { - v.generateNewHashes(nowSec, user) - } - - expire := protocol.Timestamp(now.Unix() - cacheDurationSec) - if expire > v.baseTime { - v.removeExpiredHashes(uint32(expire - v.baseTime)) - } -} - func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error { v.Lock() defer v.Unlock() - nowSec := time.Now().Unix() - - uu := &user{ - user: *u, - lastSec: protocol.Timestamp(nowSec - cacheDurationSec), - } - v.users = append(v.users, uu) - v.generateNewHashes(protocol.Timestamp(nowSec), uu) + v.users = append(v.users, u) - account := uu.user.Account.(*MemoryAccount) + account := u.Account.(*MemoryAccount) if !v.behaviorFused { hashkdf := hmac.New(sha256.New, []byte("VMESSBSKDF")) hashkdf.Write(account.ID.Bytes()) @@ -159,25 +52,6 @@ func (v *TimedUserValidator) Add(u *protocol.MemoryUser) error { return nil } -func (v *TimedUserValidator) Get(userHash []byte) (*protocol.MemoryUser, protocol.Timestamp, bool, error) { - v.RLock() - defer v.RUnlock() - - v.behaviorFused = true - - var fixedSizeHash [16]byte - copy(fixedSizeHash[:], userHash) - pair, found := v.userHash[fixedSizeHash] - if found { - user := pair.user.user - if atomic.LoadUint32(pair.taintedFuse) == 0 { - return &user, protocol.Timestamp(pair.timeInc) + v.baseTime, true, nil - } - return nil, 0, false, ErrTainted - } - return nil, 0, false, ErrNotFound -} - func (v *TimedUserValidator) GetAEAD(userHash []byte) (*protocol.MemoryUser, bool, error) { v.RLock() defer v.RUnlock() @@ -199,10 +73,10 @@ func (v *TimedUserValidator) Remove(email string) bool { email = strings.ToLower(email) idx := -1 for i, u := range v.users { - if strings.EqualFold(u.user.Email, email) { + if strings.EqualFold(u.Email, email) { idx = i var cmdkeyfl [16]byte - copy(cmdkeyfl[:], u.user.Account.(*MemoryAccount).ID.CmdKey()) + copy(cmdkeyfl[:], u.Account.(*MemoryAccount).ID.CmdKey()) v.aeadDecoderHolder.RemoveUser(cmdkeyfl) break } @@ -219,11 +93,6 @@ func (v *TimedUserValidator) Remove(email string) bool { return true } -// Close implements common.Closable. -func (v *TimedUserValidator) Close() error { - return v.task.Close() -} - func (v *TimedUserValidator) GetBehaviorSeed() uint64 { v.Lock() defer v.Unlock() @@ -235,34 +104,6 @@ func (v *TimedUserValidator) GetBehaviorSeed() uint64 { return v.behaviorSeed } -func (v *TimedUserValidator) BurnTaintFuse(userHash []byte) error { - v.RLock() - defer v.RUnlock() - - var userHashFL [16]byte - copy(userHashFL[:], userHash) - - pair, found := v.userHash[userHashFL] - if found { - if atomic.CompareAndSwapUint32(pair.taintedFuse, 0, 1) { - return nil - } - return ErrTainted - } - return ErrNotFound -} - -/* ShouldShowLegacyWarn will return whether a Legacy Warning should be shown -Not guaranteed to only return true once for every inbound, but it is okay. -*/ -func (v *TimedUserValidator) ShouldShowLegacyWarn() bool { - if v.legacyWarnShown { - return false - } - v.legacyWarnShown = true - return true -} - var ErrNotFound = newError("Not Found") var ErrTainted = newError("ErrTainted") diff --git a/proxy/vmess/validator_test.go b/proxy/vmess/validator_test.go index ee170655ee63..83313cbc4c4e 100644 --- a/proxy/vmess/validator_test.go +++ b/proxy/vmess/validator_test.go @@ -5,7 +5,6 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/protocol" - "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/uuid" . "github.com/xtls/xray-core/proxy/vmess" ) @@ -16,81 +15,9 @@ func toAccount(a *Account) protocol.Account { return account } -func TestUserValidator(t *testing.T) { - hasher := protocol.DefaultIDHash - v := NewTimedUserValidator(hasher) - defer common.Close(v) - - id := uuid.New() - user := &protocol.MemoryUser{ - Email: "test", - Account: toAccount(&Account{ - Id: id.String(), - }), - } - common.Must(v.Add(user)) - - { - testSmallLag := func(lag int64) { - ts := int64(v.GetBaseTime()) + lag + 240 - idHash := hasher(id.Bytes()) - common.Must2(serial.WriteUint64(idHash, uint64(ts))) - userHash := idHash.Sum(nil) - - euser, ets, found, _ := v.Get(userHash) - if !found { - t.Fatal("user not found") - } - if euser.Email != user.Email { - t.Error("unexpected user email: ", euser.Email, " want ", user.Email) - } - if int64(ets) != ts { - t.Error("unexpected timestamp: ", ets, " want ", ts) - } - } - - testSmallLag(0) - testSmallLag(40) - testSmallLag(-40) - testSmallLag(80) - testSmallLag(-80) - testSmallLag(120) - testSmallLag(-120) - } - - { - testBigLag := func(lag int64) { - ts := int64(v.GetBaseTime()) + lag + 240 - idHash := hasher(id.Bytes()) - common.Must2(serial.WriteUint64(idHash, uint64(ts))) - userHash := idHash.Sum(nil) - - euser, _, found, _ := v.Get(userHash) - if found || euser != nil { - t.Error("unexpected user") - } - } - - testBigLag(121) - testBigLag(-121) - testBigLag(310) - testBigLag(-310) - testBigLag(500) - testBigLag(-500) - } - - if v := v.Remove(user.Email); !v { - t.Error("unable to remove user") - } - if v := v.Remove(user.Email); v { - t.Error("remove user twice") - } -} - func BenchmarkUserValidator(b *testing.B) { for i := 0; i < b.N; i++ { - hasher := protocol.DefaultIDHash - v := NewTimedUserValidator(hasher) + v := NewTimedUserValidator() for j := 0; j < 1500; j++ { id := uuid.New() diff --git a/proxy/vmess/vmessCtxInterface.go b/proxy/vmess/vmessCtxInterface.go deleted file mode 100644 index 5d26f9e5d27c..000000000000 --- a/proxy/vmess/vmessCtxInterface.go +++ /dev/null @@ -1,4 +0,0 @@ -package vmess - -// example -const AlterID = "VMessCtxInterface_AlterID" diff --git a/proxy/wireguard/bind.go b/proxy/wireguard/bind.go index 1136f5ed8f6d..527f0e74e52b 100644 --- a/proxy/wireguard/bind.go +++ b/proxy/wireguard/bind.go @@ -31,6 +31,7 @@ type netBindClient struct { dialer internet.Dialer dns dns.Client dnsOption dns.IPOption + reserved []byte readQueue chan *netReadInfo } @@ -128,6 +129,13 @@ func (bind *netBindClient) connectTo(endpoint *netEndpoint) error { return } i, err := c.Read(v.buff) + + if i > 3 { + v.buff[1] = 0 + v.buff[2] = 0 + v.buff[3] = 0 + } + v.bytes = i v.endpoint = endpoint v.err = err @@ -157,6 +165,10 @@ func (bind *netBindClient) Send(buff []byte, endpoint conn.Endpoint) error { } } + if len(buff) > 3 && len(bind.reserved) == 3 { + copy(buff[1:], bind.reserved) + } + _, err = nend.conn.Write(buff) return err diff --git a/proxy/wireguard/config.pb.go b/proxy/wireguard/config.pb.go index 149fa9589c35..442d78f00927 100644 --- a/proxy/wireguard/config.pb.go +++ b/proxy/wireguard/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.9 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: proxy/wireguard/config.proto package wireguard @@ -109,6 +109,7 @@ type DeviceConfig struct { Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` NumWorkers int32 `protobuf:"varint,5,opt,name=num_workers,json=numWorkers,proto3" json:"num_workers,omitempty"` + Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"` } func (x *DeviceConfig) Reset() { @@ -178,6 +179,13 @@ func (x *DeviceConfig) GetNumWorkers() int32 { return 0 } +func (x *DeviceConfig) GetReserved() []byte { + if x != nil { + return x.Reserved + } + return nil +} + var File_proxy_wireguard_config_proto protoreflect.FileDescriptor var file_proxy_wireguard_config_proto_rawDesc = []byte{ @@ -195,7 +203,7 @@ var file_proxy_wireguard_config_proto_rawDesc = []byte{ 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, - 0x64, 0x49, 0x70, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, + 0x64, 0x49, 0x70, 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, @@ -206,14 +214,15 @@ var file_proxy_wireguard_config_proto_rawDesc = []byte{ 0x67, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x0a, 0x6e, 0x75, 0x6d, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x42, 0x5e, 0x0a, 0x18, 0x63, - 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x77, 0x69, - 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, - 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x77, 0x69, 0x72, 0x65, 0x67, - 0x75, 0x61, 0x72, 0x64, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, - 0x79, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x47, 0x75, 0x61, 0x72, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x0a, 0x6e, 0x75, 0x6d, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, + 0x61, 0x72, 0x64, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, + 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x57, 0x69, + 0x72, 0x65, 0x47, 0x75, 0x61, 0x72, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proxy/wireguard/config.proto b/proxy/wireguard/config.proto index dde3b41b05cb..810a1126c484 100644 --- a/proxy/wireguard/config.proto +++ b/proxy/wireguard/config.proto @@ -20,4 +20,5 @@ message DeviceConfig { repeated PeerConfig peers = 3; int32 mtu = 4; int32 num_workers = 5; + bytes reserved = 6; } \ No newline at end of file diff --git a/proxy/wireguard/tun.go b/proxy/wireguard/tun.go index 4d1cb7f6894f..ed6e434fbbd8 100644 --- a/proxy/wireguard/tun.go +++ b/proxy/wireguard/tun.go @@ -14,7 +14,7 @@ import ( "github.com/sagernet/wireguard-go/tun" "github.com/xtls/xray-core/features/dns" - "gvisor.dev/gvisor/pkg/bufferv2" + "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" @@ -30,7 +30,7 @@ type netTun struct { ep *channel.Endpoint stack *stack.Stack events chan tun.Event - incomingPacket chan *bufferv2.View + incomingPacket chan *buffer.View mtu int dnsClient dns.Client hasV4, hasV6 bool @@ -48,7 +48,7 @@ func CreateNetTUN(localAddresses []netip.Addr, dnsClient dns.Client, mtu int) (t ep: channel.New(1024, uint32(mtu), ""), stack: stack.New(opts), events: make(chan tun.Event, 10), - incomingPacket: make(chan *bufferv2.View), + incomingPacket: make(chan *buffer.View), dnsClient: dnsClient, mtu: mtu, } @@ -66,7 +66,7 @@ func CreateNetTUN(localAddresses []netip.Addr, dnsClient dns.Client, mtu int) (t } protoAddr := tcpip.ProtocolAddress{ Protocol: protoNumber, - AddressWithPrefix: tcpip.Address(ip.AsSlice()).WithPrefix(), + AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(), } tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{}) if tcpipErr != nil { @@ -116,7 +116,7 @@ func (tun *netTun) Write(buf []byte, offset int) (int, error) { return 0, nil } - pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: bufferv2.MakeWithData(packet)}) + pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)}) switch packet[0] >> 4 { case 4: tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb) @@ -172,7 +172,7 @@ func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.Networ } return tcpip.FullAddress{ NIC: 1, - Addr: tcpip.Address(endpoint.Addr().AsSlice()), + Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()), Port: endpoint.Port(), }, protoNumber } diff --git a/proxy/wireguard/wireguard.go b/proxy/wireguard/wireguard.go index 51cee8767d68..899dcac5415c 100644 --- a/proxy/wireguard/wireguard.go +++ b/proxy/wireguard/wireguard.go @@ -75,6 +75,16 @@ func New(ctx context.Context, conf *DeviceConfig) (*Handler, error) { // Process implements OutboundHandler.Dispatch(). func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { + outbound := session.OutboundFromContext(ctx) + if outbound == nil || !outbound.Target.IsValid() { + return newError("target not specified") + } + outbound.Name = "wireguard" + inbound := session.InboundFromContext(ctx) + if inbound != nil { + inbound.SetCanSpliceCopy(3) + } + if h.bind == nil || h.bind.dialer != dialer || h.net == nil { log.Record(&log.GeneralMessage{ Severity: log.Severity_Info, @@ -82,9 +92,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte }) // bind := conn.NewStdNetBind() // TODO: conn.Bind wrapper for dialer bind := &netBindClient{ - dialer: dialer, - workers: int(h.conf.NumWorkers), - dns: h.dns, + dialer: dialer, + workers: int(h.conf.NumWorkers), + dns: h.dns, + reserved: h.conf.Reserved, } net, err := h.makeVirtualTun(bind) @@ -100,10 +111,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte h.bind = bind } - outbound := session.OutboundFromContext(ctx) - if outbound == nil || !outbound.Target.IsValid() { - return newError("target not specified") - } // Destination of the inner request. destination := outbound.Target command := protocol.RequestCommandTCP @@ -126,10 +133,21 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte addr = net.IPAddress(ips[0]) } + var newCtx context.Context + var newCancel context.CancelFunc + if session.TimeoutOnlyFromContext(ctx) { + newCtx, newCancel = context.WithCancel(context.Background()) + } + p := h.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, p.Timeouts.ConnectionIdle) addrPort := netip.AddrPortFrom(toNetIpAddr(addr), destination.Port.Value()) var requestFunc func() error @@ -140,6 +158,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte if err != nil { return newError("failed to create TCP connection").Base(err) } + defer conn.Close() requestFunc = func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) @@ -154,6 +173,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte if err != nil { return newError("failed to create UDP connection").Base(err) } + defer conn.Close() requestFunc = func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) @@ -165,8 +185,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { + common.Interrupt(link.Reader) + common.Interrupt(link.Writer) return newError("connection ends").Base(err) } diff --git a/testing/scenarios/common.go b/testing/scenarios/common.go index f011a64f8ab9..8d629c59bab5 100644 --- a/testing/scenarios/common.go +++ b/testing/scenarios/common.go @@ -14,7 +14,6 @@ import ( "testing" "time" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/app/dispatcher" "github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/common" @@ -25,6 +24,7 @@ import ( "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/units" core "github.com/xtls/xray-core/core" + "google.golang.org/protobuf/proto" ) func xor(b []byte) []byte { diff --git a/testing/scenarios/http_test.go b/testing/scenarios/http_test.go index d6a765bb0fb5..b9b112ffa8d3 100644 --- a/testing/scenarios/http_test.go +++ b/testing/scenarios/http_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/url" + "strings" "testing" "time" @@ -128,9 +129,8 @@ func TestHttpError(t *testing.T) { } resp, err := client.Get("http://127.0.0.1:" + dest.Port.String()) - common.Must(err) - if resp.StatusCode != 503 { - t.Error("status: ", resp.StatusCode) + if resp != nil && resp.StatusCode != 503 || err != nil && !strings.Contains(err.Error(), "malformed HTTP status code") { + t.Error("should not receive http response", err) } } } diff --git a/testing/scenarios/tls_test.go b/testing/scenarios/tls_test.go index cac1d49933af..0b752ecf8c63 100644 --- a/testing/scenarios/tls_test.go +++ b/testing/scenarios/tls_test.go @@ -928,3 +928,316 @@ func TestSimpleTLSConnectionPinned(t *testing.T) { t.Fatal(err) } } + +func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + certificateDer := cert.MustGenerate(nil) + certificate := tls.ParseCertificate(certificateDer) + certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + certHash[1] += 1 + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{certificate}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + AllowInsecure: true, + PinnedPeerCertificateChainSha256: [][]byte{certHash}, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + if err := testTCPConn(clientPort, 1024, time.Second*20)(); err == nil { + t.Fatal(err) + } +} + +func TestUTLSConnectionPinned(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + certificateDer := cert.MustGenerate(nil) + certificate := tls.ParseCertificate(certificateDer) + certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{certificate}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Fingerprint: "random", + AllowInsecure: true, + PinnedPeerCertificateChainSha256: [][]byte{certHash}, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + if err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil { + t.Fatal(err) + } +} + +func TestUTLSConnectionPinnedWrongCert(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + certificateDer := cert.MustGenerate(nil) + certificate := tls.ParseCertificate(certificateDer) + certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate}) + certHash[1] += 1 + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{certificate}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Receiver: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vmess.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Fingerprint: "random", + AllowInsecure: true, + PinnedPeerCertificateChainSha256: [][]byte{certHash}, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + if err := testTCPConn(clientPort, 1024, time.Second*20)(); err == nil { + t.Fatal(err) + } +} diff --git a/testing/scenarios/vless_test.go b/testing/scenarios/vless_test.go new file mode 100644 index 000000000000..84d826436451 --- /dev/null +++ b/testing/scenarios/vless_test.go @@ -0,0 +1,518 @@ +package scenarios + +import ( + // "encoding/base64" + // "encoding/hex" + "testing" + "time" + + "github.com/xtls/xray-core/app/log" + "github.com/xtls/xray-core/app/proxyman" + "github.com/xtls/xray-core/common" + clog "github.com/xtls/xray-core/common/log" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/protocol" + "github.com/xtls/xray-core/common/protocol/tls/cert" + "github.com/xtls/xray-core/common/serial" + "github.com/xtls/xray-core/common/uuid" + core "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/proxy/dokodemo" + "github.com/xtls/xray-core/proxy/freedom" + "github.com/xtls/xray-core/proxy/vless" + "github.com/xtls/xray-core/proxy/vless/inbound" + "github.com/xtls/xray-core/proxy/vless/outbound" + "github.com/xtls/xray-core/testing/servers/tcp" + "github.com/xtls/xray-core/transport/internet" + // "github.com/xtls/xray-core/transport/internet/reality" + transtcp "github.com/xtls/xray-core/transport/internet/tcp" + "github.com/xtls/xray-core/transport/internet/tls" + "golang.org/x/sync/errgroup" +) + +func TestVless(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + Clients: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Vnext: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + var errg errgroup.Group + for i := 0; i < 10; i++ { + errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) + } + if err := errg.Wait(); err != nil { + t.Error(err) + } +} + +func TestVlessTls(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + Protocol: internet.TransportProtocol_TCP, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + Clients: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: userID.String(), + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Vnext: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: userID.String(), + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + Protocol: internet.TransportProtocol_TCP, + TransportSettings: []*internet.TransportConfig{ + { + Protocol: internet.TransportProtocol_TCP, + Settings: serial.ToTypedMessage(&transtcp.Config{}), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + AllowInsecure: true, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + var errg errgroup.Group + for i := 0; i < 10; i++ { + errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) + } + if err := errg.Wait(); err != nil { + t.Error(err) + } +} + +func TestVlessXtlsVision(t *testing.T) { + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + common.Must(err) + defer tcpServer.Close() + + userID := protocol.NewID(uuid.New()) + serverPort := tcp.PickPort() + serverConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + StreamSettings: &internet.StreamConfig{ + Protocol: internet.TransportProtocol_TCP, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))}, + }), + }, + }, + }), + ProxySettings: serial.ToTypedMessage(&inbound.Config{ + Clients: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: userID.String(), + Flow: vless.XRV, + }), + }, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := tcp.PickPort() + clientConfig := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&log.Config{ + ErrorLogLevel: clog.Severity_Debug, + ErrorLogType: log.LogType_Console, + }), + }, + Inbound: []*core.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ + Address: net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&outbound.Config{ + Vnext: []*protocol.ServerEndpoint{ + { + Address: net.NewIPOrDomain(net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&vless.Account{ + Id: userID.String(), + Flow: vless.XRV, + }), + }, + }, + }, + }, + }), + SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ + StreamSettings: &internet.StreamConfig{ + Protocol: internet.TransportProtocol_TCP, + TransportSettings: []*internet.TransportConfig{ + { + Protocol: internet.TransportProtocol_TCP, + Settings: serial.ToTypedMessage(&transtcp.Config{}), + }, + }, + SecurityType: serial.GetMessageType(&tls.Config{}), + SecuritySettings: []*serial.TypedMessage{ + serial.ToTypedMessage(&tls.Config{ + AllowInsecure: true, + }), + }, + }, + }), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig, clientConfig) + common.Must(err) + defer CloseAllServers(servers) + + var errg errgroup.Group + for i := 0; i < 10; i++ { + errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) + } + if err := errg.Wait(); err != nil { + t.Error(err) + } +} + +// func TestVlessXtlsVisionReality(t *testing.T) { +// tcpServer := tcp.Server{ +// MsgProcessor: xor, +// } +// dest, err := tcpServer.Start() +// common.Must(err) +// defer tcpServer.Close() + +// userID := protocol.NewID(uuid.New()) +// serverPort := tcp.PickPort() +// privateKey, _ := base64.RawURLEncoding.DecodeString("aGSYystUbf59_9_6LKRxD27rmSW_-2_nyd9YG_Gwbks") +// publicKey, _ := base64.RawURLEncoding.DecodeString("E59WjnvZcQMu7tR7_BgyhycuEdBS-CtKxfImRCdAvFM") +// shortIds := make([][]byte, 1) +// shortIds[0] = make([]byte, 8) +// hex.Decode(shortIds[0], []byte("0123456789abcdef")) +// serverConfig := &core.Config{ +// App: []*serial.TypedMessage{ +// serial.ToTypedMessage(&log.Config{ +// ErrorLogLevel: clog.Severity_Debug, +// ErrorLogType: log.LogType_Console, +// }), +// }, +// Inbound: []*core.InboundHandlerConfig{ +// { +// ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ +// PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}}, +// Listen: net.NewIPOrDomain(net.LocalHostIP), +// StreamSettings: &internet.StreamConfig{ +// Protocol: internet.TransportProtocol_TCP, +// SecurityType: serial.GetMessageType(&reality.Config{}), +// SecuritySettings: []*serial.TypedMessage{ +// serial.ToTypedMessage(&reality.Config{ +// Show: true, +// Dest: "www.google.com:443", // use google for now, may fail in some region +// ServerNames: []string{"www.google.com"}, +// PrivateKey: privateKey, +// ShortIds: shortIds, +// }), +// }, +// }, +// }), +// ProxySettings: serial.ToTypedMessage(&inbound.Config{ +// Clients: []*protocol.User{ +// { +// Account: serial.ToTypedMessage(&vless.Account{ +// Id: userID.String(), +// Flow: vless.XRV, +// }), +// }, +// }, +// }), +// }, +// }, +// Outbound: []*core.OutboundHandlerConfig{ +// { +// ProxySettings: serial.ToTypedMessage(&freedom.Config{}), +// }, +// }, +// } + +// clientPort := tcp.PickPort() +// clientConfig := &core.Config{ +// App: []*serial.TypedMessage{ +// serial.ToTypedMessage(&log.Config{ +// ErrorLogLevel: clog.Severity_Debug, +// ErrorLogType: log.LogType_Console, +// }), +// }, +// Inbound: []*core.InboundHandlerConfig{ +// { +// ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ +// PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}}, +// Listen: net.NewIPOrDomain(net.LocalHostIP), +// }), +// ProxySettings: serial.ToTypedMessage(&dokodemo.Config{ +// Address: net.NewIPOrDomain(dest.Address), +// Port: uint32(dest.Port), +// NetworkList: &net.NetworkList{ +// Network: []net.Network{net.Network_TCP}, +// }, +// }), +// }, +// }, +// Outbound: []*core.OutboundHandlerConfig{ +// { +// ProxySettings: serial.ToTypedMessage(&outbound.Config{ +// Vnext: []*protocol.ServerEndpoint{ +// { +// Address: net.NewIPOrDomain(net.LocalHostIP), +// Port: uint32(serverPort), +// User: []*protocol.User{ +// { +// Account: serial.ToTypedMessage(&vless.Account{ +// Id: userID.String(), +// Flow: vless.XRV, +// }), +// }, +// }, +// }, +// }, +// }), +// SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ +// StreamSettings: &internet.StreamConfig{ +// Protocol: internet.TransportProtocol_TCP, +// TransportSettings: []*internet.TransportConfig{ +// { +// Protocol: internet.TransportProtocol_TCP, +// Settings: serial.ToTypedMessage(&transtcp.Config{}), +// }, +// }, +// SecurityType: serial.GetMessageType(&reality.Config{}), +// SecuritySettings: []*serial.TypedMessage{ +// serial.ToTypedMessage(&reality.Config{ +// Show: true, +// Fingerprint: "chrome", +// ServerName: "www.google.com", +// PublicKey: publicKey, +// ShortId: shortIds[0], +// SpiderX: "/", +// }), +// }, +// }, +// }), +// }, +// }, +// } + +// servers, err := InitializeServerConfigs(serverConfig, clientConfig) +// common.Must(err) +// defer CloseAllServers(servers) + +// var errg errgroup.Group +// for i := 0; i < 1; i++ { +// errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) +// } +// if err := errg.Wait(); err != nil { +// t.Error(err) +// } +// } diff --git a/testing/scenarios/vmess_test.go b/testing/scenarios/vmess_test.go index 9f2b0abc6261..2239b13ce1dd 100644 --- a/testing/scenarios/vmess_test.go +++ b/testing/scenarios/vmess_test.go @@ -1174,10 +1174,10 @@ func TestVMessGCMMuxUDP(t *testing.T) { servers, err := InitializeServerConfigs(serverConfig, clientConfig) common.Must(err) - for range "abcd" { + for range "ab" { var errg errgroup.Group for i := 0; i < 16; i++ { - errg.Go(testTCPConn(clientPort, 10240, time.Second*20)) + errg.Go(testTCPConn(clientPort, 1024, time.Second*10)) errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*10)) } if err := errg.Wait(); err != nil { diff --git a/transport/global/config.pb.go b/transport/global/config.pb.go index 78e97b1e199a..1d763122b44c 100644 --- a/transport/global/config.pb.go +++ b/transport/global/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/global/config.proto package global @@ -24,7 +24,7 @@ const ( // Global transport settings. This affects all type of connections that go // through Xray. Deprecated. Use each settings in StreamConfig. // -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in transport/global/config.proto. type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/transport/internet/config.pb.go b/transport/internet/config.pb.go index 1cd5cfa34944..33519b63337e 100644 --- a/transport/internet/config.pb.go +++ b/transport/internet/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.31.0 +// protoc v4.24.1 // source: transport/internet/config.proto package internet @@ -191,7 +191,7 @@ type TransportConfig struct { // Type of network that this settings supports. // Deprecated. Use the string form below. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in transport/internet/config.proto. Protocol TransportProtocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=xray.transport.internet.TransportProtocol" json:"protocol,omitempty"` // Type of network that this settings supports. ProtocolName string `protobuf:"bytes,3,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"` @@ -231,7 +231,7 @@ func (*TransportConfig) Descriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{0} } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in transport/internet/config.proto. func (x *TransportConfig) GetProtocol() TransportProtocol { if x != nil { return x.Protocol @@ -260,7 +260,7 @@ type StreamConfig struct { // Effective network. Deprecated. Use the string form below. // - // Deprecated: Do not use. + // Deprecated: Marked as deprecated in transport/internet/config.proto. Protocol TransportProtocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=xray.transport.internet.TransportProtocol" json:"protocol,omitempty"` // Effective network. ProtocolName string `protobuf:"bytes,5,opt,name=protocol_name,json=protocolName,proto3" json:"protocol_name,omitempty"` @@ -304,7 +304,7 @@ func (*StreamConfig) Descriptor() ([]byte, []int) { return file_transport_internet_config_proto_rawDescGZIP(), []int{1} } -// Deprecated: Do not use. +// Deprecated: Marked as deprecated in transport/internet/config.proto. func (x *StreamConfig) GetProtocol() TransportProtocol { if x != nil { return x.Protocol @@ -425,6 +425,13 @@ type SocketConfig struct { TcpKeepAliveInterval int32 `protobuf:"varint,10,opt,name=tcp_keep_alive_interval,json=tcpKeepAliveInterval,proto3" json:"tcp_keep_alive_interval,omitempty"` TcpKeepAliveIdle int32 `protobuf:"varint,11,opt,name=tcp_keep_alive_idle,json=tcpKeepAliveIdle,proto3" json:"tcp_keep_alive_idle,omitempty"` TcpCongestion string `protobuf:"bytes,12,opt,name=tcp_congestion,json=tcpCongestion,proto3" json:"tcp_congestion,omitempty"` + Interface string `protobuf:"bytes,13,opt,name=interface,proto3" json:"interface,omitempty"` + V6Only bool `protobuf:"varint,14,opt,name=v6only,proto3" json:"v6only,omitempty"` + TcpWindowClamp int32 `protobuf:"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3" json:"tcp_window_clamp,omitempty"` + TcpUserTimeout int32 `protobuf:"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3" json:"tcp_user_timeout,omitempty"` + TcpMaxSeg int32 `protobuf:"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3" json:"tcp_max_seg,omitempty"` + TcpNoDelay bool `protobuf:"varint,18,opt,name=tcp_no_delay,json=tcpNoDelay,proto3" json:"tcp_no_delay,omitempty"` + TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"` } func (x *SocketConfig) Reset() { @@ -543,6 +550,55 @@ func (x *SocketConfig) GetTcpCongestion() string { return "" } +func (x *SocketConfig) GetInterface() string { + if x != nil { + return x.Interface + } + return "" +} + +func (x *SocketConfig) GetV6Only() bool { + if x != nil { + return x.V6Only + } + return false +} + +func (x *SocketConfig) GetTcpWindowClamp() int32 { + if x != nil { + return x.TcpWindowClamp + } + return 0 +} + +func (x *SocketConfig) GetTcpUserTimeout() int32 { + if x != nil { + return x.TcpUserTimeout + } + return 0 +} + +func (x *SocketConfig) GetTcpMaxSeg() int32 { + if x != nil { + return x.TcpMaxSeg + } + return 0 +} + +func (x *SocketConfig) GetTcpNoDelay() bool { + if x != nil { + return x.TcpNoDelay + } + return false +} + +func (x *SocketConfig) GetTcpMptcp() bool { + if x != nil { + return x.TcpMptcp + } + return false +} + var File_transport_internet_config_proto protoreflect.FileDescriptor var file_transport_internet_config_proto_rawDesc = []byte{ @@ -595,7 +651,7 @@ var file_transport_internet_config_proto_rawDesc = []byte{ 0x12, 0x30, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, - 0x78, 0x79, 0x22, 0xe8, 0x04, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x78, 0x79, 0x22, 0xd1, 0x06, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72, @@ -630,27 +686,42 @@ var file_transport_internet_config_proto_rawDesc = []byte{ 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, - 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2f, 0x0a, 0x0a, - 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, - 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, - 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x5a, 0x0a, - 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, - 0x44, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, - 0x0a, 0x09, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, - 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x05, 0x2a, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, - 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, - 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, - 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x42, 0x67, 0x0a, 0x1b, - 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, - 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, - 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, - 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x36, + 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36, 0x6f, 0x6e, + 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, + 0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, + 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28, 0x0a, 0x10, + 0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x63, 0x70, + 0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x63, 0x70, 0x5f, 0x6e, 0x6f, + 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x63, + 0x70, 0x4e, 0x6f, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, + 0x6d, 0x70, 0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, + 0x4d, 0x70, 0x74, 0x63, 0x70, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, + 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, + 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x5a, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x54, + 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, + 0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x65, 0x62, 0x53, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x04, + 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x10, 0x05, 0x2a, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, + 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, + 0x49, 0x50, 0x36, 0x10, 0x03, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, + 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/config.proto b/transport/internet/config.proto index 8b81302f662a..550d00ec8c00 100644 --- a/transport/internet/config.proto +++ b/transport/internet/config.proto @@ -98,4 +98,18 @@ message SocketConfig { int32 tcp_keep_alive_idle = 11; string tcp_congestion = 12; + + string interface = 13; + + bool v6only = 14; + + int32 tcp_window_clamp = 15; + + int32 tcp_user_timeout = 16; + + int32 tcp_max_seg = 17; + + bool tcp_no_delay = 18; + + bool tcp_mptcp = 19; } diff --git a/transport/internet/domainsocket/config.pb.go b/transport/internet/domainsocket/config.pb.go index 40cebd5965a4..6089cf34523b 100644 --- a/transport/internet/domainsocket/config.pb.go +++ b/transport/internet/domainsocket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/domainsocket/config.proto package domainsocket diff --git a/transport/internet/domainsocket/dial.go b/transport/internet/domainsocket/dial.go index 556c48e36170..a0032b36694e 100644 --- a/transport/internet/domainsocket/dial.go +++ b/transport/internet/domainsocket/dial.go @@ -9,9 +9,9 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) { @@ -28,8 +28,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { return tls.Client(conn, config.GetTLSConfig(tls.WithDestination(dest))), nil - } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { - return xtls.Client(conn, config.GetXTLSConfig(xtls.WithDestination(dest))), nil + } else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { + return reality.UClient(conn, config, ctx, dest) } return conn, nil diff --git a/transport/internet/domainsocket/listener.go b/transport/internet/domainsocket/listener.go index a8185d6b8ee9..323321e4deee 100644 --- a/transport/internet/domainsocket/listener.go +++ b/transport/internet/domainsocket/listener.go @@ -9,24 +9,24 @@ import ( "os" "strings" - goxtls "github.com/xtls/go" + goreality "github.com/xtls/reality" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" "golang.org/x/sys/unix" ) type Listener struct { - addr *net.UnixAddr - ln net.Listener - tlsConfig *gotls.Config - xtlsConfig *goxtls.Config - config *Config - addConn internet.ConnHandler - locker *fileLocker + addr *net.UnixAddr + ln net.Listener + tlsConfig *gotls.Config + realityConfig *goreality.Config + config *Config + addConn internet.ConnHandler + locker *fileLocker } func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) { @@ -61,8 +61,8 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { ln.tlsConfig = config.GetTLSConfig() } - if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { - ln.xtlsConfig = config.GetXTLSConfig() + if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { + ln.realityConfig = config.GetREALITYConfig() } go ln.run() @@ -91,14 +91,17 @@ func (ln *Listener) run() { newError("failed to accepted raw connections").Base(err).AtWarning().WriteToLog() continue } - - if ln.tlsConfig != nil { - conn = tls.Server(conn, ln.tlsConfig) - } else if ln.xtlsConfig != nil { - conn = xtls.Server(conn, ln.xtlsConfig) - } - - ln.addConn(stat.Connection(conn)) + go func() { + if ln.tlsConfig != nil { + conn = tls.Server(conn, ln.tlsConfig) + } else if ln.realityConfig != nil { + if conn, err = reality.Server(conn, ln.realityConfig); err != nil { + newError(err).AtInfo().WriteToLog() + return + } + } + ln.addConn(stat.Connection(conn)) + }() } } diff --git a/transport/internet/grpc/config.go b/transport/internet/grpc/config.go index d87722a4a4d3..aab1178e06e8 100644 --- a/transport/internet/grpc/config.go +++ b/transport/internet/grpc/config.go @@ -2,6 +2,7 @@ package grpc import ( "net/url" + "strings" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/transport/internet" @@ -15,6 +16,46 @@ func init() { })) } -func (c *Config) getNormalizedName() string { - return url.PathEscape(c.ServiceName) +func (c *Config) getServiceName() string { + // Normal old school config + if !strings.HasPrefix(c.ServiceName, "/") { + return url.PathEscape(c.ServiceName) + } + + // Otherwise new custom paths + lastIndex := strings.LastIndex(c.ServiceName, "/") + if lastIndex < 1 { + lastIndex = 1 + } + rawServiceName := c.ServiceName[1:lastIndex] // trim from first to last '/' + serviceNameParts := strings.Split(rawServiceName, "/") + for i := range serviceNameParts { + serviceNameParts[i] = url.PathEscape(serviceNameParts[i]) + } + return strings.Join(serviceNameParts, "/") +} + +func (c *Config) getTunStreamName() string { + // Normal old school config + if !strings.HasPrefix(c.ServiceName, "/") { + return "Tun" + } + // Otherwise new custom paths + endingPath := c.ServiceName[strings.LastIndex(c.ServiceName, "/")+1:] // from the last '/' to end of string + return url.PathEscape(strings.Split(endingPath, "|")[0]) +} + +func (c *Config) getTunMultiStreamName() string { + // Normal old school config + if !strings.HasPrefix(c.ServiceName, "/") { + return "TunMulti" + } + // Otherwise new custom paths + endingPath := c.ServiceName[strings.LastIndex(c.ServiceName, "/")+1:] // from the last '/' to end of string + streamNames := strings.Split(endingPath, "|") + if len(streamNames) == 1 { // client side. Service name is the full path to multi tun + return url.PathEscape(streamNames[0]) + } else { // server side. The second part is the path to multi tun + return url.PathEscape(streamNames[1]) + } } diff --git a/transport/internet/grpc/config.pb.go b/transport/internet/grpc/config.pb.go index f45f090a2ed0..91289606b63e 100644 --- a/transport/internet/grpc/config.pb.go +++ b/transport/internet/grpc/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/grpc/config.proto package grpc @@ -32,6 +32,7 @@ type Config struct { HealthCheckTimeout int32 `protobuf:"varint,5,opt,name=health_check_timeout,json=healthCheckTimeout,proto3" json:"health_check_timeout,omitempty"` PermitWithoutStream bool `protobuf:"varint,6,opt,name=permit_without_stream,json=permitWithoutStream,proto3" json:"permit_without_stream,omitempty"` InitialWindowsSize int32 `protobuf:"varint,7,opt,name=initial_windows_size,json=initialWindowsSize,proto3" json:"initial_windows_size,omitempty"` + UserAgent string `protobuf:"bytes,8,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` } func (x *Config) Reset() { @@ -115,6 +116,13 @@ func (x *Config) GetInitialWindowsSize() int32 { return 0 } +func (x *Config) GetUserAgent() string { + if x != nil { + return x.UserAgent + } + return "" +} + var File_transport_internet_grpc_config_proto protoreflect.FileDescriptor var file_transport_internet_grpc_config_proto_rawDesc = []byte{ @@ -122,7 +130,7 @@ var file_transport_internet_grpc_config_proto_rawDesc = []byte{ 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x25, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x99, 0x02, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x22, 0xb8, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, @@ -140,11 +148,13 @@ var file_transport_internet_grpc_config_proto_rawDesc = []byte{ 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, - 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, - 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, + 0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, + 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/grpc/config.proto b/transport/internet/grpc/config.proto index 7d256b2ce1df..56c507ae930f 100644 --- a/transport/internet/grpc/config.proto +++ b/transport/internet/grpc/config.proto @@ -11,4 +11,5 @@ message Config { int32 health_check_timeout = 5; bool permit_without_stream = 6; int32 initial_windows_size = 7; + string user_agent = 8; } diff --git a/transport/internet/grpc/config_test.go b/transport/internet/grpc/config_test.go new file mode 100644 index 000000000000..b159ffdff48c --- /dev/null +++ b/transport/internet/grpc/config_test.go @@ -0,0 +1,117 @@ +package grpc + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_GetServiceName(t *testing.T) { + tests := []struct { + TestName string + ServiceName string + Expected string + }{ + { + TestName: "simple no absolute path", + ServiceName: "hello", + Expected: "hello", + }, + { + TestName: "escape no absolute path", + ServiceName: "hello/world!", + Expected: "hello%2Fworld%21", + }, + { + TestName: "absolute path", + ServiceName: "/my/sample/path/a|b", + Expected: "my/sample/path", + }, + { + TestName: "escape absolute path", + ServiceName: "/hello /world!/a|b", + Expected: "hello%20/world%21", + }, + { + TestName: "path with only one '/'", + ServiceName: "/foo", + Expected: "", + }, + } + for _, test := range tests { + t.Run(test.TestName, func(t *testing.T) { + config := Config{ServiceName: test.ServiceName} + assert.Equal(t, test.Expected, config.getServiceName()) + }) + } +} + +func TestConfig_GetTunStreamName(t *testing.T) { + tests := []struct { + TestName string + ServiceName string + Expected string + }{ + { + TestName: "no absolute path", + ServiceName: "hello", + Expected: "Tun", + }, + { + TestName: "absolute path server", + ServiceName: "/my/sample/path/tun_service|multi_service", + Expected: "tun_service", + }, + { + TestName: "absolute path client", + ServiceName: "/my/sample/path/tun_service", + Expected: "tun_service", + }, + { + TestName: "escape absolute path client", + ServiceName: "/m y/sa !mple/pa\\th/tun\\_serv!ice", + Expected: "tun%5C_serv%21ice", + }, + } + for _, test := range tests { + t.Run(test.TestName, func(t *testing.T) { + config := Config{ServiceName: test.ServiceName} + assert.Equal(t, test.Expected, config.getTunStreamName()) + }) + } +} + +func TestConfig_GetTunMultiStreamName(t *testing.T) { + tests := []struct { + TestName string + ServiceName string + Expected string + }{ + { + TestName: "no absolute path", + ServiceName: "hello", + Expected: "TunMulti", + }, + { + TestName: "absolute path server", + ServiceName: "/my/sample/path/tun_service|multi_service", + Expected: "multi_service", + }, + { + TestName: "absolute path client", + ServiceName: "/my/sample/path/multi_service", + Expected: "multi_service", + }, + { + TestName: "escape absolute path client", + ServiceName: "/m y/sa !mple/pa\\th/mu%lti\\_serv!ice", + Expected: "mu%25lti%5C_serv%21ice", + }, + } + for _, test := range tests { + t.Run(test.TestName, func(t *testing.T) { + config := Config{ServiceName: test.ServiceName} + assert.Equal(t, test.Expected, config.getTunMultiStreamName()) + }) + } +} diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 9836d93a9bf4..16af63cd8fd9 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -11,6 +11,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/grpc/encoding" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" "google.golang.org/grpc" @@ -53,15 +54,16 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne } client := encoding.NewGRPCServiceClient(conn) if grpcSettings.MultiMode { - newError("using gRPC multi mode").AtDebug().WriteToLog() - grpcService, err := client.(encoding.GRPCServiceClientX).TunMultiCustomName(ctx, grpcSettings.getNormalizedName()) + newError("using gRPC multi mode service name: `" + grpcSettings.getServiceName() + "` stream name: `" + grpcSettings.getTunMultiStreamName() + "`").AtDebug().WriteToLog() + grpcService, err := client.(encoding.GRPCServiceClientX).TunMultiCustomName(ctx, grpcSettings.getServiceName(), grpcSettings.getTunMultiStreamName()) if err != nil { return nil, newError("Cannot dial gRPC").Base(err) } return encoding.NewMultiHunkConn(grpcService, nil), nil } - grpcService, err := client.(encoding.GRPCServiceClientX).TunCustomName(ctx, grpcSettings.getNormalizedName()) + newError("using gRPC tun mode service name: `" + grpcSettings.getServiceName() + "` stream name: `" + grpcSettings.getTunStreamName() + "`").AtDebug().WriteToLog() + grpcService, err := client.(encoding.GRPCServiceClientX).TunCustomName(ctx, grpcSettings.getServiceName(), grpcSettings.getTunStreamName()) if err != nil { return nil, newError("Cannot dial gRPC").Base(err) } @@ -77,6 +79,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in globalDialerMap = make(map[dialerConf]*grpc.ClientConn) } tlsConfig := tls.ConfigFromStreamSettings(streamSettings) + realityConfig := reality.ConfigFromStreamSettings(streamSettings) sockopt := streamSettings.SocketSettings grpcSettings := streamSettings.ProtocolSettings.(*Config) @@ -95,16 +98,13 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in MinConnectTimeout: 5 * time.Second, }), grpc.WithContextDialer(func(gctx context.Context, s string) (gonet.Conn, error) { - gctx = session.ContextWithID(gctx, session.IDFromContext(ctx)) - gctx = session.ContextWithOutbound(gctx, session.OutboundFromContext(ctx)) - - rawHost, rawPort, err := net.SplitHostPort(s) select { case <-gctx.Done(): return nil, gctx.Err() default: } + rawHost, rawPort, err := net.SplitHostPort(s) if err != nil { return nil, err } @@ -116,13 +116,22 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in return nil, err } address := net.ParseAddress(rawHost) - return internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) + + gctx = session.ContextWithID(gctx, session.IDFromContext(ctx)) + gctx = session.ContextWithOutbound(gctx, session.OutboundFromContext(ctx)) + gctx = session.ContextWithTimeoutOnly(gctx, true) + + c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) + if err == nil && realityConfig != nil { + return reality.UClient(c, realityConfig, gctx, dest) + } + return c, err }), } if tlsConfig != nil { var transportCredential credentials.TransportCredentials - if fingerprint, exists := tls.Fingerprints[tlsConfig.Fingerprint]; exists { + if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { transportCredential = tls.NewGrpcUtls(tlsConfig.GetTLSConfig(), fingerprint) } else { // Fallback to normal gRPC TLS transportCredential = credentials.NewTLS(tlsConfig.GetTLSConfig()) @@ -144,6 +153,10 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in dialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize)) } + if grpcSettings.UserAgent != "" { + dialOptions = append(dialOptions, grpc.WithUserAgent(grpcSettings.UserAgent)) + } + var grpcDestHost string if dest.Address.Family().IsDomain() { grpcDestHost = dest.Address.Domain() diff --git a/transport/internet/grpc/encoding/customSeviceName.go b/transport/internet/grpc/encoding/customSeviceName.go index aa098835af70..dd99075557de 100644 --- a/transport/internet/grpc/encoding/customSeviceName.go +++ b/transport/internet/grpc/encoding/customSeviceName.go @@ -6,20 +6,20 @@ import ( "google.golang.org/grpc" ) -func ServerDesc(name string) grpc.ServiceDesc { +func ServerDesc(name, tun, tunMulti string) grpc.ServiceDesc { return grpc.ServiceDesc{ ServiceName: name, HandlerType: (*GRPCServiceServer)(nil), Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ { - StreamName: "Tun", + StreamName: tun, Handler: _GRPCService_Tun_Handler, ServerStreams: true, ClientStreams: true, }, { - StreamName: "TunMulti", + StreamName: tunMulti, Handler: _GRPCService_TunMulti_Handler, ServerStreams: true, ClientStreams: true, @@ -29,8 +29,8 @@ func ServerDesc(name string) grpc.ServiceDesc { } } -func (c *gRPCServiceClient) TunCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunClient, error) { - stream, err := c.cc.NewStream(ctx, &ServerDesc(name).Streams[0], "/"+name+"/Tun", opts...) +func (c *gRPCServiceClient) TunCustomName(ctx context.Context, name, tun string, opts ...grpc.CallOption) (GRPCService_TunClient, error) { + stream, err := c.cc.NewStream(ctx, &ServerDesc(name, tun, "").Streams[0], "/"+name+"/"+tun, opts...) if err != nil { return nil, err } @@ -38,8 +38,8 @@ func (c *gRPCServiceClient) TunCustomName(ctx context.Context, name string, opts return x, nil } -func (c *gRPCServiceClient) TunMultiCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) { - stream, err := c.cc.NewStream(ctx, &ServerDesc(name).Streams[1], "/"+name+"/TunMulti", opts...) +func (c *gRPCServiceClient) TunMultiCustomName(ctx context.Context, name, tunMulti string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) { + stream, err := c.cc.NewStream(ctx, &ServerDesc(name, "", tunMulti).Streams[1], "/"+name+"/"+tunMulti, opts...) if err != nil { return nil, err } @@ -48,13 +48,13 @@ func (c *gRPCServiceClient) TunMultiCustomName(ctx context.Context, name string, } type GRPCServiceClientX interface { - TunCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunClient, error) - TunMultiCustomName(ctx context.Context, name string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) + TunCustomName(ctx context.Context, name, tun string, opts ...grpc.CallOption) (GRPCService_TunClient, error) + TunMultiCustomName(ctx context.Context, name, tunMulti string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) Tun(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunClient, error) TunMulti(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) } -func RegisterGRPCServiceServerX(s *grpc.Server, srv GRPCServiceServer, name string) { - desc := ServerDesc(name) +func RegisterGRPCServiceServerX(s *grpc.Server, srv GRPCServiceServer, name, tun, tunMulti string) { + desc := ServerDesc(name, tun, tunMulti) s.RegisterService(&desc, srv) } diff --git a/transport/internet/grpc/encoding/stream.pb.go b/transport/internet/grpc/encoding/stream.pb.go index 65ca0859aaf7..96cf41bd44fb 100644 --- a/transport/internet/grpc/encoding/stream.pb.go +++ b/transport/internet/grpc/encoding/stream.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/grpc/encoding/stream.proto package encoding diff --git a/transport/internet/grpc/encoding/stream_grpc.pb.go b/transport/internet/grpc/encoding/stream_grpc.pb.go index fa1d195090dc..e7cc8a5e0cb3 100644 --- a/transport/internet/grpc/encoding/stream_grpc.pb.go +++ b/transport/internet/grpc/encoding/stream_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.18.0 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.1 // source: transport/internet/grpc/encoding/stream.proto package encoding @@ -18,6 +18,11 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + GRPCService_Tun_FullMethodName = "/xray.transport.internet.grpc.encoding.GRPCService/Tun" + GRPCService_TunMulti_FullMethodName = "/xray.transport.internet.grpc.encoding.GRPCService/TunMulti" +) + // GRPCServiceClient is the client API for GRPCService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -35,7 +40,7 @@ func NewGRPCServiceClient(cc grpc.ClientConnInterface) GRPCServiceClient { } func (c *gRPCServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunClient, error) { - stream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[0], "/xray.transport.internet.grpc.encoding.GRPCService/Tun", opts...) + stream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[0], GRPCService_Tun_FullMethodName, opts...) if err != nil { return nil, err } @@ -66,7 +71,7 @@ func (x *gRPCServiceTunClient) Recv() (*Hunk, error) { } func (c *gRPCServiceClient) TunMulti(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) { - stream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[1], "/xray.transport.internet.grpc.encoding.GRPCService/TunMulti", opts...) + stream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[1], GRPCService_TunMulti_FullMethodName, opts...) if err != nil { return nil, err } diff --git a/transport/internet/grpc/hub.go b/transport/internet/grpc/hub.go index 4f5530700b89..e55f6f77c2e6 100644 --- a/transport/internet/grpc/hub.go +++ b/transport/internet/grpc/hub.go @@ -4,11 +4,13 @@ import ( "context" "time" + goreality "github.com/xtls/reality" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/grpc/encoding" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/tls" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -21,7 +23,6 @@ type Listener struct { handler internet.ConnHandler local net.Addr config *Config - locker *internet.FileLocker // for unix domain socket s *grpc.Server } @@ -108,10 +109,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i newError("failed to listen on ", address).Base(err).AtError().WriteToLog(session.ExportIDToError(ctx)) return } - locker := ctx.Value(address.Domain()) - if locker != nil { - listener.locker = locker.(*internet.FileLocker) - } } else { // tcp streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), @@ -123,8 +120,12 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i } } - encoding.RegisterGRPCServiceServerX(s, listener, grpcSettings.getNormalizedName()) + newError("gRPC listen for service name `" + grpcSettings.getServiceName() + "` tun `" + grpcSettings.getTunStreamName() + "` multi tun `" + grpcSettings.getTunMultiStreamName() + "`").AtDebug().WriteToLog() + encoding.RegisterGRPCServiceServerX(s, listener, grpcSettings.getServiceName(), grpcSettings.getTunStreamName(), grpcSettings.getTunMultiStreamName()) + if config := reality.ConfigFromStreamSettings(settings); config != nil { + streamListener = goreality.NewListener(streamListener, config.GetREALITYConfig()) + } if err = s.Serve(streamListener); err != nil { newError("Listener for gRPC ended").Base(err).WriteToLog() } diff --git a/transport/internet/headers/dns/config.pb.go b/transport/internet/headers/dns/config.pb.go new file mode 100644 index 000000000000..aeadae6a221b --- /dev/null +++ b/transport/internet/headers/dns/config.pb.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v4.23.1 +// source: transport/internet/headers/dns/config.proto + +package dns + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain string `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_headers_dns_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_headers_dns_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_headers_dns_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +var File_transport_internet_headers_dns_config_proto protoreflect.FileDescriptor + +var file_transport_internet_headers_dns_config_proto_rawDesc = []byte{ + 0x0a, 0x2b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x64, 0x6e, 0x73, + 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x23, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x64, + 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x8b, 0x01, 0x0a, 0x27, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x64, 0x6e, 0x73, + 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, + 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x23, 0x58, + 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x44, + 0x4e, 0x53, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_transport_internet_headers_dns_config_proto_rawDescOnce sync.Once + file_transport_internet_headers_dns_config_proto_rawDescData = file_transport_internet_headers_dns_config_proto_rawDesc +) + +func file_transport_internet_headers_dns_config_proto_rawDescGZIP() []byte { + file_transport_internet_headers_dns_config_proto_rawDescOnce.Do(func() { + file_transport_internet_headers_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_headers_dns_config_proto_rawDescData) + }) + return file_transport_internet_headers_dns_config_proto_rawDescData +} + +var file_transport_internet_headers_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_headers_dns_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: xray.transport.internet.headers.dns.Config +} +var file_transport_internet_headers_dns_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_headers_dns_config_proto_init() } +func file_transport_internet_headers_dns_config_proto_init() { + if File_transport_internet_headers_dns_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_headers_dns_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_transport_internet_headers_dns_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_headers_dns_config_proto_goTypes, + DependencyIndexes: file_transport_internet_headers_dns_config_proto_depIdxs, + MessageInfos: file_transport_internet_headers_dns_config_proto_msgTypes, + }.Build() + File_transport_internet_headers_dns_config_proto = out.File + file_transport_internet_headers_dns_config_proto_rawDesc = nil + file_transport_internet_headers_dns_config_proto_goTypes = nil + file_transport_internet_headers_dns_config_proto_depIdxs = nil +} diff --git a/transport/internet/headers/dns/config.proto b/transport/internet/headers/dns/config.proto new file mode 100644 index 000000000000..a9a44ff463ab --- /dev/null +++ b/transport/internet/headers/dns/config.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package xray.transport.internet.headers.dns; +option csharp_namespace = "Xray.Transport.Internet.Headers.DNS"; +option go_package = "github.com/xtls/xray-core/transport/internet/headers/dns"; +option java_package = "com.xray.transport.internet.headers.dns"; +option java_multiple_files = true; + +message Config { + string domain = 1; +} + diff --git a/transport/internet/headers/dns/dns.go b/transport/internet/headers/dns/dns.go new file mode 100644 index 000000000000..b6345213983b --- /dev/null +++ b/transport/internet/headers/dns/dns.go @@ -0,0 +1,123 @@ +package dns + +import ( + "context" + "encoding/binary" + "errors" + + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/dice" +) + +type DNS struct { + header []byte +} + +func (d DNS) Size() int32 { + return int32(len(d.header)) +} + +// Serialize implements PacketHeader. +func (d DNS) Serialize(b []byte) { + copy(b, d.header) + binary.BigEndian.PutUint16(b[0:], dice.RollUint16()) // random transaction ID +} + +// NewDNS returns a new DNS instance based on given config. +func NewDNS(ctx context.Context, config interface{}) (interface{}, error) { + var header []byte + + header = binary.BigEndian.AppendUint16(header, 0x0000) // Transaction ID + header = binary.BigEndian.AppendUint16(header, 0x0100) // Flags: Standard query + header = binary.BigEndian.AppendUint16(header, 0x0001) // Questions + header = binary.BigEndian.AppendUint16(header, 0x0000) // Answer RRs + header = binary.BigEndian.AppendUint16(header, 0x0000) // Authority RRs + header = binary.BigEndian.AppendUint16(header, 0x0000) // Additional RRs + + buf := make([]byte, 0x100) + + off1, err := packDomainName(config.(*Config).Domain+".", buf) + if err != nil { + return nil, err + } + + header = append(header, buf[:off1]...) + + header = binary.BigEndian.AppendUint16(header, 0x0001) // Type: A + header = binary.BigEndian.AppendUint16(header, 0x0001) // Class: IN + + return DNS{ + header: header, + }, nil +} + +// copied from github.com/miekg/dns +func packDomainName(s string, msg []byte) (off1 int, err error) { + off := 0 + ls := len(s) + // Each dot ends a segment of the name. + // We trade each dot byte for a length byte. + // Except for escaped dots (\.), which are normal dots. + // There is also a trailing zero. + + // Emit sequence of counted strings, chopping at dots. + var ( + begin int + bs []byte + ) + for i := 0; i < ls; i++ { + var c byte + if bs == nil { + c = s[i] + } else { + c = bs[i] + } + + switch c { + case '\\': + if off+1 > len(msg) { + return len(msg), errors.New("buffer size too small") + } + + if bs == nil { + bs = []byte(s) + } + + copy(bs[i:ls-1], bs[i+1:]) + ls-- + case '.': + labelLen := i - begin + if labelLen >= 1<<6 { // top two bits of length must be clear + return len(msg), errors.New("bad rdata") + } + + // off can already (we're in a loop) be bigger than len(msg) + // this happens when a name isn't fully qualified + if off+1+labelLen > len(msg) { + return len(msg), errors.New("buffer size too small") + } + + // The following is covered by the length check above. + msg[off] = byte(labelLen) + + if bs == nil { + copy(msg[off+1:], s[begin:i]) + } else { + copy(msg[off+1:], bs[begin:i]) + } + off += 1 + labelLen + begin = i + 1 + default: + } + } + + if off < len(msg) { + msg[off] = 0 + } + + return off + 1, nil +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), NewDNS)) +} diff --git a/transport/internet/headers/http/config.pb.go b/transport/internet/headers/http/config.pb.go index 6ba64c5386e8..786bd92810fa 100644 --- a/transport/internet/headers/http/config.pb.go +++ b/transport/internet/headers/http/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/http/config.proto package http diff --git a/transport/internet/headers/noop/config.pb.go b/transport/internet/headers/noop/config.pb.go index 81f254a2e5da..cd8880a8443f 100644 --- a/transport/internet/headers/noop/config.pb.go +++ b/transport/internet/headers/noop/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/noop/config.proto package noop diff --git a/transport/internet/headers/srtp/config.pb.go b/transport/internet/headers/srtp/config.pb.go index b9992d37776b..553349e85380 100644 --- a/transport/internet/headers/srtp/config.pb.go +++ b/transport/internet/headers/srtp/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/srtp/config.proto package srtp diff --git a/transport/internet/headers/tls/config.pb.go b/transport/internet/headers/tls/config.pb.go index e24daf8e916a..8d940553a494 100644 --- a/transport/internet/headers/tls/config.pb.go +++ b/transport/internet/headers/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/tls/config.proto package tls diff --git a/transport/internet/headers/utp/config.pb.go b/transport/internet/headers/utp/config.pb.go index a0ce40125c1b..9dff1aed9f53 100644 --- a/transport/internet/headers/utp/config.pb.go +++ b/transport/internet/headers/utp/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/utp/config.proto package utp diff --git a/transport/internet/headers/wechat/config.pb.go b/transport/internet/headers/wechat/config.pb.go index 928678ff0f12..02c4f50d1c50 100644 --- a/transport/internet/headers/wechat/config.pb.go +++ b/transport/internet/headers/wechat/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/wechat/config.proto package wechat diff --git a/transport/internet/headers/wireguard/config.pb.go b/transport/internet/headers/wireguard/config.pb.go index d10072a4f448..68c72d96560b 100644 --- a/transport/internet/headers/wireguard/config.pb.go +++ b/transport/internet/headers/wireguard/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/headers/wireguard/config.proto package wireguard diff --git a/transport/internet/http/config.pb.go b/transport/internet/http/config.pb.go index cef7700253ba..6abb97ddfab5 100644 --- a/transport/internet/http/config.pb.go +++ b/transport/internet/http/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/http/config.proto package http diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go index 5c4cbdfd95e9..1ea3a738c70a 100644 --- a/transport/internet/http/dialer.go +++ b/transport/internet/http/dialer.go @@ -3,6 +3,7 @@ package http import ( "context" gotls "crypto/tls" + "io" "net/http" "net/url" "sync" @@ -14,6 +15,7 @@ import ( "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/pipe" @@ -40,8 +42,9 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in httpSettings := streamSettings.ProtocolSettings.(*Config) tlsConfigs := tls.ConfigFromStreamSettings(streamSettings) - if tlsConfigs == nil { - return nil, newError("TLS must be enabled for http transport.").AtWarning() + realityConfigs := reality.ConfigFromStreamSettings(streamSettings) + if tlsConfigs == nil && realityConfigs == nil { + return nil, newError("TLS or REALITY must be enabled for http transport.").AtWarning() } sockopt := streamSettings.SocketSettings @@ -50,7 +53,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in } transport := &http2.Transport{ - DialTLS: func(network string, addr string, tlsConfig *gotls.Config) (net.Conn, error) { + DialTLSContext: func(hctx context.Context, string, addr string, tlsConfig *gotls.Config) (net.Conn, error) { rawHost, rawPort, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -64,18 +67,22 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in } address := net.ParseAddress(rawHost) - dctx := context.Background() - dctx = session.ContextWithID(dctx, session.IDFromContext(ctx)) - dctx = session.ContextWithOutbound(dctx, session.OutboundFromContext(ctx)) + hctx = session.ContextWithID(hctx, session.IDFromContext(ctx)) + hctx = session.ContextWithOutbound(hctx, session.OutboundFromContext(ctx)) + hctx = session.ContextWithTimeoutOnly(hctx, true) - pconn, err := internet.DialSystem(dctx, net.TCPDestination(address, port), sockopt) + pconn, err := internet.DialSystem(hctx, net.TCPDestination(address, port), sockopt) if err != nil { newError("failed to dial to " + addr).Base(err).AtError().WriteToLog() return nil, err } + if realityConfigs != nil { + return reality.UClient(pconn, realityConfigs, hctx, dest) + } + var cn tls.Interface - if fingerprint, ok := tls.Fingerprints[tlsConfigs.Fingerprint]; ok { + if fingerprint := tls.GetFingerprint(tlsConfigs.Fingerprint); fingerprint != nil { cn = tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) } else { cn = tls.Client(pconn, tlsConfig).(*tls.Conn) @@ -99,7 +106,10 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in } return cn, nil }, - TLSClientConfig: tlsConfigs.GetTLSConfig(tls.WithDestination(dest)), + } + + if tlsConfigs != nil { + transport.TLSClientConfig = tlsConfigs.GetTLSConfig(tls.WithDestination(dest)) } if httpSettings.IdleTimeout > 0 || httpSettings.HealthCheckTimeout > 0 { @@ -157,23 +167,77 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me // Disable any compression method from server. request.Header.Set("Accept-Encoding", "identity") - response, err := client.Do(request) - if err != nil { - return nil, newError("failed to dial to ", dest).Base(err).AtWarning() - } - if response.StatusCode != 200 { - return nil, newError("unexpected status", response.StatusCode).AtWarning() - } + wrc := &WaitReadCloser{Wait: make(chan struct{})} + go func() { + response, err := client.Do(request) + if err != nil { + newError("failed to dial to ", dest).Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) + wrc.Close() + { + // Abandon `client` if `client.Do(request)` failed + // See https://github.com/golang/go/issues/30702 + globalDialerAccess.Lock() + if globalDialerMap[dialerConf{dest, streamSettings}] == client { + delete(globalDialerMap, dialerConf{dest, streamSettings}) + } + globalDialerAccess.Unlock() + } + return + } + if response.StatusCode != 200 { + newError("unexpected status", response.StatusCode).AtWarning().WriteToLog(session.ExportIDToError(ctx)) + wrc.Close() + return + } + wrc.Set(response.Body) + }() bwriter := buf.NewBufferedWriter(pwriter) common.Must(bwriter.SetBuffered(false)) return cnc.NewConnection( - cnc.ConnectionOutput(response.Body), + cnc.ConnectionOutput(wrc), cnc.ConnectionInput(bwriter), - cnc.ConnectionOnClose(common.ChainedClosable{breader, bwriter, response.Body}), + cnc.ConnectionOnClose(common.ChainedClosable{breader, bwriter, wrc}), ), nil } func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } + +type WaitReadCloser struct { + Wait chan struct{} + io.ReadCloser +} + +func (w *WaitReadCloser) Set(rc io.ReadCloser) { + w.ReadCloser = rc + defer func() { + if recover() != nil { + rc.Close() + } + }() + close(w.Wait) +} + +func (w *WaitReadCloser) Read(b []byte) (int, error) { + if w.ReadCloser == nil { + if <-w.Wait; w.ReadCloser == nil { + return 0, io.ErrClosedPipe + } + } + return w.ReadCloser.Read(b) +} + +func (w *WaitReadCloser) Close() error { + if w.ReadCloser != nil { + return w.ReadCloser.Close() + } + defer func() { + if recover() != nil && w.ReadCloser != nil { + w.ReadCloser.Close() + } + }() + close(w.Wait) + return nil +} diff --git a/transport/internet/http/hub.go b/transport/internet/http/hub.go index 13149267397b..f0260460d714 100644 --- a/transport/internet/http/hub.go +++ b/transport/internet/http/hub.go @@ -7,6 +7,7 @@ import ( "strings" "time" + goreality "github.com/xtls/reality" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" @@ -15,6 +16,7 @@ import ( "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/tls" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" @@ -25,7 +27,6 @@ type Listener struct { handler internet.ConnHandler local net.Addr config *Config - locker *internet.FileLocker // for unix domain socket } func (l *Listener) Addr() net.Addr { @@ -33,9 +34,6 @@ func (l *Listener) Addr() net.Addr { } func (l *Listener) Close() error { - if l.locker != nil { - l.locker.Release() - } return l.server.Close() } @@ -49,6 +47,13 @@ func (fw flushWriter) Write(p []byte) (n int, err error) { return 0, io.ErrClosedPipe } + defer func() { + if recover() != nil { + fw.d.Close() + err = io.ErrClosedPipe + } + }() + n, err = fw.w.Write(p) if f, ok := fw.w.(http.Flusher); ok && err == nil { f.Flush() @@ -171,10 +176,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti newError("failed to listen on ", address).Base(err).AtError().WriteToLog(session.ExportIDToError(ctx)) return } - locker := ctx.Value(address.Domain()) - if locker != nil { - listener.locker = locker.(*internet.FileLocker) - } } else { // tcp streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), @@ -187,14 +188,17 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti } if config == nil { + if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { + streamListener = goreality.NewListener(streamListener, config.GetREALITYConfig()) + } err = server.Serve(streamListener) if err != nil { - newError("stopping serving H2C").Base(err).WriteToLog(session.ExportIDToError(ctx)) + newError("stopping serving H2C or REALITY H2").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } else { err = server.ServeTLS(streamListener, "", "") if err != nil { - newError("stopping serving TLS").Base(err).WriteToLog(session.ExportIDToError(ctx)) + newError("stopping serving TLS H2").Base(err).WriteToLog(session.ExportIDToError(ctx)) } } }() diff --git a/transport/internet/kcp/config.pb.go b/transport/internet/kcp/config.pb.go index 3af1ffa09686..8b6dbd40f520 100644 --- a/transport/internet/kcp/config.pb.go +++ b/transport/internet/kcp/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/kcp/config.proto package kcp diff --git a/transport/internet/kcp/dialer.go b/transport/internet/kcp/dialer.go index dd6393bc6af6..3e8d1220e346 100644 --- a/transport/internet/kcp/dialer.go +++ b/transport/internet/kcp/dialer.go @@ -12,7 +12,6 @@ import ( "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) var globalConv = uint32(dice.RollUint16()) @@ -87,8 +86,6 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest))) - } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { - iConn = xtls.Client(iConn, config.GetXTLSConfig(xtls.WithDestination(dest))) } return iConn, nil diff --git a/transport/internet/kcp/kcp.go b/transport/internet/kcp/kcp.go index 242dce949ed5..1a486faaa269 100644 --- a/transport/internet/kcp/kcp.go +++ b/transport/internet/kcp/kcp.go @@ -1,8 +1,9 @@ // Package kcp - A Fast and Reliable ARQ Protocol // // Acknowledgement: -// skywind3000@github for inventing the KCP protocol -// xtaci@github for translating to Golang +// +// skywind3000@github for inventing the KCP protocol +// xtaci@github for translating to Golang package kcp //go:generate go run github.com/xtls/xray-core/common/errors/errorgen diff --git a/transport/internet/kcp/listener.go b/transport/internet/kcp/listener.go index 6cf2a5398340..82b5a3bc9063 100644 --- a/transport/internet/kcp/listener.go +++ b/transport/internet/kcp/listener.go @@ -6,7 +6,6 @@ import ( gotls "crypto/tls" "sync" - goxtls "github.com/xtls/go" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" @@ -14,7 +13,6 @@ import ( "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/udp" - "github.com/xtls/xray-core/transport/internet/xtls" ) type ConnectionID struct { @@ -26,15 +24,14 @@ type ConnectionID struct { // Listener defines a server listening for connections type Listener struct { sync.Mutex - sessions map[ConnectionID]*Connection - hub *udp.Hub - tlsConfig *gotls.Config - xtlsConfig *goxtls.Config - config *Config - reader PacketReader - header internet.PacketHeader - security cipher.AEAD - addConn internet.ConnHandler + sessions map[ConnectionID]*Connection + hub *udp.Hub + tlsConfig *gotls.Config + config *Config + reader PacketReader + header internet.PacketHeader + security cipher.AEAD + addConn internet.ConnHandler } func NewListener(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (*Listener, error) { @@ -62,9 +59,6 @@ func NewListener(ctx context.Context, address net.Address, port net.Port, stream if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { l.tlsConfig = config.GetTLSConfig() } - if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { - l.xtlsConfig = config.GetXTLSConfig() - } hub, err := udp.ListenUDP(ctx, address, port, streamSettings, udp.HubCapacity(1024)) if err != nil { @@ -137,8 +131,6 @@ func (l *Listener) OnReceive(payload *buf.Buffer, src net.Destination) { var netConn stat.Connection = conn if l.tlsConfig != nil { netConn = tls.Server(conn, l.tlsConfig) - } else if l.xtlsConfig != nil { - netConn = xtls.Server(conn, l.xtlsConfig) } l.addConn(netConn) diff --git a/transport/internet/quic/config.pb.go b/transport/internet/quic/config.pb.go index 15f1436eed9b..0b8509002a2b 100644 --- a/transport/internet/quic/config.pb.go +++ b/transport/internet/quic/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/quic/config.proto package quic diff --git a/transport/internet/quic/conn.go b/transport/internet/quic/conn.go index a349eb42d542..11bee7c543a9 100644 --- a/transport/internet/quic/conn.go +++ b/transport/internet/quic/conn.go @@ -7,7 +7,7 @@ import ( "syscall" "time" - "github.com/lucas-clemente/quic-go" + "github.com/quic-go/quic-go" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" diff --git a/transport/internet/quic/dialer.go b/transport/internet/quic/dialer.go index 9e12513ef3c3..f6083561ba4a 100644 --- a/transport/internet/quic/dialer.go +++ b/transport/internet/quic/dialer.go @@ -2,13 +2,12 @@ package quic import ( "context" - "io" "sync" "time" - "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/logging" - "github.com/lucas-clemente/quic-go/qlog" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/task" @@ -140,15 +139,13 @@ func (s *clientConnections) openConnection(ctx context.Context, destAddr net.Add } quicConfig := &quic.Config{ - ConnectionIDLength: 12, - KeepAlivePeriod: 0, + KeepAlivePeriod: 0, HandshakeIdleTimeout: time.Second * 8, MaxIdleTimeout: time.Second * 300, - Tracer: qlog.NewTracer(func(_ logging.Perspective, connID []byte) io.WriteCloser { - return &QlogWriter{connID: connID} - }), + Tracer: func(ctx context.Context, p logging.Perspective, ci quic.ConnectionID) logging.ConnectionTracer { + return qlog.NewConnectionTracer(&QlogWriter{connID: ci}, p, ci) + }, } - udpConn, _ := rawConn.(*net.UDPConn) if udpConn == nil { udpConn = rawConn.(*internet.PacketConnWrapper).Conn.(*net.UDPConn) @@ -158,8 +155,11 @@ func (s *clientConnections) openConnection(ctx context.Context, destAddr net.Add rawConn.Close() return nil, err } - - conn, err := quic.DialContext(context.Background(), sysConn, destAddr, "", tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig) + tr := quic.Transport{ + ConnectionIDLength: 12, + Conn: sysConn, + } + conn, err := tr.Dial(context.Background(), destAddr, tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig) if err != nil { sysConn.Close() return nil, err diff --git a/transport/internet/quic/hub.go b/transport/internet/quic/hub.go index 5d7e2b95395d..8bab5bf35d9a 100644 --- a/transport/internet/quic/hub.go +++ b/transport/internet/quic/hub.go @@ -2,12 +2,11 @@ package quic import ( "context" - "io" "time" - "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/logging" - "github.com/lucas-clemente/quic-go/qlog" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/logging" + "github.com/quic-go/quic-go/qlog" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol/tls/cert" @@ -19,7 +18,7 @@ import ( // Listener is an internet.Listener that listens for TCP connections. type Listener struct { rawConn *sysConn - listener quic.Listener + listener *quic.Listener done *done.Instance addConn internet.ConnHandler } @@ -104,15 +103,14 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti } quicConfig := &quic.Config{ - ConnectionIDLength: 12, KeepAlivePeriod: 0, - HandshakeIdleTimeout: time.Second * 8, - MaxIdleTimeout: time.Second * 300, + HandshakeIdleTimeout: time.Second * 8, + MaxIdleTimeout: time.Second * 300, MaxIncomingStreams: 32, MaxIncomingUniStreams: -1, - Tracer: qlog.NewTracer(func(_ logging.Perspective, connID []byte) io.WriteCloser { - return &QlogWriter{connID: connID} - }), + Tracer: func(ctx context.Context, p logging.Perspective, ci quic.ConnectionID) logging.ConnectionTracer { + return qlog.NewConnectionTracer(&QlogWriter{connID: ci}, p, ci) + }, } conn, err := wrapSysConn(rawConn.(*net.UDPConn), config) @@ -120,8 +118,11 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti conn.Close() return nil, err } - - qListener, err := quic.Listen(conn, tlsConfig.GetTLSConfig(), quicConfig) + tr := quic.Transport{ + ConnectionIDLength: 12, + Conn: conn, + } + qListener, err := tr.Listen(tlsConfig.GetTLSConfig(), quicConfig) if err != nil { conn.Close() return nil, err diff --git a/transport/internet/quic/qlogWriter.go b/transport/internet/quic/qlogWriter.go index 1b7913e6e60d..54284d29cea6 100644 --- a/transport/internet/quic/qlogWriter.go +++ b/transport/internet/quic/qlogWriter.go @@ -1,22 +1,20 @@ package quic -import ( - "fmt" - - "github.com/xtls/xray-core/common/log" -) +import "github.com/quic-go/quic-go" type QlogWriter struct { - connID []byte + connID quic.ConnectionID } func (w *QlogWriter) Write(b []byte) (int, error) { - if len(b) > 1 { // skip line separator "0a" in qlog - log.Record(&log.GeneralMessage{ - Severity: log.Severity_Debug, - Content: fmt.Sprintf("[%x] %s", w.connID, b), - }) - } + // to much log, only turn on when debug Quic + + // if len(b) > 1 { // skip line separator "0a" in qlog + // log.Record(&log.GeneralMessage{ + // Severity: log.Severity_Debug, + // Content: fmt.Sprintf("[%x] %s", w.connID, b), + // }) + // } return len(b), nil } diff --git a/transport/internet/reality/config.go b/transport/internet/reality/config.go new file mode 100644 index 000000000000..58608720628e --- /dev/null +++ b/transport/internet/reality/config.go @@ -0,0 +1,49 @@ +package reality + +import ( + "net" + "time" + + "github.com/xtls/reality" + "github.com/xtls/xray-core/transport/internet" +) + +func (c *Config) GetREALITYConfig() *reality.Config { + var dialer net.Dialer + config := &reality.Config{ + DialContext: dialer.DialContext, + + Show: c.Show, + Type: c.Type, + Dest: c.Dest, + Xver: byte(c.Xver), + + PrivateKey: c.PrivateKey, + MinClientVer: c.MinClientVer, + MaxClientVer: c.MaxClientVer, + MaxTimeDiff: time.Duration(c.MaxTimeDiff) * time.Millisecond, + + NextProtos: nil, // should be nil + SessionTicketsDisabled: true, + } + config.ServerNames = make(map[string]bool) + for _, serverName := range c.ServerNames { + config.ServerNames[serverName] = true + } + config.ShortIds = make(map[[8]byte]bool) + for _, shortId := range c.ShortIds { + config.ShortIds[*(*[8]byte)(shortId)] = true + } + return config +} + +func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { + if settings == nil { + return nil + } + config, ok := settings.SecuritySettings.(*Config) + if !ok { + return nil + } + return config +} diff --git a/transport/internet/reality/config.pb.go b/transport/internet/reality/config.pb.go new file mode 100644 index 000000000000..799e30d476f8 --- /dev/null +++ b/transport/internet/reality/config.pb.go @@ -0,0 +1,300 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v4.23.1 +// source: transport/internet/reality/config.proto + +package reality + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Show bool `protobuf:"varint,1,opt,name=show,proto3" json:"show,omitempty"` + Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Xver uint64 `protobuf:"varint,4,opt,name=xver,proto3" json:"xver,omitempty"` + ServerNames []string `protobuf:"bytes,5,rep,name=server_names,json=serverNames,proto3" json:"server_names,omitempty"` + PrivateKey []byte `protobuf:"bytes,6,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` + MinClientVer []byte `protobuf:"bytes,7,opt,name=min_client_ver,json=minClientVer,proto3" json:"min_client_ver,omitempty"` + MaxClientVer []byte `protobuf:"bytes,8,opt,name=max_client_ver,json=maxClientVer,proto3" json:"max_client_ver,omitempty"` + MaxTimeDiff uint64 `protobuf:"varint,9,opt,name=max_time_diff,json=maxTimeDiff,proto3" json:"max_time_diff,omitempty"` + ShortIds [][]byte `protobuf:"bytes,10,rep,name=short_ids,json=shortIds,proto3" json:"short_ids,omitempty"` + Fingerprint string `protobuf:"bytes,21,opt,name=Fingerprint,proto3" json:"Fingerprint,omitempty"` + ServerName string `protobuf:"bytes,22,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` + PublicKey []byte `protobuf:"bytes,23,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + ShortId []byte `protobuf:"bytes,24,opt,name=short_id,json=shortId,proto3" json:"short_id,omitempty"` + SpiderX string `protobuf:"bytes,25,opt,name=spider_x,json=spiderX,proto3" json:"spider_x,omitempty"` + SpiderY []int64 `protobuf:"varint,26,rep,packed,name=spider_y,json=spiderY,proto3" json:"spider_y,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_reality_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_reality_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_reality_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetShow() bool { + if x != nil { + return x.Show + } + return false +} + +func (x *Config) GetDest() string { + if x != nil { + return x.Dest + } + return "" +} + +func (x *Config) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Config) GetXver() uint64 { + if x != nil { + return x.Xver + } + return 0 +} + +func (x *Config) GetServerNames() []string { + if x != nil { + return x.ServerNames + } + return nil +} + +func (x *Config) GetPrivateKey() []byte { + if x != nil { + return x.PrivateKey + } + return nil +} + +func (x *Config) GetMinClientVer() []byte { + if x != nil { + return x.MinClientVer + } + return nil +} + +func (x *Config) GetMaxClientVer() []byte { + if x != nil { + return x.MaxClientVer + } + return nil +} + +func (x *Config) GetMaxTimeDiff() uint64 { + if x != nil { + return x.MaxTimeDiff + } + return 0 +} + +func (x *Config) GetShortIds() [][]byte { + if x != nil { + return x.ShortIds + } + return nil +} + +func (x *Config) GetFingerprint() string { + if x != nil { + return x.Fingerprint + } + return "" +} + +func (x *Config) GetServerName() string { + if x != nil { + return x.ServerName + } + return "" +} + +func (x *Config) GetPublicKey() []byte { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *Config) GetShortId() []byte { + if x != nil { + return x.ShortId + } + return nil +} + +func (x *Config) GetSpiderX() string { + if x != nil { + return x.SpiderX + } + return "" +} + +func (x *Config) GetSpiderY() []int64 { + if x != nil { + return x.SpiderY + } + return nil +} + +var File_transport_internet_reality_config_proto protoreflect.FileDescriptor + +var file_transport_internet_reality_config_proto_rawDesc = []byte{ + 0x0a, 0x27, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x22, 0xdc, 0x03, 0x0a, 0x06, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x04, 0x78, 0x76, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, + 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x12, + 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x65, + 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x56, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, + 0x78, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x69, 0x66, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x6f, + 0x72, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x68, + 0x6f, 0x72, 0x74, 0x49, 0x64, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x46, 0x69, 0x6e, + 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x6f, 0x72, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x68, 0x6f, 0x72, + 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x78, 0x18, + 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x58, 0x12, 0x19, + 0x0a, 0x08, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x79, 0x18, 0x1a, 0x20, 0x03, 0x28, 0x03, + 0x52, 0x07, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x59, 0x42, 0x7f, 0x0a, 0x23, 0x63, 0x6f, 0x6d, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, + 0x50, 0x01, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, + 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0xaa, 0x02, 0x1f, 0x58, 0x72, 0x61, 0x79, 0x2e, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x2e, 0x52, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_transport_internet_reality_config_proto_rawDescOnce sync.Once + file_transport_internet_reality_config_proto_rawDescData = file_transport_internet_reality_config_proto_rawDesc +) + +func file_transport_internet_reality_config_proto_rawDescGZIP() []byte { + file_transport_internet_reality_config_proto_rawDescOnce.Do(func() { + file_transport_internet_reality_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_reality_config_proto_rawDescData) + }) + return file_transport_internet_reality_config_proto_rawDescData +} + +var file_transport_internet_reality_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_transport_internet_reality_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: xray.transport.internet.reality.Config +} +var file_transport_internet_reality_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_transport_internet_reality_config_proto_init() } +func file_transport_internet_reality_config_proto_init() { + if File_transport_internet_reality_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_reality_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_transport_internet_reality_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_reality_config_proto_goTypes, + DependencyIndexes: file_transport_internet_reality_config_proto_depIdxs, + MessageInfos: file_transport_internet_reality_config_proto_msgTypes, + }.Build() + File_transport_internet_reality_config_proto = out.File + file_transport_internet_reality_config_proto_rawDesc = nil + file_transport_internet_reality_config_proto_goTypes = nil + file_transport_internet_reality_config_proto_depIdxs = nil +} diff --git a/transport/internet/reality/config.proto b/transport/internet/reality/config.proto new file mode 100644 index 000000000000..f9ae3a4fd80a --- /dev/null +++ b/transport/internet/reality/config.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package xray.transport.internet.reality; +option csharp_namespace = "Xray.Transport.Internet.Reality"; +option go_package = "github.com/xtls/xray-core/transport/internet/reality"; +option java_package = "com.xray.transport.internet.reality"; +option java_multiple_files = true; + +message Config { + bool show = 1; + string dest = 2; + string type = 3; + uint64 xver = 4; + repeated string server_names = 5; + bytes private_key = 6; + bytes min_client_ver = 7; + bytes max_client_ver = 8; + uint64 max_time_diff = 9; + repeated bytes short_ids = 10; + + string Fingerprint = 21; + string server_name = 22; + bytes public_key = 23; + bytes short_id = 24; + string spider_x = 25; + repeated int64 spider_y = 26; +} diff --git a/proxy/mtproto/errors.generated.go b/transport/internet/reality/errors.generated.go similarity index 92% rename from proxy/mtproto/errors.generated.go rename to transport/internet/reality/errors.generated.go index 012202d67ca9..e578015f52e4 100644 --- a/proxy/mtproto/errors.generated.go +++ b/transport/internet/reality/errors.generated.go @@ -1,4 +1,4 @@ -package mtproto +package reality import "github.com/xtls/xray-core/common/errors" diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go new file mode 100644 index 000000000000..30d4e2ae5187 --- /dev/null +++ b/transport/internet/reality/reality.go @@ -0,0 +1,282 @@ +package reality + +import ( + "bytes" + "context" + "crypto/aes" + "crypto/cipher" + "crypto/ecdh" + "crypto/ed25519" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + gotls "crypto/tls" + "crypto/x509" + "encoding/binary" + "fmt" + "io" + "math/big" + "net/http" + "reflect" + "regexp" + "strings" + "sync" + "time" + "unsafe" + + utls "github.com/refraction-networking/utls" + "github.com/xtls/reality" + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/transport/internet/tls" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" + "golang.org/x/net/http2" +) + +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen + +//go:linkname aesgcmPreferred github.com/refraction-networking/utls.aesgcmPreferred +func aesgcmPreferred(ciphers []uint16) bool + +type Conn struct { + *reality.Conn +} + +func (c *Conn) HandshakeAddress() net.Address { + if err := c.Handshake(); err != nil { + return nil + } + state := c.ConnectionState() + if state.ServerName == "" { + return nil + } + return net.ParseAddress(state.ServerName) +} + +func Server(c net.Conn, config *reality.Config) (net.Conn, error) { + realityConn, err := reality.Server(context.Background(), c, config) + return &Conn{Conn: realityConn}, err +} + +type UConn struct { + *utls.UConn + ServerName string + AuthKey []byte + Verified bool +} + +func (c *UConn) HandshakeAddress() net.Address { + if err := c.Handshake(); err != nil { + return nil + } + state := c.ConnectionState() + if state.ServerName == "" { + return nil + } + return net.ParseAddress(state.ServerName) +} + +func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") + certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset)) + if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok { + h := hmac.New(sha512.New, c.AuthKey) + h.Write(pub) + if bytes.Equal(h.Sum(nil), certs[0].Signature) { + c.Verified = true + return nil + } + } + opts := x509.VerifyOptions{ + DNSName: c.ServerName, + Intermediates: x509.NewCertPool(), + } + for _, cert := range certs[1:] { + opts.Intermediates.AddCert(cert) + } + if _, err := certs[0].Verify(opts); err != nil { + return err + } + return nil +} + +func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) { + localAddr := c.LocalAddr().String() + uConn := &UConn{} + utlsConfig := &utls.Config{ + VerifyPeerCertificate: uConn.VerifyPeerCertificate, + ServerName: config.ServerName, + InsecureSkipVerify: true, + SessionTicketsDisabled: true, + } + if utlsConfig.ServerName == "" { + utlsConfig.ServerName = dest.Address.String() + } + uConn.ServerName = utlsConfig.ServerName + fingerprint := tls.GetFingerprint(config.Fingerprint) + if fingerprint == nil { + return nil, newError("REALITY: failed to get fingerprint").AtError() + } + uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint) + { + uConn.BuildHandshakeState() + hello := uConn.HandshakeState.Hello + hello.SessionId = make([]byte, 32) + copy(hello.Raw[39:], hello.SessionId) // the fixed location of `Session ID` + hello.SessionId[0] = core.Version_x + hello.SessionId[1] = core.Version_y + hello.SessionId[2] = core.Version_z + hello.SessionId[3] = 0 // reserved + binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix())) + copy(hello.SessionId[8:], config.ShortId) + if config.Show { + fmt.Printf("REALITY localAddr: %v\thello.SessionId[:16]: %v\n", localAddr, hello.SessionId[:16]) + } + publicKey, _ := ecdh.X25519().NewPublicKey(config.PublicKey) + uConn.AuthKey, _ = uConn.HandshakeState.State13.EcdheKey.ECDH(publicKey) + if uConn.AuthKey == nil { + return nil, errors.New("REALITY: SharedKey == nil") + } + if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil { + return nil, err + } + var aead cipher.AEAD + if aesgcmPreferred(hello.CipherSuites) { + block, _ := aes.NewCipher(uConn.AuthKey) + aead, _ = cipher.NewGCM(block) + } else { + aead, _ = chacha20poly1305.New(uConn.AuthKey) + } + if config.Show { + fmt.Printf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead) + } + aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw) + copy(hello.Raw[39:], hello.SessionId) + } + if err := uConn.HandshakeContext(ctx); err != nil { + return nil, err + } + if config.Show { + fmt.Printf("REALITY localAddr: %v\tuConn.Verified: %v\n", localAddr, uConn.Verified) + } + if !uConn.Verified { + go func() { + client := &http.Client{ + Transport: &http2.Transport{ + DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) { + fmt.Printf("REALITY localAddr: %v\tDialTLSContext\n", localAddr) + return uConn, nil + }, + }, + } + prefix := []byte("https://" + uConn.ServerName) + maps.Lock() + if maps.maps == nil { + maps.maps = make(map[string]map[string]bool) + } + paths := maps.maps[uConn.ServerName] + if paths == nil { + paths = make(map[string]bool) + paths[config.SpiderX] = true + maps.maps[uConn.ServerName] = paths + } + firstURL := string(prefix) + getPathLocked(paths) + maps.Unlock() + get := func(first bool) { + var ( + req *http.Request + resp *http.Response + err error + body []byte + ) + if first { + req, _ = http.NewRequest("GET", firstURL, nil) + } else { + maps.Lock() + req, _ = http.NewRequest("GET", string(prefix)+getPathLocked(paths), nil) + maps.Unlock() + } + req.Header.Set("User-Agent", fingerprint.Client) // TODO: User-Agent map + if first && config.Show { + fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent()) + } + times := 1 + if !first { + times = int(randBetween(config.SpiderY[4], config.SpiderY[5])) + } + for j := 0; j < times; j++ { + if !first && j == 0 { + req.Header.Set("Referer", firstURL) + } + req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(randBetween(config.SpiderY[0], config.SpiderY[1])))}) + if resp, err = client.Do(req); err != nil { + break + } + req.Header.Set("Referer", req.URL.String()) + if body, err = io.ReadAll(resp.Body); err != nil { + break + } + maps.Lock() + for _, m := range href.FindAllSubmatch(body, -1) { + m[1] = bytes.TrimPrefix(m[1], prefix) + if !bytes.Contains(m[1], dot) { + paths[string(m[1])] = true + } + } + req.URL.Path = getPathLocked(paths) + if config.Show { + fmt.Printf("REALITY localAddr: %v\treq.Referer(): %v\n", localAddr, req.Referer()) + fmt.Printf("REALITY localAddr: %v\tlen(body): %v\n", localAddr, len(body)) + fmt.Printf("REALITY localAddr: %v\tlen(paths): %v\n", localAddr, len(paths)) + } + maps.Unlock() + if !first { + time.Sleep(time.Duration(randBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval + } + } + } + get(true) + concurrency := int(randBetween(config.SpiderY[2], config.SpiderY[3])) + for i := 0; i < concurrency; i++ { + go get(false) + } + // Do not close the connection + }() + time.Sleep(time.Duration(randBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return + return nil, errors.New("REALITY: processed invalid connection") + } + return uConn, nil +} + +var ( + href = regexp.MustCompile(`href="([/h].*?)"`) + dot = []byte(".") +) + +var maps struct { + sync.Mutex + maps map[string]map[string]bool +} + +func getPathLocked(paths map[string]bool) string { + stopAt := int(randBetween(0, int64(len(paths)-1))) + i := 0 + for s := range paths { + if i == stopAt { + return s + } + i++ + } + return "/" +} + +func randBetween(left int64, right int64) int64 { + if left == right { + return left + } + bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left)) + return left + bigInt.Int64() +} diff --git a/transport/internet/sockopt_darwin.go b/transport/internet/sockopt_darwin.go index 87a524cb4374..37ced27b6a33 100644 --- a/transport/internet/sockopt_darwin.go +++ b/transport/internet/sockopt_darwin.go @@ -1,11 +1,12 @@ package internet import ( - "github.com/xtls/xray-core/common/net" - "golang.org/x/sys/unix" "os" "syscall" "unsafe" + + "github.com/xtls/xray-core/common/net" + "golang.org/x/sys/unix" ) const ( @@ -125,6 +126,12 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf return newError("failed to unset SO_KEEPALIVE", err) } } + + if config.TcpNoDelay { + if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_NODELAY, 1); err != nil { + return newError("failed to set TCP_NODELAY", err) + } + } } return nil diff --git a/transport/internet/sockopt_linux.go b/transport/internet/sockopt_linux.go index a5a832984ad5..56f24be8bad8 100644 --- a/transport/internet/sockopt_linux.go +++ b/transport/internet/sockopt_linux.go @@ -7,13 +7,6 @@ import ( "golang.org/x/sys/unix" ) -const ( - // For incoming connections. - TCP_FASTOPEN = 23 - // For out-going connections. - TCP_FASTOPEN_CONNECT = 30 -) - func bindAddr(fd uintptr, ip []byte, port uint32) error { setReuseAddr(fd) setReusePort(fd) @@ -47,14 +40,20 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf } } + if config.Interface != "" { + if err := syscall.BindToDevice(int(fd), config.Interface); err != nil { + return newError("failed to set Interface").Base(err) + } + } + if isTCPSocket(network) { tfo := config.ParseTFOValue() if tfo > 0 { tfo = 1 } if tfo >= 0 { - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN_CONNECT, tfo); err != nil { - return newError("failed to set TCP_FASTOPEN_CONNECT=", tfo).Base(err) + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN_CONNECT, tfo); err != nil { + return newError("failed to set TCP_FASTOPEN_CONNECT", tfo).Base(err) } } @@ -78,11 +77,36 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf } } - if config.TcpCongestion != "" { - if err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil { - return newError("failed to set TCP_CONGESTION", err) - } - } + if config.TcpCongestion != "" { + if err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil { + return newError("failed to set TCP_CONGESTION", err) + } + } + + if config.TcpWindowClamp > 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_WINDOW_CLAMP, int(config.TcpWindowClamp)); err != nil { + return newError("failed to set TCP_WINDOW_CLAMP", err) + } + } + + if config.TcpUserTimeout > 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(config.TcpUserTimeout)); err != nil { + return newError("failed to set TCP_USER_TIMEOUT", err) + } + } + + if config.TcpMaxSeg > 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_MAXSEG, int(config.TcpMaxSeg)); err != nil { + return newError("failed to set TCP_MAXSEG", err) + } + } + + if config.TcpNoDelay { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_NODELAY, 1); err != nil { + return newError("failed to set TCP_NODELAY", err) + } + } + } if config.Tproxy.IsEnabled() { @@ -103,8 +127,8 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) if isTCPSocket(network) { tfo := config.ParseTFOValue() if tfo >= 0 { - if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, TCP_FASTOPEN, tfo); err != nil { - return newError("failed to set TCP_FASTOPEN=", tfo).Base(err) + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN, tfo); err != nil { + return newError("failed to set TCP_FASTOPEN", tfo).Base(err) } } @@ -128,11 +152,29 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) } } - if config.TcpCongestion != "" { - if err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil { - return newError("failed to set TCP_CONGESTION", err) - } - } + if config.TcpCongestion != "" { + if err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil { + return newError("failed to set TCP_CONGESTION", err) + } + } + + if config.TcpWindowClamp > 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_WINDOW_CLAMP, int(config.TcpWindowClamp)); err != nil { + return newError("failed to set TCP_WINDOW_CLAMP", err) + } + } + + if config.TcpUserTimeout > 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(config.TcpUserTimeout)); err != nil { + return newError("failed to set TCP_USER_TIMEOUT", err) + } + } + + if config.TcpMaxSeg > 0 { + if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_MAXSEG, int(config.TcpMaxSeg)); err != nil { + return newError("failed to set TCP_MAXSEG", err) + } + } } if config.Tproxy.IsEnabled() { @@ -149,6 +191,12 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) } } + if config.V6Only { + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, syscall.IPV6_V6ONLY, 1); err != nil { + return newError("failed to set IPV6_V6ONLY", err) + } + } + return nil } diff --git a/transport/internet/sockopt_windows.go b/transport/internet/sockopt_windows.go index ccc7b039253e..703a53c2f9b1 100644 --- a/transport/internet/sockopt_windows.go +++ b/transport/internet/sockopt_windows.go @@ -34,6 +34,11 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf return newError("failed to unset SO_KEEPALIVE", err) } } + if config.TcpNoDelay { + if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1); err != nil { + return newError("failed to set TCP_NODELAY", err) + } + } } return nil diff --git a/transport/internet/system_dialer.go b/transport/internet/system_dialer.go index 93cf404ef1a7..5304595fc8cf 100644 --- a/transport/internet/system_dialer.go +++ b/transport/internet/system_dialer.go @@ -5,6 +5,7 @@ import ( "syscall" "time" + "github.com/sagernet/sing/common/control" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/features/dns" @@ -18,7 +19,7 @@ type SystemDialer interface { } type DefaultSystemDialer struct { - controllers []controller + controllers []control.Func dns dns.Client obm outbound.Manager } @@ -80,7 +81,15 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne } if sockopt != nil || len(d.controllers) > 0 { + if sockopt != nil && sockopt.TcpMptcp { + dialer.SetMultipathTCP(true) + } dialer.Control = func(network, address string, c syscall.RawConn) error { + for _, ctl := range d.controllers { + if err := ctl(network, address, c); err != nil { + newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + } return c.Control(func(fd uintptr) { if sockopt != nil { if err := applyOutboundSocketOptions(network, address, fd, sockopt); err != nil { @@ -92,12 +101,6 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne } } } - - for _, ctl := range d.controllers { - if err := ctl(network, address, fd); err != nil { - newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - } }) } } @@ -185,7 +188,7 @@ func UseAlternativeSystemDialer(dialer SystemDialer) { // It only works when effective dialer is the default dialer. // // xray:api:beta -func RegisterDialerController(ctl func(network, address string, fd uintptr) error) error { +func RegisterDialerController(ctl control.Func) error { if ctl == nil { return newError("nil listener controller") } diff --git a/transport/internet/system_listener.go b/transport/internet/system_listener.go index 0469438345e2..6593f4bce705 100644 --- a/transport/internet/system_listener.go +++ b/transport/internet/system_listener.go @@ -10,21 +10,39 @@ import ( "time" "github.com/pires/go-proxyproto" + "github.com/sagernet/sing/common/control" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" ) var effectiveListener = DefaultListener{} -type controller func(network, address string, fd uintptr) error - type DefaultListener struct { - controllers []controller + controllers []control.Func +} + +type combinedListener struct { + net.Listener + locker *FileLocker // for unix domain socket +} + +func (cl *combinedListener) Close() error { + if cl.locker != nil { + cl.locker.Release() + cl.locker = nil + } + return cl.Listener.Close() } -func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []controller) func(network, address string, c syscall.RawConn) error { +func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []control.Func) func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { + for _, controller := range controllers { + if err := controller(network, address, c); err != nil { + newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + } + if sockopt != nil { if err := applyInboundSocketOptions(network, fd, sockopt); err != nil { newError("failed to apply socket options to incoming connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) @@ -32,12 +50,6 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []co } setReusePort(fd) - - for _, controller := range controllers { - if err := controller(network, address, fd); err != nil { - newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - } }) } } @@ -45,37 +57,29 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []co func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (l net.Listener, err error) { var lc net.ListenConfig var network, address string + // callback is called after the Listen function returns + callback := func(l net.Listener, err error) (net.Listener, error) { + return l, err + } switch addr := addr.(type) { case *net.TCPAddr: network = addr.Network() address = addr.String() lc.Control = getControlFunc(ctx, sockopt, dl.controllers) - if sockopt != nil && (sockopt.TcpKeepAliveInterval != 0 || sockopt.TcpKeepAliveIdle != 0) { - lc.KeepAlive = time.Duration(-1) + if sockopt != nil { + if sockopt.TcpKeepAliveInterval != 0 || sockopt.TcpKeepAliveIdle != 0 { + lc.KeepAlive = time.Duration(-1) + } + if sockopt.TcpMptcp { + lc.SetMultipathTCP(true) + } } case *net.UnixAddr: lc.Control = nil network = addr.Network() address = addr.Name - if s := strings.Split(address, ","); len(s) == 2 { - address = s[0] - perm, perr := strconv.ParseUint(s[1], 8, 32) - if perr != nil { - return nil, newError("failed to parse permission: " + s[1]).Base(perr) - } - - defer func(file string, permission os.FileMode) { - if err == nil { - cerr := os.Chmod(address, permission) - if cerr != nil { - err = newError("failed to set permission for " + file).Base(cerr) - } - } - }(address, os.FileMode(perm)) - } - if (runtime.GOOS == "linux" || runtime.GOOS == "android") && address[0] == '@' { // linux abstract unix domain socket is lockfree if len(address) > 1 && address[1] == '@' { @@ -85,19 +89,48 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S address = string(fullAddr) } } else { + // split permission from address + var filePerm *os.FileMode + if s := strings.Split(address, ","); len(s) == 2 { + address = s[0] + perm, perr := strconv.ParseUint(s[1], 8, 32) + if perr != nil { + return nil, newError("failed to parse permission: " + s[1]).Base(perr) + } + + mode := os.FileMode(perm) + filePerm = &mode + } // normal unix domain socket needs lock locker := &FileLocker{ path: address + ".lock", } - err := locker.Acquire() - if err != nil { + if err := locker.Acquire(); err != nil { return nil, err } - ctx = context.WithValue(ctx, address, locker) + + // set callback to combine listener and set permission + callback = func(l net.Listener, err error) (net.Listener, error) { + if err != nil { + locker.Release() + return l, err + } + l = &combinedListener{Listener: l, locker: locker} + if filePerm == nil { + return l, nil + } + err = os.Chmod(address, *filePerm) + if err != nil { + l.Close() + return nil, newError("failed to set permission for " + address).Base(err) + } + return l, nil + } } } l, err = lc.Listen(ctx, network, address) + l, err = callback(l, err) if sockopt != nil && sockopt.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } l = &proxyproto.Listener{Listener: l, Policy: policyFunc} @@ -117,7 +150,7 @@ func (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sock // The controller can be used to operate on file descriptors before they are put into use. // // xray:api:beta -func RegisterListenerController(controller func(network, address string, fd uintptr) error) error { +func RegisterListenerController(controller control.Func) error { if controller == nil { return newError("nil listener controller") } diff --git a/transport/internet/system_listener_test.go b/transport/internet/system_listener_test.go index 0fcc9a95ae09..390888e7a786 100644 --- a/transport/internet/system_listener_test.go +++ b/transport/internet/system_listener_test.go @@ -3,8 +3,10 @@ package internet_test import ( "context" "net" + "syscall" "testing" + "github.com/sagernet/sing/common/control" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/transport/internet" ) @@ -12,9 +14,11 @@ import ( func TestRegisterListenerController(t *testing.T) { var gotFd uintptr - common.Must(internet.RegisterListenerController(func(network string, addr string, fd uintptr) error { - gotFd = fd - return nil + common.Must(internet.RegisterListenerController(func(network, address string, conn syscall.RawConn) error { + return control.Raw(conn, func(fd uintptr) error { + gotFd = fd + return nil + }) })) conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{ diff --git a/transport/internet/tcp/config.pb.go b/transport/internet/tcp/config.pb.go index 1a73b054bee0..1ff79f642b28 100644 --- a/transport/internet/tcp/config.pb.go +++ b/transport/internet/tcp/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/tcp/config.proto package tcp diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index 296c7d8df2e5..840062b1f288 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -7,9 +7,9 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) // Dial dials a new TCP connection to the given destination. @@ -22,7 +22,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - if fingerprint, ok := tls.Fingerprints[config.Fingerprint]; ok { + if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).Handshake(); err != nil { return nil, err @@ -30,9 +30,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } else { conn = tls.Client(conn, tlsConfig) } - } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { - xtlsConfig := config.GetXTLSConfig(xtls.WithDestination(dest)) - conn = xtls.Client(conn, xtlsConfig) + } else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { + if conn, err = reality.UClient(conn, config, ctx, dest); err != nil { + return nil, err + } } tcpSettings := streamSettings.ProtocolSettings.(*Config) diff --git a/transport/internet/tcp/hub.go b/transport/internet/tcp/hub.go index 828bf97267d9..d4b4f8b5ef04 100644 --- a/transport/internet/tcp/hub.go +++ b/transport/internet/tcp/hub.go @@ -6,25 +6,24 @@ import ( "strings" "time" - goxtls "github.com/xtls/go" + goreality "github.com/xtls/reality" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/transport/internet" + "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/tls" - "github.com/xtls/xray-core/transport/internet/xtls" ) // Listener is an internet.Listener that listens for TCP connections. type Listener struct { - listener net.Listener - tlsConfig *gotls.Config - xtlsConfig *goxtls.Config - authConfig internet.ConnectionAuthenticator - config *Config - addConn internet.ConnHandler - locker *internet.FileLocker // for unix domain socket + listener net.Listener + tlsConfig *gotls.Config + realityConfig *goreality.Config + authConfig internet.ConnectionAuthenticator + config *Config + addConn internet.ConnHandler } // ListenTCP creates a new Listener based on configurations. @@ -51,10 +50,6 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe return nil, newError("failed to listen Unix Domain Socket on ", address).Base(err) } newError("listening Unix Domain Socket on ", address).WriteToLog(session.ExportIDToError(ctx)) - locker := ctx.Value(address.Domain()) - if locker != nil { - l.locker = locker.(*internet.FileLocker) - } } else { listener, err = internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), @@ -75,8 +70,8 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { l.tlsConfig = config.GetTLSConfig() } - if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { - l.xtlsConfig = config.GetXTLSConfig() + if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { + l.realityConfig = config.GetREALITYConfig() } if tcpSettings.HeaderSettings != nil { @@ -109,17 +104,20 @@ func (v *Listener) keepAccepting() { } continue } - - if v.tlsConfig != nil { - conn = tls.Server(conn, v.tlsConfig) - } else if v.xtlsConfig != nil { - conn = xtls.Server(conn, v.xtlsConfig) - } - if v.authConfig != nil { - conn = v.authConfig.Server(conn) - } - - v.addConn(stat.Connection(conn)) + go func() { + if v.tlsConfig != nil { + conn = tls.Server(conn, v.tlsConfig) + } else if v.realityConfig != nil { + if conn, err = reality.Server(conn, v.realityConfig); err != nil { + newError(err).AtInfo().WriteToLog() + return + } + } + if v.authConfig != nil { + conn = v.authConfig.Server(conn) + } + v.addConn(stat.Connection(conn)) + }() } } @@ -130,9 +128,6 @@ func (v *Listener) Addr() net.Addr { // Close implements internet.Listener.Close. func (v *Listener) Close() error { - if v.locker != nil { - v.locker.Release() - } return v.listener.Close() } diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index e1c128825401..2e2b784a8720 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -266,6 +266,20 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert } return newError("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue)) } + + if c.PinnedPeerCertificatePublicKeySha256 != nil { + for _, v := range verifiedChains { + for _, cert := range v { + publicHash := GenerateCertPublicKeyHash(cert) + for _, c := range c.PinnedPeerCertificatePublicKeySha256 { + if hmac.Equal(publicHash, c) { + return nil + } + } + } + } + return newError("peer public key is unrecognized.") + } return nil } @@ -359,8 +373,8 @@ type Option func(*tls.Config) // WithDestination sets the server name in TLS config. func WithDestination(dest net.Destination) Option { return func(config *tls.Config) { - if dest.Address.Family().IsDomain() && config.ServerName == "" { - config.ServerName = dest.Address.Domain() + if config.ServerName == "" { + config.ServerName = dest.Address.String() } } } diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 3718ead284e3..65c18e6bf1c7 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/tls/config.proto package tls @@ -203,6 +203,11 @@ type Config struct { // @Document This value replace allow_insecure. // @Critical PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"` + // @Document A pinned certificate public key sha256 hash. + // @Document If the server's public key hash does not match this value, the connection will be aborted. + // @Document This value replace allow_insecure. + // @Critical + PinnedPeerCertificatePublicKeySha256 [][]byte `protobuf:"bytes,14,rep,name=pinned_peer_certificate_public_key_sha256,json=pinnedPeerCertificatePublicKeySha256,proto3" json:"pinned_peer_certificate_public_key_sha256,omitempty"` } func (x *Config) Reset() { @@ -328,6 +333,13 @@ func (x *Config) GetPinnedPeerCertificateChainSha256() [][]byte { return nil } +func (x *Config) GetPinnedPeerCertificatePublicKeySha256() [][]byte { + if x != nil { + return x.PinnedPeerCertificatePublicKeySha256 + } + return nil +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -357,7 +369,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, - 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xf3, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, + 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xcc, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, 0x72, @@ -396,15 +408,21 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x20, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x42, 0x73, 0x0a, - 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, - 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, - 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, - 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, - 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x57, 0x0a, + 0x29, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x24, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, + 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, + 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 28266e20d47d..227840a21d35 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -76,4 +76,11 @@ message Config { @Critical */ repeated bytes pinned_peer_certificate_chain_sha256 = 13; + + /* @Document A pinned certificate public key sha256 hash. + @Document If the server's public key hash does not match this value, the connection will be aborted. + @Document This value replace allow_insecure. + @Critical + */ + repeated bytes pinned_peer_certificate_public_key_sha256 = 14; } diff --git a/transport/internet/tls/grpc.go b/transport/internet/tls/grpc.go index ede921b7c58d..a698196b9209 100644 --- a/transport/internet/tls/grpc.go +++ b/transport/internet/tls/grpc.go @@ -3,11 +3,12 @@ package tls import ( "context" gotls "crypto/tls" - utls "github.com/refraction-networking/utls" - "google.golang.org/grpc/credentials" "net" "net/url" "strconv" + + utls "github.com/refraction-networking/utls" + "google.golang.org/grpc/credentials" ) // grpcUtlsInfo contains the auth information for a TLS authenticated connection. diff --git a/transport/internet/tls/pin.go b/transport/internet/tls/pin.go index a7b012b546d3..f561bfdf69dc 100644 --- a/transport/internet/tls/pin.go +++ b/transport/internet/tls/pin.go @@ -2,6 +2,7 @@ package tls import ( "crypto/sha256" + "crypto/x509" "encoding/base64" "encoding/pem" ) @@ -34,3 +35,8 @@ func GenerateCertChainHash(rawCerts [][]byte) []byte { } return hashValue } + +func GenerateCertPublicKeyHash(cert *x509.Certificate) []byte { + out := sha256.Sum256(cert.RawSubjectPublicKeyInfo) + return out[:] +} diff --git a/transport/internet/tls/pin_test.go b/transport/internet/tls/pin_test.go index 9607fe1f71e1..cfc60e178081 100644 --- a/transport/internet/tls/pin_test.go +++ b/transport/internet/tls/pin_test.go @@ -1,6 +1,9 @@ package tls import ( + "crypto/x509" + "encoding/base64" + "encoding/pem" "testing" "github.com/stretchr/testify/assert" @@ -108,3 +111,89 @@ tzY45d4mjPs0fKCFKSsVM6YT0tX4NwIKsOaeQg30WLtRyDwYm6ma/a/UUUS0FloZ assert.Equal(t, "FW3SVMCL6um2wVltOdgJ3DpI82aredw83YoCblkMkVM=", hash) }) } + +func TestCalculateCertPublicKeyHash(t *testing.T) { + const Single = `-----BEGIN CERTIFICATE----- +MIINWTCCC0GgAwIBAgITLQAxbA/A+lw/1sLDAAAAADFsDzANBgkqhkiG9w0BAQsF +ADBPMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MSAwHgYDVQQDExdNaWNyb3NvZnQgUlNBIFRMUyBDQSAwMjAeFw0yMjExMjUwMDU2 +NTZaFw0yMzA1MjUwMDU2NTZaMBcxFTATBgNVBAMTDHd3dy5iaW5nLmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOH89lKmtkDnClFiQwfZofZO4h8C +Ye/+ChI67pEw5Q6/MxJzHiMKe8f1WaNuc+wkdHdct+BmQ+AftozIJt+eSN6IF7eY +dsutBvR87GNLFe40MBvfyvTQVM9Ulv04JxOpKTYnsf2wmktEI3y7FCgfm9RT71n+ +Zef8Z8fa4By7aGfbbCQ0DsHl5P9o3ug/eLQODzK9NuQlwcVBHD2Zvgo+K7WOsjgE +k8JnOr+2zc0WWT4OrWSDJE/3l+jvhxmZkrwgmks4m9zUZvAnYAz/xxVCJRqbI3Ou +S5fkJJ3f6IxPbS2i8OWz6tma1aIkgQaFNJQuYOJa1esfQcEzs6kb/Xx5DXUCAwEA +AaOCCWQwgglgMIIBfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgCt9776fP8QyIud +PZwePhhqtGcpXc+xDCTKhYY069yCigAAAYSsUtxtAAAEAwBHMEUCIQCP/Jpp337p +cKITqS/kNlA4bNY6TK1Ad0VlsdkzQU+oZgIgFZb2AcsyT1UKCmM3ziGsLdvS9MAT +D1g/kztyDXhkA70AdgBVgdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAA +AYSsUtsZAAAEAwBHMEUCIQDvlqXrdA440PW6b+JLj4F0ZVQNKHcv1lub0FhQqHgR +wAIgAtC7eXvXXhVBuO+Bd3fkDI0aGQM+pcvIesBoygzStjQAdQB6MoxU2LcttiDq +OOBSHumEFnAyE4VNO9IrwTpXo1LrUgAAAYSsUtmfAAAEAwBGMEQCIDgjSYt6e/h8 +dv2KGEL3AJZUBH2gp1AA5saH8o3OyMJhAiBOCzo3oWlVFeF/8c0fxIIs9Fj4w8BY +INo0jNP/k7apgTAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMBMAoGCCsGAQUF +BwMCMD4GCSsGAQQBgjcVBwQxMC8GJysGAQQBgjcVCIfahnWD7tkBgsmFG4G1nmGF +9OtggV2Fho5Bh8KYUAIBZAIBJzCBhwYIKwYBBQUHAQEEezB5MFMGCCsGAQUFBzAC +hkdodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL21zY29ycC9NaWNyb3NvZnQl +MjBSU0ElMjBUTFMlMjBDQSUyMDAyLmNydDAiBggrBgEFBQcwAYYWaHR0cDovL29j +c3AubXNvY3NwLmNvbTAdBgNVHQ4EFgQUpuSPPchFlPGu8FTbzPhJTFxQ7RowDgYD +VR0PAQH/BAQDAgSwMIIFbQYDVR0RBIIFZDCCBWCCDHd3dy5iaW5nLmNvbYIQZGlj +dC5iaW5nLmNvbS5jboITKi5wbGF0Zm9ybS5iaW5nLmNvbYIKKi5iaW5nLmNvbYII +YmluZy5jb22CFmllb25saW5lLm1pY3Jvc29mdC5jb22CEyoud2luZG93c3NlYXJj +aC5jb22CGWNuLmllb25saW5lLm1pY3Jvc29mdC5jb22CESoub3JpZ2luLmJpbmcu +Y29tgg0qLm1tLmJpbmcubmV0gg4qLmFwaS5iaW5nLmNvbYIYZWNuLmRldi52aXJ0 +dWFsZWFydGgubmV0gg0qLmNuLmJpbmcubmV0gg0qLmNuLmJpbmcuY29tghBzc2wt +YXBpLmJpbmcuY29tghBzc2wtYXBpLmJpbmcubmV0gg4qLmFwaS5iaW5nLm5ldIIO +Ki5iaW5nYXBpcy5jb22CD2JpbmdzYW5kYm94LmNvbYIWZmVlZGJhY2subWljcm9z +b2Z0LmNvbYIbaW5zZXJ0bWVkaWEuYmluZy5vZmZpY2UubmV0gg5yLmJhdC5iaW5n +LmNvbYIQKi5yLmJhdC5iaW5nLmNvbYISKi5kaWN0LmJpbmcuY29tLmNugg8qLmRp +Y3QuYmluZy5jb22CDiouc3NsLmJpbmcuY29tghAqLmFwcGV4LmJpbmcuY29tghYq +LnBsYXRmb3JtLmNuLmJpbmcuY29tgg13cC5tLmJpbmcuY29tggwqLm0uYmluZy5j +b22CD2dsb2JhbC5iaW5nLmNvbYIRd2luZG93c3NlYXJjaC5jb22CDnNlYXJjaC5t +c24uY29tghEqLmJpbmdzYW5kYm94LmNvbYIZKi5hcGkudGlsZXMuZGl0dS5saXZl +LmNvbYIPKi5kaXR1LmxpdmUuY29tghgqLnQwLnRpbGVzLmRpdHUubGl2ZS5jb22C +GCoudDEudGlsZXMuZGl0dS5saXZlLmNvbYIYKi50Mi50aWxlcy5kaXR1LmxpdmUu +Y29tghgqLnQzLnRpbGVzLmRpdHUubGl2ZS5jb22CFSoudGlsZXMuZGl0dS5saXZl +LmNvbYILM2QubGl2ZS5jb22CE2FwaS5zZWFyY2gubGl2ZS5jb22CFGJldGEuc2Vh +cmNoLmxpdmUuY29tghVjbndlYi5zZWFyY2gubGl2ZS5jb22CDGRldi5saXZlLmNv +bYINZGl0dS5saXZlLmNvbYIRZmFyZWNhc3QubGl2ZS5jb22CDmltYWdlLmxpdmUu +Y29tgg9pbWFnZXMubGl2ZS5jb22CEWxvY2FsLmxpdmUuY29tLmF1ghRsb2NhbHNl +YXJjaC5saXZlLmNvbYIUbHM0ZC5zZWFyY2gubGl2ZS5jb22CDW1haWwubGl2ZS5j +b22CEW1hcGluZGlhLmxpdmUuY29tgg5sb2NhbC5saXZlLmNvbYINbWFwcy5saXZl +LmNvbYIQbWFwcy5saXZlLmNvbS5hdYIPbWluZGlhLmxpdmUuY29tgg1uZXdzLmxp +dmUuY29tghxvcmlnaW4uY253ZWIuc2VhcmNoLmxpdmUuY29tghZwcmV2aWV3Lmxv +Y2FsLmxpdmUuY29tgg9zZWFyY2gubGl2ZS5jb22CEnRlc3QubWFwcy5saXZlLmNv +bYIOdmlkZW8ubGl2ZS5jb22CD3ZpZGVvcy5saXZlLmNvbYIVdmlydHVhbGVhcnRo +LmxpdmUuY29tggx3YXAubGl2ZS5jb22CEndlYm1hc3Rlci5saXZlLmNvbYITd2Vi +bWFzdGVycy5saXZlLmNvbYIVd3d3LmxvY2FsLmxpdmUuY29tLmF1ghR3d3cubWFw +cy5saXZlLmNvbS5hdTCBsAYDVR0fBIGoMIGlMIGioIGfoIGchk1odHRwOi8vbXNj +cmwubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9NaWNyb3NvZnQlMjBSU0El +MjBUTFMlMjBDQSUyMDAyLmNybIZLaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br +aS9tc2NvcnAvY3JsL01pY3Jvc29mdCUyMFJTQSUyMFRMUyUyMENBJTIwMDIuY3Js +MFcGA1UdIARQME4wQgYJKwYBBAGCNyoBMDUwMwYIKwYBBQUHAgEWJ2h0dHA6Ly93 +d3cubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NwczAIBgZngQwBAgEwHwYDVR0j +BBgwFoAU/y9/4Qb0OPMt7SWNmML+DvZs/PowHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQB4OIB/EHxpF64iFZME7XkJjZYn +ZiYIfOfHs6EGDNn7fxvpZS9HVy1jOWv/RvzEbMuSV3b/fItaJN/zATBg5/6hb5Jq +HGIcnKmb+tYrKlYhSOngHSu/8/OYP1dFFIqcVe0769kwXaKUzLh6UVRaS+mB7GFc +sXmPMbv5NM7mCUEdMkOaoSmubfw/WzmmRGrcSmtCxtIwMcp8Jf13Esunq//4+9w3 +M/JXa8ubmXyrY63zt/Oz/NkVJvja89ueovscy6s5sw2r+Su4bRsJjmxwCbakp56K +rbh7z417LzW88MMuATvOyk/O8Rbw2KYVSEiQgO54kHI0YkHkJ/6IoeAT1pmCfHUE +Rd+Ec8T+/lE2BPLVqp8SjogDYiybb0IR5Gn2vYyUdzsS2h/C5qGNd2t5ehxfjQoL +G6Y3GJZQRxkSX6TLPYU0U63wWb4yeSxabpBlARaZMaAoqDa3cX53WCnrAXDz8vuH +yAtX2/Jq7IpybFK5kFzbxfI02Ik0aCWJUnXPL8L6esTskwvkzX8rSI/bjPrzcJL5 +B9pONLy6wc8/Arfu2eNlMbs8s/g8c5zkEc3fBZ9tJ1dqlnMAVgB2+fwI3aK4F34N +uyfZW7Xu65KkPhbMnO0GVGM7X4Lkyjm4ysQ9PIRV3MwMfXH+RBSXlIayLTcYG4gl +XF1a/qnao6nMjyTIyQ== +-----END CERTIFICATE----- +` + t.Run("singlepublickey", func(t *testing.T) { + block, _ := pem.Decode([]byte(Single)) + cert, err := x509.ParseCertificate(block.Bytes) + assert.Equal(t, err, nil) + hash := GenerateCertPublicKeyHash(cert) + hashstr := base64.StdEncoding.EncodeToString(hash) + assert.Equal(t, "xI/4mNm8xF9uDT4vA9G1+aKAaybwNlkRECnN8vGAHTM=", hashstr) + }) +} diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index f1291e81c6e7..2fd9a017dc21 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -1,7 +1,9 @@ package tls import ( + "crypto/rand" "crypto/tls" + "math/big" utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" @@ -10,6 +12,13 @@ import ( //go:generate go run github.com/xtls/xray-core/common/errors/errorgen +type Interface interface { + net.Conn + Handshake() error + VerifyHostname(host string) error + NegotiatedProtocol() (name string, mutual bool) +} + var _ buf.Writer = (*Conn)(nil) type Conn struct { @@ -105,22 +114,106 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne func copyConfig(c *tls.Config) *utls.Config { return &utls.Config{ - RootCAs: c.RootCAs, - ServerName: c.ServerName, - InsecureSkipVerify: c.InsecureSkipVerify, + RootCAs: c.RootCAs, + ServerName: c.ServerName, + InsecureSkipVerify: c.InsecureSkipVerify, + VerifyPeerCertificate: c.VerifyPeerCertificate, } } -var Fingerprints = map[string]*utls.ClientHelloID{ +func init() { + bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ModernFingerprints)))) + stopAt := int(bigInt.Int64()) + i := 0 + for _, v := range ModernFingerprints { + if i == stopAt { + PresetFingerprints["random"] = v + break + } + i++ + } + weights := utls.DefaultWeights + weights.TLSVersMax_Set_VersionTLS13 = 1 + weights.FirstKeyShare_Set_CurveP256 = 0 + randomized := utls.HelloRandomized + randomized.Seed, _ = utls.NewPRNGSeed() + randomized.Weights = &weights + PresetFingerprints["randomized"] = &randomized +} + +func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) { + if name == "" { + return + } + if fingerprint = PresetFingerprints[name]; fingerprint != nil { + return + } + if fingerprint = ModernFingerprints[name]; fingerprint != nil { + return + } + if fingerprint = OtherFingerprints[name]; fingerprint != nil { + return + } + return +} + +var PresetFingerprints = map[string]*utls.ClientHelloID{ + // Recommended preset options in GUI clients "chrome": &utls.HelloChrome_Auto, "firefox": &utls.HelloFirefox_Auto, - "safari": &utls.HelloIOS_Auto, - "randomized": &utls.HelloRandomized, + "safari": &utls.HelloSafari_Auto, + "ios": &utls.HelloIOS_Auto, + "android": &utls.HelloAndroid_11_OkHttp, + "edge": &utls.HelloEdge_Auto, + "360": &utls.Hello360_Auto, + "qq": &utls.HelloQQ_Auto, + "random": nil, + "randomized": nil, } -type Interface interface { - net.Conn - Handshake() error - VerifyHostname(host string) error - NegotiatedProtocol() (name string, mutual bool) +var ModernFingerprints = map[string]*utls.ClientHelloID{ + // One of these will be chosen as `random` at startup + "hellofirefox_99": &utls.HelloFirefox_99, + "hellofirefox_102": &utls.HelloFirefox_102, + "hellofirefox_105": &utls.HelloFirefox_105, + "hellochrome_83": &utls.HelloChrome_83, + "hellochrome_87": &utls.HelloChrome_87, + "hellochrome_96": &utls.HelloChrome_96, + "hellochrome_100": &utls.HelloChrome_100, + "hellochrome_102": &utls.HelloChrome_102, + "hellochrome_106_shuffle": &utls.HelloChrome_106_Shuffle, + "helloios_13": &utls.HelloIOS_13, + "helloios_14": &utls.HelloIOS_14, + "helloedge_85": &utls.HelloEdge_85, + "helloedge_106": &utls.HelloEdge_106, + "hellosafari_16_0": &utls.HelloSafari_16_0, + "hello360_11_0": &utls.Hello360_11_0, + "helloqq_11_1": &utls.HelloQQ_11_1, +} + +var OtherFingerprints = map[string]*utls.ClientHelloID{ + // Golang, randomized, auto, and fingerprints that are too old + "hellogolang": &utls.HelloGolang, + "hellorandomized": &utls.HelloRandomized, + "hellorandomizedalpn": &utls.HelloRandomizedALPN, + "hellorandomizednoalpn": &utls.HelloRandomizedNoALPN, + "hellofirefox_auto": &utls.HelloFirefox_Auto, + "hellofirefox_55": &utls.HelloFirefox_55, + "hellofirefox_56": &utls.HelloFirefox_56, + "hellofirefox_63": &utls.HelloFirefox_63, + "hellofirefox_65": &utls.HelloFirefox_65, + "hellochrome_auto": &utls.HelloChrome_Auto, + "hellochrome_58": &utls.HelloChrome_58, + "hellochrome_62": &utls.HelloChrome_62, + "hellochrome_70": &utls.HelloChrome_70, + "hellochrome_72": &utls.HelloChrome_72, + "helloios_auto": &utls.HelloIOS_Auto, + "helloios_11_1": &utls.HelloIOS_11_1, + "helloios_12_1": &utls.HelloIOS_12_1, + "helloandroid_11_okhttp": &utls.HelloAndroid_11_OkHttp, + "helloedge_auto": &utls.HelloEdge_Auto, + "hellosafari_auto": &utls.HelloSafari_Auto, + "hello360_auto": &utls.Hello360_Auto, + "hello360_7_5": &utls.Hello360_7_5, + "helloqq_auto": &utls.HelloQQ_Auto, } diff --git a/transport/internet/udp/config.pb.go b/transport/internet/udp/config.pb.go index d58b8b69d851..b3921e46f61e 100644 --- a/transport/internet/udp/config.pb.go +++ b/transport/internet/udp/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/udp/config.proto package udp diff --git a/transport/internet/udp/dispatcher.go b/transport/internet/udp/dispatcher.go index dda26e6eefed..32c8c8ac6a6a 100644 --- a/transport/internet/udp/dispatcher.go +++ b/transport/internet/udp/dispatcher.go @@ -31,6 +31,7 @@ type Dispatcher struct { conns map[net.Destination]*connEntry dispatcher routing.Dispatcher callback ResponseCallback + callClose func() error } func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Dispatcher { @@ -51,12 +52,12 @@ func (v *Dispatcher) RemoveRay(dest net.Destination) { } } -func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) *connEntry { +func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*connEntry, error) { v.Lock() defer v.Unlock() if entry, found := v.conns[dest]; found { - return entry + return entry, nil } newError("establishing new connection for ", dest).WriteToLog() @@ -67,22 +68,31 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) *c v.RemoveRay(dest) } timer := signal.CancelAfterInactivity(ctx, removeRay, time.Minute) - link, _ := v.dispatcher.Dispatch(ctx, dest) + + link, err := v.dispatcher.Dispatch(ctx, dest) + if err != nil { + return nil, newError("failed to dispatch request to ", dest).Base(err) + } + entry := &connEntry{ link: link, timer: timer, cancel: removeRay, } v.conns[dest] = entry - go handleInput(ctx, entry, dest, v.callback) - return entry + go handleInput(ctx, entry, dest, v.callback, v.callClose) + return entry, nil } func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination, payload *buf.Buffer) { // TODO: Add user to destString newError("dispatch request to: ", destination).AtDebug().WriteToLog(session.ExportIDToError(ctx)) - conn := v.getInboundRay(ctx, destination) + conn, err := v.getInboundRay(ctx, destination) + if err != nil { + newError("failed to get inbound").Base(err).WriteToLog(session.ExportIDToError(ctx)) + return + } outputStream := conn.link.Writer if outputStream != nil { if err := outputStream.WriteMultiBuffer(buf.MultiBuffer{payload}); err != nil { @@ -93,8 +103,13 @@ func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination, } } -func handleInput(ctx context.Context, conn *connEntry, dest net.Destination, callback ResponseCallback) { - defer conn.cancel() +func handleInput(ctx context.Context, conn *connEntry, dest net.Destination, callback ResponseCallback, callClose func() error) { + defer func() { + conn.cancel() + if callClose != nil { + callClose() + } + }() input := conn.link.Reader timer := conn.timer @@ -127,15 +142,22 @@ type dispatcherConn struct { dispatcher *Dispatcher cache chan *udp.Packet done *done.Instance + ctx context.Context } func DialDispatcher(ctx context.Context, dispatcher routing.Dispatcher) (net.PacketConn, error) { c := &dispatcherConn{ cache: make(chan *udp.Packet, 16), done: done.New(), + ctx: ctx, } - d := NewDispatcher(dispatcher, c.callback) + d := &Dispatcher{ + conns: make(map[net.Destination]*connEntry), + dispatcher: dispatcher, + callback: c.callback, + callClose: c.Close, + } c.dispatcher = d return c, nil } @@ -153,16 +175,22 @@ func (c *dispatcherConn) callback(ctx context.Context, packet *udp.Packet) { } func (c *dispatcherConn) ReadFrom(p []byte) (int, net.Addr, error) { + var packet *udp.Packet +s: select { case <-c.done.Wait(): - return 0, nil, io.EOF - case packet := <-c.cache: - n := copy(p, packet.Payload.Bytes()) - return n, &net.UDPAddr{ - IP: packet.Source.Address.IP(), - Port: int(packet.Source.Port), - }, nil + select { + case packet = <-c.cache: + break s + default: + return 0, nil, io.EOF + } + case packet = <-c.cache: } + return copy(p, packet.Payload.Bytes()), &net.UDPAddr{ + IP: packet.Source.Address.IP(), + Port: int(packet.Source.Port), + }, nil } func (c *dispatcherConn) WriteTo(p []byte, addr net.Addr) (int, error) { @@ -171,8 +199,7 @@ func (c *dispatcherConn) WriteTo(p []byte, addr net.Addr) (int, error) { n := copy(raw, p) buffer.Resize(0, int32(n)) - ctx := context.Background() - c.dispatcher.Dispatch(ctx, net.DestinationFromAddr(addr), buffer) + c.dispatcher.Dispatch(c.ctx, net.DestinationFromAddr(addr), buffer) return n, nil } diff --git a/transport/internet/websocket/config.pb.go b/transport/internet/websocket/config.pb.go index 6e60f5a811e7..7880a1a5b74d 100644 --- a/transport/internet/websocket/config.pb.go +++ b/transport/internet/websocket/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 +// protoc-gen-go v1.30.0 +// protoc v4.23.1 // source: transport/internet/websocket/config.proto package websocket diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index a8f712647adc..5017cb5006be 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -86,7 +86,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in protocol = "wss" tlsConfig := config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) dialer.TLSClientConfig = tlsConfig - if fingerprint, exists := tls.Fingerprints[config.Fingerprint]; exists { + if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { dialer.NetDialTLSContext = func(_ context.Context, _, addr string) (gonet.Conn, error) { // Like the NetDial in the dialer pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) diff --git a/transport/internet/websocket/hub.go b/transport/internet/websocket/hub.go index c0cf34467e12..7951b1f4c252 100644 --- a/transport/internet/websocket/hub.go +++ b/transport/internet/websocket/hub.go @@ -75,7 +75,6 @@ type Listener struct { listener net.Listener config *Config addConn internet.ConnHandler - locker *internet.FileLocker // for unix domain socket } func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) { @@ -101,10 +100,6 @@ func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSet return nil, newError("failed to listen unix domain socket(for WS) on ", address).Base(err) } newError("listening unix domain socket(for WS) on ", address).WriteToLog(session.ExportIDToError(ctx)) - locker := ctx.Value(address.Domain()) - if locker != nil { - l.locker = locker.(*internet.FileLocker) - } } else { // tcp listener, err = internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), @@ -153,9 +148,6 @@ func (ln *Listener) Addr() net.Addr { // Close implements net.Listener.Close(). func (ln *Listener) Close() error { - if ln.locker != nil { - ln.locker.Release() - } return ln.listener.Close() } diff --git a/transport/internet/websocket/ws.go b/transport/internet/websocket/ws.go index 553871006341..85c5ffb014ed 100644 --- a/transport/internet/websocket/ws.go +++ b/transport/internet/websocket/ws.go @@ -1,4 +1,5 @@ -/*Package websocket implements WebSocket transport +/* +Package websocket implements WebSocket transport WebSocket transport implements an HTTP(S) compliable, surveillance proof transport method with plausible deniability. */ diff --git a/transport/internet/xtls/config.go b/transport/internet/xtls/config.go deleted file mode 100644 index c1d93f4c6801..000000000000 --- a/transport/internet/xtls/config.go +++ /dev/null @@ -1,377 +0,0 @@ -package xtls - -import ( - "crypto/hmac" - "crypto/x509" - "encoding/base64" - "strings" - "sync" - "time" - - xtls "github.com/xtls/go" - "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/ocsp" - "github.com/xtls/xray-core/common/platform/filesystem" - "github.com/xtls/xray-core/common/protocol/tls/cert" - "github.com/xtls/xray-core/transport/internet" - "github.com/xtls/xray-core/transport/internet/tls" -) - -var globalSessionCache = xtls.NewLRUClientSessionCache(128) - -// ParseCertificate converts a cert.Certificate to Certificate. -func ParseCertificate(c *cert.Certificate) *Certificate { - if c != nil { - certPEM, keyPEM := c.ToPEM() - return &Certificate{ - Certificate: certPEM, - Key: keyPEM, - } - } - return nil -} - -func (c *Config) loadSelfCertPool() (*x509.CertPool, error) { - root := x509.NewCertPool() - for _, cert := range c.Certificate { - if !root.AppendCertsFromPEM(cert.Certificate) { - return nil, newError("failed to append cert").AtWarning() - } - } - return root, nil -} - -// BuildCertificates builds a list of TLS certificates from proto definition. -func (c *Config) BuildCertificates() []*xtls.Certificate { - certs := make([]*xtls.Certificate, 0, len(c.Certificate)) - for _, entry := range c.Certificate { - if entry.Usage != Certificate_ENCIPHERMENT { - continue - } - keyPair, err := xtls.X509KeyPair(entry.Certificate, entry.Key) - if err != nil { - newError("ignoring invalid X509 key pair").Base(err).AtWarning().WriteToLog() - continue - } - keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0]) - if err != nil { - newError("ignoring invalid certificate").Base(err).AtWarning().WriteToLog() - continue - } - certs = append(certs, &keyPair) - if !entry.OneTimeLoading { - var isOcspstapling bool - hotReloadInterval := uint64(3600) - if entry.OcspStapling != 0 { - hotReloadInterval = entry.OcspStapling - isOcspstapling = true - } - index := len(certs) - 1 - go func(entry *Certificate, cert *xtls.Certificate, index int) { - t := time.NewTicker(time.Duration(hotReloadInterval) * time.Second) - for { - if entry.CertificatePath != "" && entry.KeyPath != "" { - newCert, err := filesystem.ReadFile(entry.CertificatePath) - if err != nil { - newError("failed to parse certificate").Base(err).AtError().WriteToLog() - <-t.C - continue - } - newKey, err := filesystem.ReadFile(entry.KeyPath) - if err != nil { - newError("failed to parse key").Base(err).AtError().WriteToLog() - <-t.C - continue - } - if string(newCert) != string(entry.Certificate) && string(newKey) != string(entry.Key) { - newKeyPair, err := xtls.X509KeyPair(newCert, newKey) - if err != nil { - newError("ignoring invalid X509 key pair").Base(err).AtError().WriteToLog() - <-t.C - continue - } - if newKeyPair.Leaf, err = x509.ParseCertificate(newKeyPair.Certificate[0]); err != nil { - newError("ignoring invalid certificate").Base(err).AtError().WriteToLog() - <-t.C - continue - } - cert = &newKeyPair - } - } - if isOcspstapling { - if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil { - newError("ignoring invalid OCSP").Base(err).AtWarning().WriteToLog() - } else if string(newOCSPData) != string(cert.OCSPStaple) { - cert.OCSPStaple = newOCSPData - } - } - certs[index] = cert - <-t.C - } - }(entry, certs[index], index) - } - } - return certs -} - -func isCertificateExpired(c *xtls.Certificate) bool { - if c.Leaf == nil && len(c.Certificate) > 0 { - if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil { - c.Leaf = pc - } - } - - // If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate. - return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(-time.Minute)) -} - -func issueCertificate(rawCA *Certificate, domain string) (*xtls.Certificate, error) { - parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key) - if err != nil { - return nil, newError("failed to parse raw certificate").Base(err) - } - newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain)) - if err != nil { - return nil, newError("failed to generate new certificate for ", domain).Base(err) - } - newCertPEM, newKeyPEM := newCert.ToPEM() - cert, err := xtls.X509KeyPair(newCertPEM, newKeyPEM) - return &cert, err -} - -func (c *Config) getCustomCA() []*Certificate { - certs := make([]*Certificate, 0, len(c.Certificate)) - for _, certificate := range c.Certificate { - if certificate.Usage == Certificate_AUTHORITY_ISSUE { - certs = append(certs, certificate) - } - } - return certs -} - -func getGetCertificateFunc(c *xtls.Config, ca []*Certificate) func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { - var access sync.RWMutex - - return func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { - domain := hello.ServerName - certExpired := false - - access.RLock() - certificate, found := c.NameToCertificate[domain] - access.RUnlock() - - if found { - if !isCertificateExpired(certificate) { - return certificate, nil - } - certExpired = true - } - - if certExpired { - newCerts := make([]xtls.Certificate, 0, len(c.Certificates)) - - access.Lock() - for _, certificate := range c.Certificates { - if !isCertificateExpired(&certificate) { - newCerts = append(newCerts, certificate) - } - } - - c.Certificates = newCerts - access.Unlock() - } - - var issuedCertificate *xtls.Certificate - - // Create a new certificate from existing CA if possible - for _, rawCert := range ca { - if rawCert.Usage == Certificate_AUTHORITY_ISSUE { - newCert, err := issueCertificate(rawCert, domain) - if err != nil { - newError("failed to issue new certificate for ", domain).Base(err).WriteToLog() - continue - } - - access.Lock() - c.Certificates = append(c.Certificates, *newCert) - issuedCertificate = &c.Certificates[len(c.Certificates)-1] - access.Unlock() - break - } - } - - if issuedCertificate == nil { - return nil, newError("failed to create a new certificate for ", domain) - } - - access.Lock() - c.BuildNameToCertificate() - access.Unlock() - - return issuedCertificate, nil - } -} - -func getNewGetCertificateFunc(certs []*xtls.Certificate, rejectUnknownSNI bool) func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { - return func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { - if len(certs) == 0 { - return nil, errNoCertificates - } - sni := strings.ToLower(hello.ServerName) - if !rejectUnknownSNI && (len(certs) == 1 || sni == "") { - return certs[0], nil - } - gsni := "*" - if index := strings.IndexByte(sni, '.'); index != -1 { - gsni += sni[index:] - } - for _, keyPair := range certs { - if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni { - return keyPair, nil - } - for _, name := range keyPair.Leaf.DNSNames { - if name == sni || name == gsni { - return keyPair, nil - } - } - } - if rejectUnknownSNI { - return nil, errNoCertificates - } - return certs[0], nil - } -} - -func (c *Config) parseServerName() string { - return c.ServerName -} - -func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - if c.PinnedPeerCertificateChainSha256 != nil { - hashValue := tls.GenerateCertChainHash(rawCerts) - for _, v := range c.PinnedPeerCertificateChainSha256 { - if hmac.Equal(hashValue, v) { - return nil - } - } - return newError("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue)) - } - return nil -} - -// GetXTLSConfig converts this Config into xtls.Config. -func (c *Config) GetXTLSConfig(opts ...Option) *xtls.Config { - root, err := c.getCertPool() - if err != nil { - newError("failed to load system root certificate").AtError().Base(err).WriteToLog() - } - - if c == nil { - return &xtls.Config{ - ClientSessionCache: globalSessionCache, - RootCAs: root, - InsecureSkipVerify: false, - NextProtos: nil, - SessionTicketsDisabled: true, - } - } - - config := &xtls.Config{ - ClientSessionCache: globalSessionCache, - RootCAs: root, - InsecureSkipVerify: c.AllowInsecure, - NextProtos: c.NextProtocol, - SessionTicketsDisabled: !c.EnableSessionResumption, - VerifyPeerCertificate: c.verifyPeerCert, - } - - for _, opt := range opts { - opt(config) - } - - caCerts := c.getCustomCA() - if len(caCerts) > 0 { - config.GetCertificate = getGetCertificateFunc(config, caCerts) - } else { - config.GetCertificate = getNewGetCertificateFunc(c.BuildCertificates(), c.RejectUnknownSni) - } - - if sn := c.parseServerName(); len(sn) > 0 { - config.ServerName = sn - } - - if len(config.NextProtos) == 0 { - config.NextProtos = []string{"h2", "http/1.1"} - } - - switch c.MinVersion { - case "1.0": - config.MinVersion = xtls.VersionTLS10 - case "1.1": - config.MinVersion = xtls.VersionTLS11 - case "1.2": - config.MinVersion = xtls.VersionTLS12 - case "1.3": - config.MinVersion = xtls.VersionTLS13 - } - - switch c.MaxVersion { - case "1.0": - config.MaxVersion = xtls.VersionTLS10 - case "1.1": - config.MaxVersion = xtls.VersionTLS11 - case "1.2": - config.MaxVersion = xtls.VersionTLS12 - case "1.3": - config.MaxVersion = xtls.VersionTLS13 - } - - if len(c.CipherSuites) > 0 { - id := make(map[string]uint16) - for _, s := range xtls.CipherSuites() { - id[s.Name] = s.ID - } - for _, n := range strings.Split(c.CipherSuites, ":") { - if id[n] != 0 { - config.CipherSuites = append(config.CipherSuites, id[n]) - } - } - } - - config.PreferServerCipherSuites = c.PreferServerCipherSuites - - return config -} - -// Option for building XTLS config. -type Option func(*xtls.Config) - -// WithDestination sets the server name in XTLS config. -func WithDestination(dest net.Destination) Option { - return func(config *xtls.Config) { - if dest.Address.Family().IsDomain() && config.ServerName == "" { - config.ServerName = dest.Address.Domain() - } - } -} - -// WithNextProto sets the ALPN values in XTLS config. -func WithNextProto(protocol ...string) Option { - return func(config *xtls.Config) { - if len(config.NextProtos) == 0 { - config.NextProtos = protocol - } - } -} - -// ConfigFromStreamSettings fetches Config from stream settings. Nil if not found. -func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { - if settings == nil { - return nil - } - config, ok := settings.SecuritySettings.(*Config) - if !ok { - return nil - } - return config -} diff --git a/transport/internet/xtls/config.pb.go b/transport/internet/xtls/config.pb.go deleted file mode 100644 index c6a6df53c320..000000000000 --- a/transport/internet/xtls/config.pb.go +++ /dev/null @@ -1,478 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.18.0 -// source: transport/internet/xtls/config.proto - -package xtls - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Certificate_Usage int32 - -const ( - Certificate_ENCIPHERMENT Certificate_Usage = 0 - Certificate_AUTHORITY_VERIFY Certificate_Usage = 1 - Certificate_AUTHORITY_ISSUE Certificate_Usage = 2 -) - -// Enum value maps for Certificate_Usage. -var ( - Certificate_Usage_name = map[int32]string{ - 0: "ENCIPHERMENT", - 1: "AUTHORITY_VERIFY", - 2: "AUTHORITY_ISSUE", - } - Certificate_Usage_value = map[string]int32{ - "ENCIPHERMENT": 0, - "AUTHORITY_VERIFY": 1, - "AUTHORITY_ISSUE": 2, - } -) - -func (x Certificate_Usage) Enum() *Certificate_Usage { - p := new(Certificate_Usage) - *p = x - return p -} - -func (x Certificate_Usage) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Certificate_Usage) Descriptor() protoreflect.EnumDescriptor { - return file_transport_internet_xtls_config_proto_enumTypes[0].Descriptor() -} - -func (Certificate_Usage) Type() protoreflect.EnumType { - return &file_transport_internet_xtls_config_proto_enumTypes[0] -} - -func (x Certificate_Usage) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Certificate_Usage.Descriptor instead. -func (Certificate_Usage) EnumDescriptor() ([]byte, []int) { - return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{0, 0} -} - -type Certificate struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // TLS certificate in x509 format. - Certificate []byte `protobuf:"bytes,1,opt,name=certificate,proto3" json:"certificate,omitempty"` - // TLS key in x509 format. - Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - Usage Certificate_Usage `protobuf:"varint,3,opt,name=usage,proto3,enum=xray.transport.internet.xtls.Certificate_Usage" json:"usage,omitempty"` - OcspStapling uint64 `protobuf:"varint,4,opt,name=ocsp_stapling,json=ocspStapling,proto3" json:"ocsp_stapling,omitempty"` - // TLS certificate path - CertificatePath string `protobuf:"bytes,5,opt,name=certificate_path,json=certificatePath,proto3" json:"certificate_path,omitempty"` - // TLS Key path - KeyPath string `protobuf:"bytes,6,opt,name=key_path,json=keyPath,proto3" json:"key_path,omitempty"` - // If true, one-Time Loading - OneTimeLoading bool `protobuf:"varint,7,opt,name=One_time_loading,json=OneTimeLoading,proto3" json:"One_time_loading,omitempty"` -} - -func (x *Certificate) Reset() { - *x = Certificate{} - if protoimpl.UnsafeEnabled { - mi := &file_transport_internet_xtls_config_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Certificate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Certificate) ProtoMessage() {} - -func (x *Certificate) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_xtls_config_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Certificate.ProtoReflect.Descriptor instead. -func (*Certificate) Descriptor() ([]byte, []int) { - return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{0} -} - -func (x *Certificate) GetCertificate() []byte { - if x != nil { - return x.Certificate - } - return nil -} - -func (x *Certificate) GetKey() []byte { - if x != nil { - return x.Key - } - return nil -} - -func (x *Certificate) GetUsage() Certificate_Usage { - if x != nil { - return x.Usage - } - return Certificate_ENCIPHERMENT -} - -func (x *Certificate) GetOcspStapling() uint64 { - if x != nil { - return x.OcspStapling - } - return 0 -} - -func (x *Certificate) GetCertificatePath() string { - if x != nil { - return x.CertificatePath - } - return "" -} - -func (x *Certificate) GetKeyPath() string { - if x != nil { - return x.KeyPath - } - return "" -} - -func (x *Certificate) GetOneTimeLoading() bool { - if x != nil { - return x.OneTimeLoading - } - return false -} - -type Config struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Whether or not to allow self-signed certificates. - AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` - // List of certificates to be served on server. - Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"` - // Override server name. - ServerName string `protobuf:"bytes,3,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` - // Lists of string as ALPN values. - NextProtocol []string `protobuf:"bytes,4,rep,name=next_protocol,json=nextProtocol,proto3" json:"next_protocol,omitempty"` - // Whether or not to enable session (ticket) resumption. - EnableSessionResumption bool `protobuf:"varint,5,opt,name=enable_session_resumption,json=enableSessionResumption,proto3" json:"enable_session_resumption,omitempty"` - // If true, root certificates on the system will not be loaded for - // verification. - DisableSystemRoot bool `protobuf:"varint,6,opt,name=disable_system_root,json=disableSystemRoot,proto3" json:"disable_system_root,omitempty"` - // The minimum TLS version. - MinVersion string `protobuf:"bytes,7,opt,name=min_version,json=minVersion,proto3" json:"min_version,omitempty"` - // The maximum TLS version. - MaxVersion string `protobuf:"bytes,8,opt,name=max_version,json=maxVersion,proto3" json:"max_version,omitempty"` - // Specify cipher suites, except for TLS 1.3. - CipherSuites string `protobuf:"bytes,9,opt,name=cipher_suites,json=cipherSuites,proto3" json:"cipher_suites,omitempty"` - // Whether the server selects its most preferred ciphersuite. - PreferServerCipherSuites bool `protobuf:"varint,10,opt,name=prefer_server_cipher_suites,json=preferServerCipherSuites,proto3" json:"prefer_server_cipher_suites,omitempty"` - RejectUnknownSni bool `protobuf:"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3" json:"reject_unknown_sni,omitempty"` - // @Document A pinned certificate chain sha256 hash. - // @Document If the server's hash does not match this value, the connection will be aborted. - // @Document This value replace allow_insecure. - // @Critical - PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"` -} - -func (x *Config) Reset() { - *x = Config{} - if protoimpl.UnsafeEnabled { - mi := &file_transport_internet_xtls_config_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Config) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Config) ProtoMessage() {} - -func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_transport_internet_xtls_config_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Config.ProtoReflect.Descriptor instead. -func (*Config) Descriptor() ([]byte, []int) { - return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{1} -} - -func (x *Config) GetAllowInsecure() bool { - if x != nil { - return x.AllowInsecure - } - return false -} - -func (x *Config) GetCertificate() []*Certificate { - if x != nil { - return x.Certificate - } - return nil -} - -func (x *Config) GetServerName() string { - if x != nil { - return x.ServerName - } - return "" -} - -func (x *Config) GetNextProtocol() []string { - if x != nil { - return x.NextProtocol - } - return nil -} - -func (x *Config) GetEnableSessionResumption() bool { - if x != nil { - return x.EnableSessionResumption - } - return false -} - -func (x *Config) GetDisableSystemRoot() bool { - if x != nil { - return x.DisableSystemRoot - } - return false -} - -func (x *Config) GetMinVersion() string { - if x != nil { - return x.MinVersion - } - return "" -} - -func (x *Config) GetMaxVersion() string { - if x != nil { - return x.MaxVersion - } - return "" -} - -func (x *Config) GetCipherSuites() string { - if x != nil { - return x.CipherSuites - } - return "" -} - -func (x *Config) GetPreferServerCipherSuites() bool { - if x != nil { - return x.PreferServerCipherSuites - } - return false -} - -func (x *Config) GetRejectUnknownSni() bool { - if x != nil { - return x.RejectUnknownSni - } - return false -} - -func (x *Config) GetPinnedPeerCertificateChainSha256() [][]byte { - if x != nil { - return x.PinnedPeerCertificateChainSha256 - } - return nil -} - -var File_transport_internet_xtls_config_proto protoreflect.FileDescriptor - -var file_transport_internet_xtls_config_proto_rawDesc = []byte{ - 0x0a, 0x24, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, - 0x78, 0x74, 0x6c, 0x73, 0x22, 0xe3, 0x02, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x05, 0x75, 0x73, 0x61, 0x67, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, - 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x6f, 0x63, 0x73, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x70, 0x6c, 0x69, 0x6e, 0x67, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6f, 0x63, 0x73, 0x70, 0x53, 0x74, 0x61, 0x70, - 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, - 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x28, 0x0a, 0x10, 0x4f, 0x6e, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x61, - 0x64, 0x69, 0x6e, 0x67, 0x22, 0x44, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, - 0x0c, 0x45, 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, - 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, - 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, - 0x54, 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xd2, 0x04, 0x0a, 0x06, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, - 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4b, 0x0a, 0x0b, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, - 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, - 0x3a, 0x0a, 0x19, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x17, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x6f, - 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, - 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, - 0x69, 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, - 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, - 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, - 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, - 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x6e, 0x6b, 0x6e, - 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x6e, 0x69, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, - 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x6e, 0x69, 0x12, - 0x4e, 0x0a, 0x24, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x20, 0x70, - 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x42, - 0x76, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, - 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, - 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x65, 0x74, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1c, 0x58, 0x72, 0x61, 0x79, 0x2e, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x65, 0x74, 0x2e, 0x58, 0x74, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_transport_internet_xtls_config_proto_rawDescOnce sync.Once - file_transport_internet_xtls_config_proto_rawDescData = file_transport_internet_xtls_config_proto_rawDesc -) - -func file_transport_internet_xtls_config_proto_rawDescGZIP() []byte { - file_transport_internet_xtls_config_proto_rawDescOnce.Do(func() { - file_transport_internet_xtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_xtls_config_proto_rawDescData) - }) - return file_transport_internet_xtls_config_proto_rawDescData -} - -var file_transport_internet_xtls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_transport_internet_xtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_transport_internet_xtls_config_proto_goTypes = []interface{}{ - (Certificate_Usage)(0), // 0: xray.transport.internet.xtls.Certificate.Usage - (*Certificate)(nil), // 1: xray.transport.internet.xtls.Certificate - (*Config)(nil), // 2: xray.transport.internet.xtls.Config -} -var file_transport_internet_xtls_config_proto_depIdxs = []int32{ - 0, // 0: xray.transport.internet.xtls.Certificate.usage:type_name -> xray.transport.internet.xtls.Certificate.Usage - 1, // 1: xray.transport.internet.xtls.Config.certificate:type_name -> xray.transport.internet.xtls.Certificate - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name -} - -func init() { file_transport_internet_xtls_config_proto_init() } -func file_transport_internet_xtls_config_proto_init() { - if File_transport_internet_xtls_config_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_transport_internet_xtls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Certificate); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_transport_internet_xtls_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_transport_internet_xtls_config_proto_rawDesc, - NumEnums: 1, - NumMessages: 2, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_transport_internet_xtls_config_proto_goTypes, - DependencyIndexes: file_transport_internet_xtls_config_proto_depIdxs, - EnumInfos: file_transport_internet_xtls_config_proto_enumTypes, - MessageInfos: file_transport_internet_xtls_config_proto_msgTypes, - }.Build() - File_transport_internet_xtls_config_proto = out.File - file_transport_internet_xtls_config_proto_rawDesc = nil - file_transport_internet_xtls_config_proto_goTypes = nil - file_transport_internet_xtls_config_proto_depIdxs = nil -} diff --git a/transport/internet/xtls/config.proto b/transport/internet/xtls/config.proto deleted file mode 100644 index e2e57e558c80..000000000000 --- a/transport/internet/xtls/config.proto +++ /dev/null @@ -1,76 +0,0 @@ -syntax = "proto3"; - -package xray.transport.internet.xtls; -option csharp_namespace = "Xray.Transport.Internet.Xtls"; -option go_package = "github.com/xtls/xray-core/transport/internet/xtls"; -option java_package = "com.xray.transport.internet.xtls"; -option java_multiple_files = true; - -message Certificate { - // TLS certificate in x509 format. - bytes certificate = 1; - - // TLS key in x509 format. - bytes key = 2; - - enum Usage { - ENCIPHERMENT = 0; - AUTHORITY_VERIFY = 1; - AUTHORITY_ISSUE = 2; - } - - Usage usage = 3; - - uint64 ocsp_stapling = 4; - - // TLS certificate path - string certificate_path = 5; - - // TLS Key path - string key_path = 6; - - // If true, one-Time Loading - bool One_time_loading = 7; -} - -message Config { - // Whether or not to allow self-signed certificates. - bool allow_insecure = 1; - - // List of certificates to be served on server. - repeated Certificate certificate = 2; - - // Override server name. - string server_name = 3; - - // Lists of string as ALPN values. - repeated string next_protocol = 4; - - // Whether or not to enable session (ticket) resumption. - bool enable_session_resumption = 5; - - // If true, root certificates on the system will not be loaded for - // verification. - bool disable_system_root = 6; - - // The minimum TLS version. - string min_version = 7; - - // The maximum TLS version. - string max_version = 8; - - // Specify cipher suites, except for TLS 1.3. - string cipher_suites = 9; - - // Whether the server selects its most preferred ciphersuite. - bool prefer_server_cipher_suites = 10; - - bool reject_unknown_sni = 12; - - /* @Document A pinned certificate chain sha256 hash. - @Document If the server's hash does not match this value, the connection will be aborted. - @Document This value replace allow_insecure. - @Critical - */ - repeated bytes pinned_peer_certificate_chain_sha256 = 13; -} diff --git a/transport/internet/xtls/config_other.go b/transport/internet/xtls/config_other.go deleted file mode 100644 index a429cf37c09c..000000000000 --- a/transport/internet/xtls/config_other.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build !windows -// +build !windows - -package xtls - -import ( - "crypto/x509" - "sync" -) - -type rootCertsCache struct { - sync.Mutex - pool *x509.CertPool -} - -func (c *rootCertsCache) load() (*x509.CertPool, error) { - c.Lock() - defer c.Unlock() - - if c.pool != nil { - return c.pool, nil - } - - pool, err := x509.SystemCertPool() - if err != nil { - return nil, err - } - c.pool = pool - return pool, nil -} - -var rootCerts rootCertsCache - -func (c *Config) getCertPool() (*x509.CertPool, error) { - if c.DisableSystemRoot { - return c.loadSelfCertPool() - } - - if len(c.Certificate) == 0 { - return rootCerts.load() - } - - pool, err := x509.SystemCertPool() - if err != nil { - return nil, newError("system root").AtWarning().Base(err) - } - for _, cert := range c.Certificate { - if !pool.AppendCertsFromPEM(cert.Certificate) { - return nil, newError("append cert to root").AtWarning().Base(err) - } - } - return pool, err -} diff --git a/transport/internet/xtls/config_test.go b/transport/internet/xtls/config_test.go deleted file mode 100644 index bd7fbf1d1276..000000000000 --- a/transport/internet/xtls/config_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package xtls_test - -import ( - "crypto/x509" - "testing" - "time" - - xtls "github.com/xtls/go" - "github.com/xtls/xray-core/common" - "github.com/xtls/xray-core/common/protocol/tls/cert" - . "github.com/xtls/xray-core/transport/internet/xtls" -) - -func TestCertificateIssuing(t *testing.T) { - certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) - certificate.Usage = Certificate_AUTHORITY_ISSUE - - c := &Config{ - Certificate: []*Certificate{ - certificate, - }, - } - - xtlsConfig := c.GetXTLSConfig() - xrayCert, err := xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ - ServerName: "www.example.com", - }) - common.Must(err) - - x509Cert, err := x509.ParseCertificate(xrayCert.Certificate[0]) - common.Must(err) - if !x509Cert.NotAfter.After(time.Now()) { - t.Error("NotAfter: ", x509Cert.NotAfter) - } -} - -func TestExpiredCertificate(t *testing.T) { - caCert := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign)) - expiredCert := cert.MustGenerate(caCert, cert.NotAfter(time.Now().Add(time.Minute*-2)), cert.CommonName("www.example.com"), cert.DNSNames("www.example.com")) - - certificate := ParseCertificate(caCert) - certificate.Usage = Certificate_AUTHORITY_ISSUE - - certificate2 := ParseCertificate(expiredCert) - - c := &Config{ - Certificate: []*Certificate{ - certificate, - certificate2, - }, - } - - xtlsConfig := c.GetXTLSConfig() - xrayCert, err := xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ - ServerName: "www.example.com", - }) - common.Must(err) - - x509Cert, err := x509.ParseCertificate(xrayCert.Certificate[0]) - common.Must(err) - if !x509Cert.NotAfter.After(time.Now()) { - t.Error("NotAfter: ", x509Cert.NotAfter) - } -} - -func TestInsecureCertificates(t *testing.T) { - c := &Config{} - - xtlsConfig := c.GetXTLSConfig() - if len(xtlsConfig.CipherSuites) > 0 { - t.Fatal("Unexpected tls cipher suites list: ", xtlsConfig.CipherSuites) - } -} - -func BenchmarkCertificateIssuing(b *testing.B) { - certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) - certificate.Usage = Certificate_AUTHORITY_ISSUE - - c := &Config{ - Certificate: []*Certificate{ - certificate, - }, - } - - xtlsConfig := c.GetXTLSConfig() - lenCerts := len(xtlsConfig.Certificates) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _, _ = xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ - ServerName: "www.example.com", - }) - delete(xtlsConfig.NameToCertificate, "www.example.com") - xtlsConfig.Certificates = xtlsConfig.Certificates[:lenCerts] - } -} diff --git a/transport/internet/xtls/config_windows.go b/transport/internet/xtls/config_windows.go deleted file mode 100644 index 68f82b409c95..000000000000 --- a/transport/internet/xtls/config_windows.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build windows -// +build windows - -package xtls - -import "crypto/x509" - -func (c *Config) getCertPool() (*x509.CertPool, error) { - if c.DisableSystemRoot { - return c.loadSelfCertPool() - } - - return nil, nil -} diff --git a/transport/internet/xtls/unsafe.go b/transport/internet/xtls/unsafe.go deleted file mode 100644 index 96c89416c441..000000000000 --- a/transport/internet/xtls/unsafe.go +++ /dev/null @@ -1,6 +0,0 @@ -package xtls - -import _ "unsafe" - -//go:linkname errNoCertificates github.com/xtls/go.errNoCertificates -var errNoCertificates error diff --git a/transport/internet/xtls/xtls.go b/transport/internet/xtls/xtls.go deleted file mode 100644 index 10e678de842e..000000000000 --- a/transport/internet/xtls/xtls.go +++ /dev/null @@ -1,36 +0,0 @@ -package xtls - -import ( - xtls "github.com/xtls/go" - - "github.com/xtls/xray-core/common/net" -) - -//go:generate go run github.com/xtls/xray-core/common/errors/errorgen - -type Conn struct { - *xtls.Conn -} - -func (c *Conn) HandshakeAddress() net.Address { - if err := c.Handshake(); err != nil { - return nil - } - state := c.ConnectionState() - if state.ServerName == "" { - return nil - } - return net.ParseAddress(state.ServerName) -} - -// Client initiates a XTLS client handshake on the given connection. -func Client(c net.Conn, config *xtls.Config) net.Conn { - xtlsConn := xtls.Client(c, config) - return &Conn{Conn: xtlsConn} -} - -// Server initiates a XTLS server handshake on the given connection. -func Server(c net.Conn, config *xtls.Config) net.Conn { - xtlsConn := xtls.Server(c, config) - return &Conn{Conn: xtlsConn} -} diff --git a/transport/pipe/impl.go b/transport/pipe/impl.go index 14a18e6319d4..dbdb050ef368 100644 --- a/transport/pipe/impl.go +++ b/transport/pipe/impl.go @@ -24,7 +24,6 @@ const ( type pipeOption struct { limit int32 // maximum buffer size in bytes discardOverflow bool - onTransmission func(buffer buf.MultiBuffer) buf.MultiBuffer } func (o *pipeOption) isFull(curSize int32) bool { @@ -37,6 +36,7 @@ type pipe struct { readSignal *signal.Notifier writeSignal *signal.Notifier done *done.Instance + errChan chan error option pipeOption state state } @@ -92,6 +92,8 @@ func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) { select { case <-p.readSignal.Wait(): case <-p.done.Wait(): + case err = <-p.errChan: + return nil, err } } } @@ -138,10 +140,6 @@ func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error { return nil } - if p.option.onTransmission != nil { - mb = p.option.onTransmission(mb) - } - for { err := p.writeMultiBufferInternal(mb) if err == nil { diff --git a/transport/pipe/pipe.go b/transport/pipe/pipe.go index 0b22c2db8e66..f4b78303607b 100644 --- a/transport/pipe/pipe.go +++ b/transport/pipe/pipe.go @@ -3,7 +3,6 @@ package pipe import ( "context" - "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/features/policy" @@ -26,12 +25,6 @@ func WithSizeLimit(limit int32) Option { } } -func OnTransmission(hook func(mb buf.MultiBuffer) buf.MultiBuffer) Option { - return func(option *pipeOption) { - option.onTransmission = hook - } -} - // DiscardOverflow returns an Option for Pipe to discard writes if full. func DiscardOverflow() Option { return func(opt *pipeOption) { @@ -59,6 +52,7 @@ func New(opts ...Option) (*Reader, *Writer) { readSignal: signal.NewNotifier(), writeSignal: signal.NewNotifier(), done: done.New(), + errChan: make(chan error, 1), option: pipeOption{ limit: -1, }, diff --git a/transport/pipe/reader.go b/transport/pipe/reader.go index 6673343683b8..79f0ac033f5e 100644 --- a/transport/pipe/reader.go +++ b/transport/pipe/reader.go @@ -25,3 +25,17 @@ func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error func (r *Reader) Interrupt() { r.pipe.Interrupt() } + +// ReturnAnError makes ReadMultiBuffer return an error, only once. +func (r *Reader) ReturnAnError(err error) { + r.pipe.errChan <- err +} + +// Recover catches an error set by ReturnAnError, if exists. +func (r *Reader) Recover() (err error) { + select { + case err = <-r.pipe.errChan: + default: + } + return +}