diff --git a/xray-go/.github/workflows/release-win7.yml b/xray-go/.github/workflows/release-win7.yml index 0d00634..239de1b 100644 --- a/xray-go/.github/workflows/release-win7.yml +++ b/xray-go/.github/workflows/release-win7.yml @@ -1,11 +1,5 @@ name: Build and Release for Windows 7 -# NOTE: This Github Actions file depends on the Makefile. -# Building the correct package requires the correct binaries generated by the Makefile. To -# ensure the correct output, the Makefile must accept the appropriate input and compile the -# correct file with the correct name. If you need to modify this file, please ensure it won't -# disrupt the Makefile. - on: workflow_dispatch: release: @@ -37,6 +31,9 @@ jobs: GOARCH: ${{ matrix.goarch }} CGO_ENABLED: 0 steps: + - name: Checkout codebase + uses: actions/checkout@v4 + - name: Show workflow information run: | _NAME=${{ matrix.assetname }} @@ -46,18 +43,17 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: stable + go-version-file: go.mod check-latest: true - name: Setup patched builder run: | GOSDK=$(go env GOROOT) - curl -O -L https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip rm -r $GOSDK/* + cd $GOSDK + curl -O -L https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip unzip ./go-for-win7-linux-amd64.zip -d $GOSDK - - - name: Checkout codebase - uses: actions/checkout@v4 + rm ./go-for-win7-linux-amd64.zip - name: Get project dependencies run: go mod download @@ -65,8 +61,13 @@ jobs: - name: Build Xray run: | mkdir -p build_assets - make - find . -maxdepth 1 -type f -regex './\(wxray\|xray\).exe' -exec mv {} ./build_assets/ \; + COMMID=$(git describe --always --dirty) + echo 'Building Xray for Windows 7...' + go build -o build_assets/xray.exe -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main + echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs + echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1 + # The line below is for without running conhost.exe version. Commented for not being used. Provided for reference. + # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main - name: Restore Geodat Cache uses: actions/cache/restore@v4 diff --git a/xray-go/.github/workflows/release.yml b/xray-go/.github/workflows/release.yml index 7bc8c51..4de01e4 100644 --- a/xray-go/.github/workflows/release.yml +++ b/xray-go/.github/workflows/release.yml @@ -1,11 +1,5 @@ name: Build and Release -# NOTE: This Github Actions file depends on the Makefile. -# Building the correct package requires the correct binaries generated by the Makefile. To -# ensure the correct output, the Makefile must accept the appropriate input and compile the -# correct file with the correct name. If you need to modify this file, please ensure it won't -# disrupt the Makefile. - on: workflow_dispatch: release: @@ -129,8 +123,22 @@ jobs: - name: Build Xray run: | mkdir -p build_assets - make - find . -maxdepth 1 -type f -regex './\(wxray\|xray\|xray_softfloat\)\(\|.exe\)' -exec mv {} ./build_assets/ \; + COMMID=$(git describe --always --dirty) + if [[ ${GOOS} == 'windows' ]]; then + echo 'Building Xray for Windows...' + go build -o build_assets/xray.exe -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main + echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs + echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1 + # The line below is for without running conhost.exe version. Commented for not being used. Provided for reference. + # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main + else + echo 'Building Xray...' + go build -o build_assets/xray -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main + if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then + echo 'Building soft-float Xray for MIPS/MIPSLE 32-bit...' + GOMIPS=softfloat go build -o build_assets/xray_softfloat -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main + fi + fi - name: Restore Geodat Cache uses: actions/cache/restore@v4 diff --git a/xray-go/.github/workflows/hourly-prepare.yml b/xray-go/.github/workflows/scheduled-assets-update.yml similarity index 69% rename from xray-go/.github/workflows/hourly-prepare.yml rename to xray-go/.github/workflows/scheduled-assets-update.yml index d07c1ee..fb3ca45 100644 --- a/xray-go/.github/workflows/hourly-prepare.yml +++ b/xray-go/.github/workflows/scheduled-assets-update.yml @@ -1,4 +1,4 @@ -name: Timely assets update +name: Scheduled assets update # NOTE: This Github Actions is required by other actions, for preparing other packaging assets in a # routine manner, for example: GeoIP/GeoSite. @@ -8,19 +8,20 @@ name: Timely assets update on: workflow_dispatch: schedule: - # Update assets on every hour (xx:30) - - cron: '30 * * * *' + # Update GeoData on every day (22:30 UTC) + - cron: '30 22 * * *' push: # Prevent triggering update request storm paths: - - ".github/workflows/hourly-prepare.yml" + - ".github/workflows/scheduled-assets-update.yml" pull_request: # Prevent triggering update request storm paths: - - ".github/workflows/hourly-prepare.yml" + - ".github/workflows/scheduled-assets-update.yml" jobs: geodat: + if: github.event.schedule == '30 22 * * *' || github.event_name == 'push'|| github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - name: Restore Geodat Cache @@ -38,18 +39,18 @@ jobs: max_attempts: 60 command: | [ -d 'resources' ] || mkdir resources - LIST=('geoip geoip geoip' 'domain-list-community dlc geosite') + LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite') for i in "${LIST[@]}" do - INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}')) - FILE_NAME="${INFO[2]}.dat" + INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}')) + FILE_NAME="${INFO[3]}.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}')" + HASH="$(curl -sL "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.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 "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..." + curl -L "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.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 diff --git a/xray-go/Makefile b/xray-go/Makefile deleted file mode 100644 index 7653e30..0000000 --- a/xray-go/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -NAME = xray - -VERSION=$(shell git describe --always --dirty) - -# NOTE: This MAKEFILE can be used to build Xray-core locally and in Automatic workflows. It is \ - provided for convenience in automatic building and functions as a part of it. -# NOTE: If you need to modify this file, please be aware that:\ - - This file is not the main Makefile; it only accepts environment variables and builds the \ - binary.\ - - Automatic building expects the correct binaries to be built by this Makefile. If you \ - intend to propose a change to this Makefile, carefully review the file below and ensure \ - that the change will not accidentally break the automatic building:\ - .github/workflows/release.yml \ - Otherwise it is recommended to contact the project maintainers. - -LDFLAGS = -X github.com/xtls/xray-core/core.build=$(VERSION) -s -w -buildid= -PARAMS = -trimpath -ldflags "$(LDFLAGS)" -v -MAIN = ./main -PREFIX ?= $(shell go env GOPATH) -ifeq ($(GOOS),windows) -OUTPUT = $(NAME).exe -ADDITION = go build -o w$(NAME).exe -trimpath -ldflags "-H windowsgui $(LDFLAGS)" -v $(MAIN) -else -OUTPUT = $(NAME) -endif -ifeq ($(shell echo "$(GOARCH)" | grep -Eq "(mips|mipsle)" && echo true),true) # -ADDITION = GOMIPS=softfloat go build -o $(NAME)_softfloat -trimpath -ldflags "$(LDFLAGS)" -v $(MAIN) -endif -.PHONY: clean build - -build: - go build -o $(OUTPUT) $(PARAMS) $(MAIN) - $(ADDITION) - -clean: - go clean -v -i $(PWD) - rm -f xray xray.exe wxray.exe xray_softfloat diff --git a/xray-go/README.md b/xray-go/README.md index 033569b..7b258cf 100644 --- a/xray-go/README.md +++ b/xray-go/README.md @@ -24,7 +24,9 @@ [Project X Channel](https://t.me/projectXtls) -[Project VLESS](https://t.me/projectVless) (non-Chinese) +[Project VLESS](https://t.me/projectVless) (Русский) + +[Project XHTTP](https://t.me/projectXhttp) (Persian) ## Installation @@ -72,6 +74,8 @@ - [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)) +- Asuswrt-Merlin + - [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui) - Windows - [v2rayN](https://github.com/2dust/v2rayN) - [Furious](https://github.com/LorenEteval/Furious) @@ -122,25 +126,27 @@ - [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 +## One-line Compilation ### Windows (PowerShell) ```powershell $env:CGO_ENABLED=0 -go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main +go build -o xray.exe -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main ``` ### Linux / macOS ```bash -CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main +CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main ``` ### Reproducible Releases +Make sure that you are using the same Go version, and remember to set the git commit id (7 bytes): + ```bash -make +CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main ``` ## Stargazers over time diff --git a/xray-go/app/dns/nameserver_doh.go b/xray-go/app/dns/nameserver_doh.go index 177c756..f602d80 100644 --- a/xray-go/app/dns/nameserver_doh.go +++ b/xray-go/app/dns/nameserver_doh.go @@ -54,7 +54,12 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy if err != nil { return nil, err } - link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest) + dnsCtx := toDnsContext(ctx, s.dohURL) + if h2c { + dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance + dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname()) + } + link, err := s.dispatcher.Dispatch(dnsCtx, dest) select { case <-ctx.Done(): return nil, ctx.Err() diff --git a/xray-go/app/metrics/config.pb.go b/xray-go/app/metrics/config.pb.go index d62dcef..b0874b3 100644 --- a/xray-go/app/metrics/config.pb.go +++ b/xray-go/app/metrics/config.pb.go @@ -27,7 +27,8 @@ type Config struct { unknownFields protoimpl.UnknownFields // Tag of the outbound handler that handles metrics http connections. - Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Listen string `protobuf:"bytes,2,opt,name=listen,proto3" json:"listen,omitempty"` } func (x *Config) Reset() { @@ -67,20 +68,28 @@ func (x *Config) GetTag() string { return "" } +func (x *Config) GetListen() string { + if x != nil { + return x.Listen + } + return "" +} + var File_app_metrics_config_proto protoreflect.FileDescriptor var file_app_metrics_config_proto_rawDesc = []byte{ 0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, - 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x1a, 0x0a, 0x06, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x32, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 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, 0x61, 0x70, - 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, - 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 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, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/xray-go/app/metrics/config.proto b/xray-go/app/metrics/config.proto index 5e6880f..0441df0 100644 --- a/xray-go/app/metrics/config.proto +++ b/xray-go/app/metrics/config.proto @@ -10,4 +10,5 @@ option java_multiple_files = true; message Config { // Tag of the outbound handler that handles metrics http connections. string tag = 1; + string listen = 2; } diff --git a/xray-go/app/metrics/metrics.go b/xray-go/app/metrics/metrics.go index 757d673..1dc5b2f 100644 --- a/xray-go/app/metrics/metrics.go +++ b/xray-go/app/metrics/metrics.go @@ -24,12 +24,15 @@ type MetricsHandler struct { statsManager feature_stats.Manager observatory extension.Observatory tag string + listen string + tcpListener net.Listener } // NewMetricsHandler creates a new MetricsHandler based on the given config. func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) { c := &MetricsHandler{ - tag: config.Tag, + tag: config.Tag, + listen: config.Listen, } common.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) { c.statsManager = sm @@ -87,6 +90,23 @@ func (p *MetricsHandler) Type() interface{} { } func (p *MetricsHandler) Start() error { + + // direct listen a port if listen is set + if p.listen != "" { + TCPlistener, err := net.Listen("tcp", p.listen) + if err != nil { + return err + } + p.tcpListener = TCPlistener + errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen) + + go func() { + if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil { + errors.LogErrorInner(context.Background(), err, "failed to start metrics server") + } + }() + } + listener := &OutboundListener{ buffer: make(chan net.Conn, 4), done: done.New(), diff --git a/xray-go/app/observatory/burst/healthping.go b/xray-go/app/observatory/burst/healthping.go index f084260..0d8cab9 100644 --- a/xray-go/app/observatory/burst/healthping.go +++ b/xray-go/app/observatory/burst/healthping.go @@ -66,10 +66,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H settings.Timeout = time.Duration(5) * time.Second } return &HealthPing{ - ctx: ctx, + ctx: ctx, dispatcher: dispatcher, - Settings: settings, - Results: nil, + Settings: settings, + Results: nil, } } diff --git a/xray-go/app/observatory/observer.go b/xray-go/app/observatory/observer.go index 657396f..a6fba3a 100644 --- a/xray-go/app/observatory/observer.go +++ b/xray-go/app/observatory/observer.go @@ -32,7 +32,7 @@ type Observer struct { finished *done.Instance - ohm outbound.Manager + ohm outbound.Manager dispatcher routing.Dispatcher } @@ -226,9 +226,9 @@ func New(ctx context.Context, config *Config) (*Observer, error) { return nil, errors.New("Cannot get depended features").Base(err) } return &Observer{ - config: config, - ctx: ctx, - ohm: outboundManager, + config: config, + ctx: ctx, + ohm: outboundManager, dispatcher: dispatcher, }, nil } diff --git a/xray-go/app/proxyman/inbound/dynamic.go b/xray-go/app/proxyman/inbound/dynamic.go index 0a98b88..9bf4971 100644 --- a/xray-go/app/proxyman/inbound/dynamic.go +++ b/xray-go/app/proxyman/inbound/dynamic.go @@ -23,7 +23,7 @@ type DynamicInboundHandler struct { receiverConfig *proxyman.ReceiverConfig streamSettings *internet.MemoryStreamConfig portMutex sync.Mutex - portsInUse map[net.Port]bool + portsInUse map[net.Port]struct{} workerMutex sync.RWMutex worker []worker lastRefresh time.Time @@ -39,7 +39,7 @@ func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *p tag: tag, proxyConfig: proxyConfig, receiverConfig: receiverConfig, - portsInUse: make(map[net.Port]bool), + portsInUse: make(map[net.Port]struct{}), mux: mux.NewServer(ctx), v: v, ctx: ctx, @@ -84,7 +84,7 @@ func (h *DynamicInboundHandler) allocatePort() net.Port { port := net.Port(allPorts[r]) _, used := h.portsInUse[port] if !used { - h.portsInUse[port] = true + h.portsInUse[port] = struct{}{} return port } } diff --git a/xray-go/app/proxyman/inbound/worker.go b/xray-go/app/proxyman/inbound/worker.go index 6ef8cca..a14a338 100644 --- a/xray-go/app/proxyman/inbound/worker.go +++ b/xray-go/app/proxyman/inbound/worker.go @@ -2,7 +2,6 @@ package inbound import ( "context" - "strings" "sync" "sync/atomic" "time" @@ -464,19 +463,8 @@ func (w *dsWorker) callback(conn stat.Connection) { WriteCounter: w.downlinkCounter, } } - // For most of time, unix obviously have no source addr. But if we leave it empty, it will cause panic. - // So we use gateway as source for log. - // However, there are some special situations where a valid source address might be available. - // Such as the source address parsed from X-Forwarded-For in websocket. - // In that case, we keep it. - var source net.Destination - if !strings.Contains(conn.RemoteAddr().String(), "unix") { - source = net.DestinationFromAddr(conn.RemoteAddr()) - } else { - source = net.UnixDestination(w.address) - } ctx = session.ContextWithInbound(ctx, &session.Inbound{ - Source: source, + Source: net.DestinationFromAddr(conn.RemoteAddr()), Gateway: net.UnixDestination(w.address), Tag: w.tag, Conn: conn, diff --git a/xray-go/app/proxyman/outbound/handler.go b/xray-go/app/proxyman/outbound/handler.go index df7d52a..3819f52 100644 --- a/xray-go/app/proxyman/outbound/handler.go +++ b/xray-go/app/proxyman/outbound/handler.go @@ -273,7 +273,16 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti outbounds := session.OutboundsFromContext(ctx) ob := outbounds[len(outbounds)-1] if h.senderSettings.ViaCidr == "" { - ob.Gateway = h.senderSettings.Via.AsAddress() + if h.senderSettings.Via.AsAddress().Family().IsDomain() && h.senderSettings.Via.AsAddress().Domain() == "origin" { + if inbound := session.InboundFromContext(ctx); inbound != nil { + origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String()) + if err == nil { + ob.Gateway = net.ParseAddress(origin) + } + } + } else { + ob.Gateway = h.senderSettings.Via.AsAddress() + } } else { //Get a random address. ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr) } diff --git a/xray-go/app/stats/command/command.go b/xray-go/app/stats/command/command.go index e4926ba..64e31d3 100644 --- a/xray-go/app/stats/command/command.go +++ b/xray-go/app/stats/command/command.go @@ -60,6 +60,24 @@ func (s *statsServer) GetStatsOnline(ctx context.Context, request *GetStatsReque }, nil } +func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) { + c := s.stats.GetOnlineMap(request.Name) + + if c == nil { + return nil, errors.New(request.Name, " not found.") + } + + ips := make(map[string]int64) + for ip, t := range c.IpTimeMap() { + ips[ip] = t.Unix() + } + + return &GetStatsOnlineIpListResponse{ + Name: request.Name, + Ips: ips, + }, nil +} + func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) { matcher, err := strmatcher.Substr.New(request.Pattern) if err != nil { diff --git a/xray-go/app/stats/command/command.pb.go b/xray-go/app/stats/command/command.pb.go index c956683..062c2d2 100644 --- a/xray-go/app/stats/command/command.pb.go +++ b/xray-go/app/stats/command/command.pb.go @@ -424,6 +424,59 @@ func (x *SysStatsResponse) GetUptime() uint32 { return 0 } +type GetStatsOnlineIpListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Ips map[string]int64 `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` +} + +func (x *GetStatsOnlineIpListResponse) Reset() { + *x = GetStatsOnlineIpListResponse{} + mi := &file_app_stats_command_command_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetStatsOnlineIpListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStatsOnlineIpListResponse) ProtoMessage() {} + +func (x *GetStatsOnlineIpListResponse) ProtoReflect() protoreflect.Message { + mi := &file_app_stats_command_command_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStatsOnlineIpListResponse.ProtoReflect.Descriptor instead. +func (*GetStatsOnlineIpListResponse) Descriptor() ([]byte, []int) { + return file_app_stats_command_command_proto_rawDescGZIP(), []int{7} +} + +func (x *GetStatsOnlineIpListResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetStatsOnlineIpListResponse) GetIps() map[string]int64 { + if x != nil { + return x.Ips + } + return nil +} + type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -432,7 +485,7 @@ type Config struct { func (x *Config) Reset() { *x = Config{} - mi := &file_app_stats_command_command_proto_msgTypes[7] + mi := &file_app_stats_command_command_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -444,7 +497,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_app_stats_command_command_proto_msgTypes[7] + mi := &file_app_stats_command_command_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -457,7 +510,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_app_stats_command_command_proto_rawDescGZIP(), []int{7} + return file_app_stats_command_command_proto_rawDescGZIP(), []int{8} } var File_app_stats_command_command_proto protoreflect.FileDescriptor @@ -506,40 +559,60 @@ var file_app_stats_command_command_proto_rawDesc = []byte{ 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, - 0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xa1, 0x03, - 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, - 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, + 0x69, 0x6d, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4f, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x70, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x69, 0x70, 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x49, 0x70, 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, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x9a, 0x04, 0x0a, 0x0c, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x08, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, + 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, - 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, - 0x65, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, - 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, - 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, - 0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, - 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, - 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, - 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x42, 0x64, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, - 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, - 0x01, 0x5a, 0x2b, 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, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, - 0x16, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x77, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, + 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, + 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x34, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x64, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2b, 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, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x16, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -554,33 +627,38 @@ func file_app_stats_command_command_proto_rawDescGZIP() []byte { return file_app_stats_command_command_proto_rawDescData } -var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_app_stats_command_command_proto_goTypes = []any{ - (*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest - (*Stat)(nil), // 1: xray.app.stats.command.Stat - (*GetStatsResponse)(nil), // 2: xray.app.stats.command.GetStatsResponse - (*QueryStatsRequest)(nil), // 3: xray.app.stats.command.QueryStatsRequest - (*QueryStatsResponse)(nil), // 4: xray.app.stats.command.QueryStatsResponse - (*SysStatsRequest)(nil), // 5: xray.app.stats.command.SysStatsRequest - (*SysStatsResponse)(nil), // 6: xray.app.stats.command.SysStatsResponse - (*Config)(nil), // 7: xray.app.stats.command.Config + (*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest + (*Stat)(nil), // 1: xray.app.stats.command.Stat + (*GetStatsResponse)(nil), // 2: xray.app.stats.command.GetStatsResponse + (*QueryStatsRequest)(nil), // 3: xray.app.stats.command.QueryStatsRequest + (*QueryStatsResponse)(nil), // 4: xray.app.stats.command.QueryStatsResponse + (*SysStatsRequest)(nil), // 5: xray.app.stats.command.SysStatsRequest + (*SysStatsResponse)(nil), // 6: xray.app.stats.command.SysStatsResponse + (*GetStatsOnlineIpListResponse)(nil), // 7: xray.app.stats.command.GetStatsOnlineIpListResponse + (*Config)(nil), // 8: xray.app.stats.command.Config + nil, // 9: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry } var file_app_stats_command_command_proto_depIdxs = []int32{ 1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat 1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat - 0, // 2: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest - 0, // 3: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest - 3, // 4: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest - 5, // 5: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest - 2, // 6: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse - 2, // 7: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse - 4, // 8: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse - 6, // 9: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse - 6, // [6:10] is the sub-list for method output_type - 2, // [2:6] 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 + 9, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry + 0, // 3: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest + 0, // 4: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest + 3, // 5: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest + 5, // 6: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest + 0, // 7: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest + 2, // 8: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse + 2, // 9: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse + 4, // 10: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse + 6, // 11: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse + 7, // 12: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse + 8, // [8:13] is the sub-list for method output_type + 3, // [3:8] 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 } func init() { file_app_stats_command_command_proto_init() } @@ -594,7 +672,7 @@ func file_app_stats_command_command_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_stats_command_command_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/xray-go/app/stats/command/command.proto b/xray-go/app/stats/command/command.proto index 1d2ed86..58ed737 100644 --- a/xray-go/app/stats/command/command.proto +++ b/xray-go/app/stats/command/command.proto @@ -46,11 +46,17 @@ message SysStatsResponse { uint32 Uptime = 10; } +message GetStatsOnlineIpListResponse { + string name = 1; + map ips = 2; +} + service StatsService { rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {} rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} + rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {} } message Config {} diff --git a/xray-go/app/stats/command/command_grpc.pb.go b/xray-go/app/stats/command/command_grpc.pb.go index d0bdc60..6f72ead 100644 --- a/xray-go/app/stats/command/command_grpc.pb.go +++ b/xray-go/app/stats/command/command_grpc.pb.go @@ -19,10 +19,11 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - StatsService_GetStats_FullMethodName = "/xray.app.stats.command.StatsService/GetStats" - StatsService_GetStatsOnline_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnline" - StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats" - StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats" + StatsService_GetStats_FullMethodName = "/xray.app.stats.command.StatsService/GetStats" + StatsService_GetStatsOnline_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnline" + StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats" + StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats" + StatsService_GetStatsOnlineIpList_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnlineIpList" ) // StatsServiceClient is the client API for StatsService service. @@ -33,6 +34,7 @@ type StatsServiceClient interface { GetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) + GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error) } type statsServiceClient struct { @@ -83,6 +85,16 @@ func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsReques return out, nil } +func (c *statsServiceClient) GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetStatsOnlineIpListResponse) + err := c.cc.Invoke(ctx, StatsService_GetStatsOnlineIpList_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // StatsServiceServer is the server API for StatsService service. // All implementations must embed UnimplementedStatsServiceServer // for forward compatibility. @@ -91,6 +103,7 @@ type StatsServiceServer interface { GetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) + GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) mustEmbedUnimplementedStatsServiceServer() } @@ -113,6 +126,9 @@ func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRe func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") } +func (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStatsOnlineIpList not implemented") +} func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} func (UnimplementedStatsServiceServer) testEmbeddedByValue() {} @@ -206,6 +222,24 @@ func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _StatsService_GetStatsOnlineIpList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StatsService_GetStatsOnlineIpList_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, req.(*GetStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -229,6 +263,10 @@ var StatsService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetSysStats", Handler: _StatsService_GetSysStats_Handler, }, + { + MethodName: "GetStatsOnlineIpList", + Handler: _StatsService_GetStatsOnlineIpList_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "app/stats/command/command.proto", diff --git a/xray-go/app/stats/online_map.go b/xray-go/app/stats/online_map.go index 0ba2b92..9505b28 100644 --- a/xray-go/app/stats/online_map.go +++ b/xray-go/app/stats/online_map.go @@ -78,3 +78,13 @@ func (c *OnlineMap) RemoveExpiredIPs(list map[string]time.Time) map[string]time. } return list } + +func (c *OnlineMap) IpTimeMap() map[string]time.Time { + list := c.ipList + if time.Since(c.lastCleanup) > c.cleanupPeriod { + list = c.RemoveExpiredIPs(list) + c.lastCleanup = time.Now() + } + + return c.ipList +} diff --git a/xray-go/common/net/destination.go b/xray-go/common/net/destination.go index 952dcc6..90f8298 100644 --- a/xray-go/common/net/destination.go +++ b/xray-go/common/net/destination.go @@ -89,12 +89,10 @@ func UnixDestination(address Address) Destination { // NetAddr returns the network address in this Destination in string form. func (d Destination) NetAddr() string { addr := "" - if d.Address != nil { - if d.Network == Network_TCP || d.Network == Network_UDP { - addr = d.Address.String() + ":" + d.Port.String() - } else if d.Network == Network_UNIX { - addr = d.Address.String() - } + if d.Network == Network_TCP || d.Network == Network_UDP { + addr = d.Address.String() + ":" + d.Port.String() + } else if d.Network == Network_UNIX { + addr = d.Address.String() } return addr } diff --git a/xray-go/common/net/system.go b/xray-go/common/net/system.go index e5bded0..7e1c4b0 100644 --- a/xray-go/common/net/system.go +++ b/xray-go/common/net/system.go @@ -76,8 +76,9 @@ type ( ) var ( - ResolveUnixAddr = net.ResolveUnixAddr + ResolveTCPAddr = net.ResolveTCPAddr ResolveUDPAddr = net.ResolveUDPAddr + ResolveUnixAddr = net.ResolveUnixAddr ) type Resolver = net.Resolver diff --git a/xray-go/common/protocol/tls/cert/.gitignore b/xray-go/common/protocol/tls/cert/.gitignore index 612424a..b8987f0 100644 --- a/xray-go/common/protocol/tls/cert/.gitignore +++ b/xray-go/common/protocol/tls/cert/.gitignore @@ -1 +1,2 @@ -*.pem \ No newline at end of file +*.crt +*.key \ No newline at end of file diff --git a/xray-go/common/protocol/tls/cert/cert_test.go b/xray-go/common/protocol/tls/cert/cert_test.go index e06e61d..4245f3d 100644 --- a/xray-go/common/protocol/tls/cert/cert_test.go +++ b/xray-go/common/protocol/tls/cert/cert_test.go @@ -78,9 +78,9 @@ func printJSON(certificate *Certificate) { func printFile(certificate *Certificate, name string) error { certPEM, keyPEM := certificate.ToPEM() return task.Run(context.Background(), func() error { - return writeFile(certPEM, name+"_cert.pem") + return writeFile(certPEM, name+".crt") }, func() error { - return writeFile(keyPEM, name+"_key.pem") + return writeFile(keyPEM, name+".key") }) } diff --git a/xray-go/common/session/context.go b/xray-go/common/session/context.go index b7af69c..f2c8de7 100644 --- a/xray-go/common/session/context.go +++ b/xray-go/common/session/context.go @@ -23,6 +23,8 @@ const ( timeoutOnlyKey ctx.SessionKey = 8 allowedNetworkKey ctx.SessionKey = 9 handlerSessionKey ctx.SessionKey = 10 + mitmAlpn11Key ctx.SessionKey = 11 + mitmServerNameKey ctx.SessionKey = 12 ) func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { @@ -162,3 +164,25 @@ func AllowedNetworkFromContext(ctx context.Context) net.Network { } return net.Network_Unknown } + +func ContextWithMitmAlpn11(ctx context.Context, alpn11 bool) context.Context { + return context.WithValue(ctx, mitmAlpn11Key, alpn11) +} + +func MitmAlpn11FromContext(ctx context.Context) bool { + if val, ok := ctx.Value(mitmAlpn11Key).(bool); ok { + return val + } + return false +} + +func ContextWithMitmServerName(ctx context.Context, serverName string) context.Context { + return context.WithValue(ctx, mitmServerNameKey, serverName) +} + +func MitmServerNameFromContext(ctx context.Context) string { + if val, ok := ctx.Value(mitmServerNameKey).(string); ok { + return val + } + return "" +} diff --git a/xray-go/core/core.go b/xray-go/core/core.go index 5a52683..37e2e8b 100644 --- a/xray-go/core/core.go +++ b/xray-go/core/core.go @@ -18,8 +18,8 @@ import ( var ( Version_x byte = 25 - Version_y byte = 1 - Version_z byte = 30 + Version_y byte = 2 + Version_z byte = 21 ) var ( diff --git a/xray-go/core/xray.go b/xray-go/core/xray.go index f6ccc27..0cc56da 100644 --- a/xray-go/core/xray.go +++ b/xray-go/core/xray.go @@ -359,7 +359,7 @@ func (s *Instance) AddFeature(feature features.Feature) error { } s.pendingOptionalResolutions = pendingOptional s.resolveLock.Unlock() - + var err error for _, r := range availableResolution { err = r.callbackResolution(s.features) // only return the last error for now diff --git a/xray-go/features/stats/stats.go b/xray-go/features/stats/stats.go index de343a8..ab5b406 100644 --- a/xray-go/features/stats/stats.go +++ b/xray-go/features/stats/stats.go @@ -2,6 +2,7 @@ package stats import ( "context" + "time" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" @@ -30,6 +31,8 @@ type OnlineMap interface { AddIP(string) // List is the current OnlineMap ip list. List() []string + // IpTimeMap return client ips and their last access time. + IpTimeMap() map[string]time.Time } // Channel is the interface for stats channel. diff --git a/xray-go/go.mod b/xray-go/go.mod index 82bff71..488f227 100644 --- a/xray-go/go.mod +++ b/xray-go/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 - github.com/cloudflare/circl v1.5.0 + github.com/cloudflare/circl v1.6.0 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/golang/mock v1.7.0-rc.1 github.com/google/go-cmp v0.6.0 @@ -12,7 +12,7 @@ require ( github.com/miekg/dns v1.1.63 github.com/pelletier/go-toml v1.9.5 github.com/pires/go-proxyproto v0.8.0 - github.com/quic-go/quic-go v0.49.0 + github.com/quic-go/quic-go v0.50.0 github.com/refraction-networking/utls v1.6.7 github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-shadowsocks v0.2.7 @@ -22,13 +22,13 @@ require ( github.com/vishvananda/netlink v1.3.0 github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.32.0 - golang.org/x/net v0.34.0 - golang.org/x/sync v0.10.0 - golang.org/x/sys v0.29.0 + golang.org/x/crypto v0.33.0 + golang.org/x/net v0.35.0 + golang.org/x/sync v0.11.0 + golang.org/x/sys v0.30.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 google.golang.org/grpc v1.70.0 - google.golang.org/protobuf v1.36.4 + google.golang.org/protobuf v1.36.5 gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h12.io/socks v1.0.3 lukechampine.com/blake3 v1.3.0 @@ -51,7 +51,7 @@ require ( go.uber.org/mock v0.5.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/xray-go/go.sum b/xray-go/go.sum index e4418c7..43653c6 100644 --- a/xray-go/go.sum +++ b/xray-go/go.sum @@ -2,8 +2,8 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= -github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,8 +54,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94= -github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s= +github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo= +github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -95,8 +95,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -105,12 +105,12 @@ golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -119,14 +119,14 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -145,8 +145,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/xray-go/infra/conf/common.go b/xray-go/infra/conf/common.go index 1cda245..fa48ede 100644 --- a/xray-go/infra/conf/common.go +++ b/xray-go/infra/conf/common.go @@ -203,6 +203,24 @@ func (list *PortList) Build() *net.PortList { return portList } +func (v PortList) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +func (v PortList) String() string { + ports := []string{} + for _, port := range v.Range { + if port.From == port.To { + p := strconv.Itoa(int(port.From)) + ports = append(ports, p) + } else { + p := fmt.Sprintf("%d-%d", port.From, port.To) + ports = append(ports, p) + } + } + return strings.Join(ports, ",") +} + // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON func (list *PortList) UnmarshalJSON(data []byte) error { var listStr string diff --git a/xray-go/infra/conf/dns.go b/xray-go/infra/conf/dns.go index d9ed9ab..65964f3 100644 --- a/xray-go/infra/conf/dns.go +++ b/xray-go/infra/conf/dns.go @@ -12,13 +12,13 @@ import ( ) type NameServerConfig struct { - Address *Address - ClientIP *Address - Port uint16 - SkipFallback bool - Domains []string - ExpectIPs StringList - QueryStrategy string + Address *Address `json:"address"` + ClientIP *Address `json:"clientIp"` + Port uint16 `json:"port"` + SkipFallback bool `json:"skipFallback"` + Domains []string `json:"domains"` + ExpectIPs StringList `json:"expectIps"` + QueryStrategy string `json:"queryStrategy"` } func (c *NameServerConfig) UnmarshalJSON(data []byte) error { diff --git a/xray-go/infra/conf/metrics.go b/xray-go/infra/conf/metrics.go index 3f550e8..7596520 100644 --- a/xray-go/infra/conf/metrics.go +++ b/xray-go/infra/conf/metrics.go @@ -6,15 +6,21 @@ import ( ) type MetricsConfig struct { - Tag string `json:"tag"` + Tag string `json:"tag"` + Listen string `json:"listen"` } func (c *MetricsConfig) Build() (*metrics.Config, error) { + if c.Listen == "" && c.Tag == "" { + return nil, errors.New("Metrics must have a tag or listen address.") + } + // If the tag is empty but have "listen" set a default "Metrics" for compatibility. if c.Tag == "" { - return nil, errors.New("metrics tag can't be empty.") + c.Tag = "Metrics" } return &metrics.Config{ - Tag: c.Tag, + Tag: c.Tag, + Listen: c.Listen, }, nil } diff --git a/xray-go/infra/conf/transport_internet.go b/xray-go/infra/conf/transport_internet.go index 16b659c..7b993a3 100644 --- a/xray-go/infra/conf/transport_internet.go +++ b/xray-go/infra/conf/transport_internet.go @@ -411,6 +411,7 @@ type TLSConfig struct { CurvePreferences *StringList `json:"curvePreferences"` MasterKeyLog string `json:"masterKeyLog"` ServerNameToVerify string `json:"serverNameToVerify"` + VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` } // Build implements Buildable. @@ -432,6 +433,13 @@ func (c *TLSConfig) Build() (proto.Message, error) { if c.ALPN != nil && len(*c.ALPN) > 0 { config.NextProtocol = []string(*c.ALPN) } + if len(config.NextProtocol) > 1 { + for _, p := range config.NextProtocol { + if tcp.IsFromMitm(p) { + return nil, errors.New(`only one element is allowed in "alpn" when using "fromMitm" in it`) + } + } + } if c.CurvePreferences != nil && len(*c.CurvePreferences) > 0 { config.CurvePreferences = []string(*c.CurvePreferences) } @@ -442,7 +450,7 @@ func (c *TLSConfig) Build() (proto.Message, error) { config.CipherSuites = c.CipherSuites config.Fingerprint = strings.ToLower(c.Fingerprint) if config.Fingerprint != "unsafe" && tls.GetFingerprint(config.Fingerprint) == nil { - return nil, errors.New(`unknown fingerprint: `, config.Fingerprint) + return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint) } config.RejectUnknownSni = c.RejectUnknownSNI @@ -469,10 +477,11 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.MasterKeyLog = c.MasterKeyLog - config.ServerNameToVerify = c.ServerNameToVerify - if config.ServerNameToVerify != "" && config.Fingerprint == "unsafe" { - return nil, errors.New(`serverNameToVerify only works with uTLS for now`) + + if c.ServerNameToVerify != "" { + return nil, errors.PrintRemovedFeatureError(`"serverNameToVerify"`, `"verifyPeerCertInNames"`) } + config.VerifyPeerCertInNames = c.VerifyPeerCertInNames return config, nil } diff --git a/xray-go/infra/conf/xray.go b/xray-go/infra/conf/xray.go index 4de55ba..a9cc88b 100644 --- a/xray-go/infra/conf/xray.go +++ b/xray-go/infra/conf/xray.go @@ -292,7 +292,9 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1] } else { if address.Family().IsDomain() { - return nil, errors.New("unable to send through: " + address.String()) + if address.Address.Domain() != "origin" { + return nil, errors.New("unable to send through: " + address.String()) + } } } senderSettings.Via = address.Build() diff --git a/xray-go/main/commands/all/api/api.go b/xray-go/main/commands/all/api/api.go index 329450c..8188d07 100644 --- a/xray-go/main/commands/all/api/api.go +++ b/xray-go/main/commands/all/api/api.go @@ -27,5 +27,6 @@ var CmdAPI = &base.Command{ cmdRemoveRules, cmdSourceIpBlock, cmdOnlineStats, + cmdOnlineStatsIpList, }, } diff --git a/xray-go/main/commands/all/api/balancer_info.go b/xray-go/main/commands/all/api/balancer_info.go index 0d376d6..f5b6804 100644 --- a/xray-go/main/commands/all/api/balancer_info.go +++ b/xray-go/main/commands/all/api/balancer_info.go @@ -13,25 +13,20 @@ import ( var cmdBalancerInfo = &base.Command{ CustomFlags: true, UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...", - Short: "balancer information", + Short: "Retrieve balancer information", Long: ` -Get information of specified balancers, including health, strategy -and selecting. If no balancer tag specified, get information of -all balancers. +Retrieve information of specified balancers, including health, strategy and selecting. +If no balancer tag specified, information for all balancers is returned. -> Make sure you have "RoutingService" set in "config.api.services" -of server config. +> Ensure that "RoutingService" is enabled under "config.api.services" in the server configuration. Arguments: - -json - Use json output. - -s, -server The API server address. Default 127.0.0.1:8080 -t, -timeout - Timeout seconds to call API. Default 3 + Timeout in seconds for calling API. Default 3 Example: diff --git a/xray-go/main/commands/all/api/balancer_override.go b/xray-go/main/commands/all/api/balancer_override.go index 3ac013e..7386b1a 100644 --- a/xray-go/main/commands/all/api/balancer_override.go +++ b/xray-go/main/commands/all/api/balancer_override.go @@ -7,31 +7,27 @@ import ( var cmdBalancerOverride = &base.Command{ CustomFlags: true, - UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag", - Short: "balancer override", + UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag <-r>", + Short: "Override balancer", Long: ` -Override a balancer's selection. +Override the selection target of a balancer. -> Make sure you have "RoutingService" set in "config.api.services" -of server config. +> Ensure that the "RoutingService" is properly configured under "config.api.services" in the server configuration. -Once a balancer's selecting is overridden: +Once the balancer's selection is overridden: - The balancer's selection result will always be outboundTag Arguments: - -r, -remove - Remove the overridden - - -r, -remove - Remove the override - - -s, -server + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + -t, -timeout + Timeout in seconds for calling API. Default 3 + + -r, -remove + Remove the existing override. Example: diff --git a/xray-go/main/commands/all/api/inbound_user.go b/xray-go/main/commands/all/api/inbound_user.go index 7a44318..23c191d 100644 --- a/xray-go/main/commands/all/api/inbound_user.go +++ b/xray-go/main/commands/all/api/inbound_user.go @@ -8,20 +8,28 @@ import ( var cmdInboundUser = &base.Command{ CustomFlags: true, UsageLine: "{{.Exec}} api inbounduser [--server=127.0.0.1:8080] -tag=tag [-email=email]", - Short: "Get Inbound User", + Short: "Retrieve inbound user(s)", Long: ` Get User info from an inbound. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + -tag Inbound tag - -email - User email. If email is not given, will get all users + + -email + The user's email address. If not provided, all users will be retrieved. + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" -email="xray@love.com" + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" -email="xray@love.com" `, Run: executeInboundUser, } diff --git a/xray-go/main/commands/all/api/inbound_user_count.go b/xray-go/main/commands/all/api/inbound_user_count.go index 18cab7e..8c7c0fc 100644 --- a/xray-go/main/commands/all/api/inbound_user_count.go +++ b/xray-go/main/commands/all/api/inbound_user_count.go @@ -8,18 +8,24 @@ import ( var cmdInboundUserCount = &base.Command{ CustomFlags: true, UsageLine: "{{.Exec}} api inboundusercount [--server=127.0.0.1:8080] -tag=tag", - Short: "Get Inbound User Count", + Short: "Retrieve inbound user count", Long: ` -Get User count from an inbound. +Retrieve the user count for a specified inbound tag. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + -tag - Inbound tag + Inbound tag + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" `, Run: executeInboundUserCount, } diff --git a/xray-go/main/commands/all/api/inbounds_add.go b/xray-go/main/commands/all/api/inbounds_add.go index dad8b0f..9bad6f8 100644 --- a/xray-go/main/commands/all/api/inbounds_add.go +++ b/xray-go/main/commands/all/api/inbounds_add.go @@ -15,13 +15,18 @@ var cmdAddInbounds = &base.Command{ Short: "Add inbounds", Long: ` Add inbounds to Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json `, Run: executeAddInbounds, } diff --git a/xray-go/main/commands/all/api/inbounds_remove.go b/xray-go/main/commands/all/api/inbounds_remove.go index 9ab83e2..400a239 100644 --- a/xray-go/main/commands/all/api/inbounds_remove.go +++ b/xray-go/main/commands/all/api/inbounds_remove.go @@ -14,13 +14,18 @@ var cmdRemoveInbounds = &base.Command{ Short: "Remove inbounds", Long: ` Remove inbounds from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" `, Run: executeRemoveInbounds, } diff --git a/xray-go/main/commands/all/api/logger_restart.go b/xray-go/main/commands/all/api/logger_restart.go index 7bd6f10..15dacc1 100644 --- a/xray-go/main/commands/all/api/logger_restart.go +++ b/xray-go/main/commands/all/api/logger_restart.go @@ -11,11 +11,18 @@ var cmdRestartLogger = &base.Command{ Short: "Restart the logger", Long: ` Restart the logger of Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + +Example: + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 `, Run: executeRestartLogger, } diff --git a/xray-go/main/commands/all/api/outbounds_add.go b/xray-go/main/commands/all/api/outbounds_add.go index 5066e54..8d36f75 100644 --- a/xray-go/main/commands/all/api/outbounds_add.go +++ b/xray-go/main/commands/all/api/outbounds_add.go @@ -15,13 +15,18 @@ var cmdAddOutbounds = &base.Command{ Short: "Add outbounds", Long: ` Add outbounds to Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json `, Run: executeAddOutbounds, } diff --git a/xray-go/main/commands/all/api/outbounds_remove.go b/xray-go/main/commands/all/api/outbounds_remove.go index 9fdbc07..a081bc6 100644 --- a/xray-go/main/commands/all/api/outbounds_remove.go +++ b/xray-go/main/commands/all/api/outbounds_remove.go @@ -14,13 +14,18 @@ var cmdRemoveOutbounds = &base.Command{ Short: "Remove outbounds", Long: ` Remove outbounds from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" `, Run: executeRemoveOutbounds, } diff --git a/xray-go/main/commands/all/api/rules_add.go b/xray-go/main/commands/all/api/rules_add.go index 11ec1e0..2e1404e 100644 --- a/xray-go/main/commands/all/api/rules_add.go +++ b/xray-go/main/commands/all/api/rules_add.go @@ -16,16 +16,21 @@ var cmdAddRules = &base.Command{ Short: "Add routing rules", Long: ` Add routing rules to Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout + + -t, -timeout Timeout seconds to call API. Default 3 + -append - append or replace config. Default false + Append to the existing configuration instead of replacing it. Default false Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json `, Run: executeAddRules, } diff --git a/xray-go/main/commands/all/api/rules_remove.go b/xray-go/main/commands/all/api/rules_remove.go index 4904022..ac9a8d0 100644 --- a/xray-go/main/commands/all/api/rules_remove.go +++ b/xray-go/main/commands/all/api/rules_remove.go @@ -9,17 +9,22 @@ import ( var cmdRemoveRules = &base.Command{ CustomFlags: true, - UsageLine: "{{.Exec}} api rmrules [--server=127.0.0.1:8080] ruleTag1 ruleTag2...", + UsageLine: "{{.Exec}} api rmrules [--server=127.0.0.1:8080] [ruleTag]...", Short: "Remove routing rules by ruleTag", Long: ` Remove routing rules by ruleTag from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 ruleTag1 ruleTag2 + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 ruleTag1 ruleTag2 `, Run: executeRemoveRules, } diff --git a/xray-go/main/commands/all/api/source_ip_block.go b/xray-go/main/commands/all/api/source_ip_block.go index 1f77318..11f3d55 100644 --- a/xray-go/main/commands/all/api/source_ip_block.go +++ b/xray-go/main/commands/all/api/source_ip_block.go @@ -14,25 +14,34 @@ import ( var cmdSourceIpBlock = &base.Command{ CustomFlags: true, UsageLine: "{{.Exec}} api sib [--server=127.0.0.1:8080] -outbound=blocked -inbound=socks 1.2.3.4", - Short: "Drop connections by source ip", + Short: "Block connections by source IP", Long: ` -Drop connections by source ip. +Block connections by source IP address. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + -outbound - route traffic to specific outbound. + Specifies the outbound tag. + -inbound - target traffig from specific inbound. + Specifies the inbound tag. + -ruletag - set ruleTag. Default sourceIpBlock + The ruleTag. Default sourceIpBlock + -reset remove ruletag and apply new source IPs. Default false - Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json +Example: + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -outbound=blocked -inbound=socks 1.2.3.4 + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -outbound=blocked -inbound=socks 1.2.3.4 -reset `, Run: executeSourceIpBlock, } diff --git a/xray-go/main/commands/all/api/stats_get.go b/xray-go/main/commands/all/api/stats_get.go index c03fe5f..9b5d82f 100644 --- a/xray-go/main/commands/all/api/stats_get.go +++ b/xray-go/main/commands/all/api/stats_get.go @@ -8,19 +8,26 @@ import ( var cmdGetStats = &base.Command{ CustomFlags: true, UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [-name '']", - Short: "Get statistics", + Short: "Retrieve statistics", Long: ` -Get statistics from Xray. +Retrieve the statistics from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + -name - Name of the stat counter. + Name of the counter. + -reset - Reset the counter to fetching its value. + Reset the counter after fetching their values. Default false + Example: + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -name "inbound>>>statin>>>traffic>>>downlink" `, Run: executeGetStats, diff --git a/xray-go/main/commands/all/api/stats_online.go b/xray-go/main/commands/all/api/stats_online.go index 9359280..aca547a 100644 --- a/xray-go/main/commands/all/api/stats_online.go +++ b/xray-go/main/commands/all/api/stats_online.go @@ -7,21 +7,25 @@ import ( var cmdOnlineStats = &base.Command{ CustomFlags: true, - UsageLine: "{{.Exec}} api statsonline [--server=127.0.0.1:8080] [-name '']", - Short: "Get online user", + UsageLine: "{{.Exec}} api statsonline [--server=127.0.0.1:8080] [-email '']", + Short: "Retrieve the online session count for a user", Long: ` -Get statistics from Xray. +Retrieve the current number of active sessions for a user from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + -email - email of the user. - -reset - Reset the counter to fetching its value. + The user's email address. + Example: - {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email "user1@test.com" + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email "xray@love.com" `, Run: executeOnlineStats, } diff --git a/xray-go/main/commands/all/api/stats_online_ip_list.go b/xray-go/main/commands/all/api/stats_online_ip_list.go new file mode 100644 index 0000000..74e066f --- /dev/null +++ b/xray-go/main/commands/all/api/stats_online_ip_list.go @@ -0,0 +1,51 @@ +package api + +import ( + statsService "github.com/xtls/xray-core/app/stats/command" + "github.com/xtls/xray-core/main/commands/base" +) + +var cmdOnlineStatsIpList = &base.Command{ + CustomFlags: true, + UsageLine: "{{.Exec}} api statsonlineiplist [--server=127.0.0.1:8080] [-email '']", + Short: "Retrieve a user's online IP addresses and access times", + Long: ` +Retrieve the online IP addresses and corresponding access timestamps for a user from Xray. + +Arguments: + + -s, -server + The API server address. Default 127.0.0.1:8080 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + + -email + The user's email address. + +Example: + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email "xray@love.com" +`, + Run: executeOnlineStatsIpList, +} + +func executeOnlineStatsIpList(cmd *base.Command, args []string) { + setSharedFlags(cmd) + email := cmd.Flag.String("email", "", "") + cmd.Flag.Parse(args) + statName := "user>>>" + *email + ">>>online" + conn, ctx, close := dialAPIServer() + defer close() + + client := statsService.NewStatsServiceClient(conn) + r := &statsService.GetStatsRequest{ + Name: statName, + Reset_: false, + } + resp, err := client.GetStatsOnlineIpList(ctx, r) + if err != nil { + base.Fatalf("failed to get stats: %s", err) + } + showJSONResponse(resp) +} diff --git a/xray-go/main/commands/all/api/stats_query.go b/xray-go/main/commands/all/api/stats_query.go index 3b9c986..bb6949f 100644 --- a/xray-go/main/commands/all/api/stats_query.go +++ b/xray-go/main/commands/all/api/stats_query.go @@ -11,16 +11,23 @@ var cmdQueryStats = &base.Command{ Short: "Query statistics", Long: ` Query statistics from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + -pattern - Pattern of the query. + Filter pattern for the statistics query. + -reset - Reset the counter to fetching its value. + Reset the counter after fetching their values. Default false + Example: + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -pattern "counter_" `, Run: executeQueryStats, diff --git a/xray-go/main/commands/all/api/stats_sys.go b/xray-go/main/commands/all/api/stats_sys.go index de7a8ce..e34d056 100644 --- a/xray-go/main/commands/all/api/stats_sys.go +++ b/xray-go/main/commands/all/api/stats_sys.go @@ -8,14 +8,21 @@ import ( var cmdSysStats = &base.Command{ CustomFlags: true, UsageLine: "{{.Exec}} api statssys [--server=127.0.0.1:8080]", - Short: "Get system statistics", + Short: "Retrieve system statistics", Long: ` -Get system statistics from Xray. +Retrieve system statistics from Xray. + Arguments: - -s, -server + + -s, -server The API server address. Default 127.0.0.1:8080 - -t, -timeout - Timeout seconds to call API. Default 3 + + -t, -timeout + Timeout in seconds for calling API. Default 3 + +Example: + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 `, Run: executeSysStats, } diff --git a/xray-go/main/commands/all/curve25519.go b/xray-go/main/commands/all/curve25519.go index 25cc812..bb706c6 100644 --- a/xray-go/main/commands/all/curve25519.go +++ b/xray-go/main/commands/all/curve25519.go @@ -42,7 +42,8 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) { // Modify random bytes using algorithm described at: // https://cr.yp.to/ecdh.html. privateKey[0] &= 248 - privateKey[31] &= 127 | 64 + privateKey[31] &= 127 + privateKey[31] |= 64 if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil { output = err.Error() diff --git a/xray-go/main/commands/all/tls/cert.go b/xray-go/main/commands/all/tls/cert.go index c7e39eb..83cb42b 100644 --- a/xray-go/main/commands/all/tls/cert.go +++ b/xray-go/main/commands/all/tls/cert.go @@ -120,9 +120,9 @@ func writeFile(content []byte, name string) error { func printFile(certificate *cert.Certificate, name string) error { certPEM, keyPEM := certificate.ToPEM() return task.Run(context.Background(), func() error { - return writeFile(certPEM, name+"_cert.pem") + return writeFile(certPEM, name+".crt") }, func() error { - return writeFile(keyPEM, name+"_key.pem") + return writeFile(keyPEM, name+".key") }) } diff --git a/xray-go/main/run.go b/xray-go/main/run.go index c1a6cca..251e55a 100644 --- a/xray-go/main/run.go +++ b/xray-go/main/run.go @@ -54,6 +54,7 @@ The -dump flag tells Xray to print the merged config. func init() { cmdRun.Run = executeRun // break init loop + log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) } var ( diff --git a/xray-go/proxy/dokodemo/dokodemo.go b/xray-go/proxy/dokodemo/dokodemo.go index 6522f07..4fb431f 100644 --- a/xray-go/proxy/dokodemo/dokodemo.go +++ b/xray-go/proxy/dokodemo/dokodemo.go @@ -18,6 +18,7 @@ import ( "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/transport/internet/stat" + "github.com/xtls/xray-core/transport/internet/tls" ) func init() { @@ -63,10 +64,6 @@ func (d *DokodemoDoor) policy() policy.Session { return p } -type hasHandshakeAddressContext interface { - HandshakeAddressContext(ctx context.Context) net.Address -} - // Process implements proxy.Inbound. func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { errors.LogDebug(ctx, "processing connection from: ", conn.RemoteAddr()) @@ -86,11 +83,14 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st destinationOverridden = true } } - if handshake, ok := conn.(hasHandshakeAddressContext); ok && !destinationOverridden { - addr := handshake.HandshakeAddressContext(ctx) - if addr != nil { - dest.Address = addr + if tlsConn, ok := conn.(tls.Interface); ok && !destinationOverridden { + if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" { + dest.Address = net.DomainAddress(serverName) destinationOverridden = true + ctx = session.ContextWithMitmServerName(ctx, serverName) + } + if tlsConn.NegotiatedProtocol() != "h2" { + ctx = session.ContextWithMitmAlpn11(ctx, true) } } } diff --git a/xray-go/proxy/http/client.go b/xray-go/proxy/http/client.go index 862ca41..b1326be 100644 --- a/xray-go/proxy/http/client.go +++ b/xray-go/proxy/http/client.go @@ -151,6 +151,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) } responseFunc := func() error { + ob.CanSpliceCopy = 1 defer timer.SetTimeout(p.Timeouts.UplinkOnly) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } diff --git a/xray-go/proxy/http/server.go b/xray-go/proxy/http/server.go index 0121651..24708e6 100644 --- a/xray-go/proxy/http/server.go +++ b/xray-go/proxy/http/server.go @@ -207,6 +207,7 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *buf } responseDone := func() error { + inbound.CanSpliceCopy = 1 defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) v2writer := buf.NewWriter(conn) diff --git a/xray-go/proxy/proxy.go b/xray-go/proxy/proxy.go index a3d3fcc..3fec31a 100644 --- a/xray-go/proxy/proxy.go +++ b/xray-go/proxy/proxy.go @@ -107,41 +107,65 @@ type TrafficState struct { IsTLS bool Cipher uint16 RemainingServerHello int32 + Inbound InboundState + Outbound OutboundState +} +type InboundState struct { + // reader link state + WithinPaddingBuffers bool + UplinkReaderDirectCopy bool + RemainingCommand int32 + RemainingContent int32 + RemainingPadding int32 + CurrentCommand int + // write link state + IsPadding bool + DownlinkWriterDirectCopy bool +} + +type OutboundState struct { // reader link state WithinPaddingBuffers bool DownlinkReaderDirectCopy bool - UplinkReaderDirectCopy bool RemainingCommand int32 RemainingContent int32 RemainingPadding int32 CurrentCommand int - // write link state - IsPadding bool - DownlinkWriterDirectCopy bool - UplinkWriterDirectCopy bool + IsPadding bool + UplinkWriterDirectCopy bool } func NewTrafficState(userUUID []byte) *TrafficState { return &TrafficState{ - UserUUID: userUUID, - NumberOfPacketToFilter: 8, - EnableXtls: false, - IsTLS12orAbove: false, - IsTLS: false, - Cipher: 0, - RemainingServerHello: -1, - WithinPaddingBuffers: true, - DownlinkReaderDirectCopy: false, - UplinkReaderDirectCopy: false, - RemainingCommand: -1, - RemainingContent: -1, - RemainingPadding: -1, - CurrentCommand: 0, - IsPadding: true, - DownlinkWriterDirectCopy: false, - UplinkWriterDirectCopy: false, + UserUUID: userUUID, + NumberOfPacketToFilter: 8, + EnableXtls: false, + IsTLS12orAbove: false, + IsTLS: false, + Cipher: 0, + RemainingServerHello: -1, + Inbound: InboundState{ + WithinPaddingBuffers: true, + UplinkReaderDirectCopy: false, + RemainingCommand: -1, + RemainingContent: -1, + RemainingPadding: -1, + CurrentCommand: 0, + IsPadding: true, + DownlinkWriterDirectCopy: false, + }, + Outbound: OutboundState{ + WithinPaddingBuffers: true, + DownlinkReaderDirectCopy: false, + RemainingCommand: -1, + RemainingContent: -1, + RemainingPadding: -1, + CurrentCommand: 0, + IsPadding: true, + UplinkWriterDirectCopy: false, + }, } } @@ -166,28 +190,43 @@ func NewVisionReader(reader buf.Reader, state *TrafficState, isUplink bool, cont func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { buffer, err := w.Reader.ReadMultiBuffer() if !buffer.IsEmpty() { - if w.trafficState.WithinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 { + var withinPaddingBuffers *bool + var remainingContent *int32 + var remainingPadding *int32 + var currentCommand *int + var switchToDirectCopy *bool + if w.isUplink { + withinPaddingBuffers = &w.trafficState.Inbound.WithinPaddingBuffers + remainingContent = &w.trafficState.Inbound.RemainingContent + remainingPadding = &w.trafficState.Inbound.RemainingPadding + currentCommand = &w.trafficState.Inbound.CurrentCommand + switchToDirectCopy = &w.trafficState.Inbound.UplinkReaderDirectCopy + } else { + withinPaddingBuffers = &w.trafficState.Outbound.WithinPaddingBuffers + remainingContent = &w.trafficState.Outbound.RemainingContent + remainingPadding = &w.trafficState.Outbound.RemainingPadding + currentCommand = &w.trafficState.Outbound.CurrentCommand + switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy + } + + if *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 { mb2 := make(buf.MultiBuffer, 0, len(buffer)) for _, b := range buffer { - newbuffer := XtlsUnpadding(b, w.trafficState, w.ctx) + newbuffer := XtlsUnpadding(b, w.trafficState, w.isUplink, 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 - if w.isUplink { - w.trafficState.UplinkReaderDirectCopy = true - } else { - w.trafficState.DownlinkReaderDirectCopy = true - } + if *remainingContent > 0 || *remainingPadding > 0 || *currentCommand == 0 { + *withinPaddingBuffers = true + } else if *currentCommand == 1 { + *withinPaddingBuffers = false + } else if *currentCommand == 2 { + *withinPaddingBuffers = false + *switchToDirectCopy = true } else { - errors.LogInfo(w.ctx, "XtlsRead unknown command ", w.trafficState.CurrentCommand, buffer.Len()) + errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len()) } } if w.trafficState.NumberOfPacketToFilter > 0 { @@ -223,7 +262,16 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { if w.trafficState.NumberOfPacketToFilter > 0 { XtlsFilterTls(mb, w.trafficState, w.ctx) } - if w.trafficState.IsPadding { + var isPadding *bool + var switchToDirectCopy *bool + if w.isUplink { + isPadding = &w.trafficState.Outbound.IsPadding + switchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy + } else { + isPadding = &w.trafficState.Inbound.IsPadding + switchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy + } + if *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) @@ -233,11 +281,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { for i, b := range mb { if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) { if w.trafficState.EnableXtls { - if w.isUplink { - w.trafficState.UplinkWriterDirectCopy = true - } else { - w.trafficState.DownlinkWriterDirectCopy = true - } + *switchToDirectCopy = true } var command byte = CommandPaddingContinue if i == len(mb)-1 { @@ -247,16 +291,16 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { } } mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx) - w.trafficState.IsPadding = false // padding going to end + *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 + *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 { + if i == len(mb)-1 && !*isPadding { command = CommandPaddingEnd if w.trafficState.EnableXtls { command = CommandPaddingDirect @@ -343,38 +387,53 @@ func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool } // 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 { // initial state +func XtlsUnpadding(b *buf.Buffer, s *TrafficState, isUplink bool, ctx context.Context) *buf.Buffer { + var remainingCommand *int32 + var remainingContent *int32 + var remainingPadding *int32 + var currentCommand *int + if isUplink { + remainingCommand = &s.Inbound.RemainingCommand + remainingContent = &s.Inbound.RemainingContent + remainingPadding = &s.Inbound.RemainingPadding + currentCommand = &s.Inbound.CurrentCommand + } else { + remainingCommand = &s.Outbound.RemainingCommand + remainingContent = &s.Outbound.RemainingContent + remainingPadding = &s.Outbound.RemainingPadding + currentCommand = &s.Outbound.CurrentCommand + } + if *remainingCommand == -1 && *remainingContent == -1 && *remainingPadding == -1 { // initial state if b.Len() >= 21 && bytes.Equal(s.UserUUID, b.BytesTo(16)) { b.Advance(16) - s.RemainingCommand = 5 + *remainingCommand = 5 } else { return b } } newbuffer := buf.New() for b.Len() > 0 { - if s.RemainingCommand > 0 { + if *remainingCommand > 0 { data, err := b.ReadByte() if err != nil { return newbuffer } - switch s.RemainingCommand { + switch *remainingCommand { case 5: - s.CurrentCommand = int(data) + *currentCommand = int(data) case 4: - s.RemainingContent = int32(data) << 8 + *remainingContent = int32(data) << 8 case 3: - s.RemainingContent = s.RemainingContent | int32(data) + *remainingContent = *remainingContent | int32(data) case 2: - s.RemainingPadding = int32(data) << 8 + *remainingPadding = int32(data) << 8 case 1: - s.RemainingPadding = s.RemainingPadding | int32(data) - errors.LogInfo(ctx, "Xtls Unpadding new block, content ", s.RemainingContent, " padding ", s.RemainingPadding, " command ", s.CurrentCommand) + *remainingPadding = *remainingPadding | int32(data) + errors.LogInfo(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand) } - s.RemainingCommand-- - } else if s.RemainingContent > 0 { - len := s.RemainingContent + *remainingCommand-- + } else if *remainingContent > 0 { + len := *remainingContent if b.Len() < len { len = b.Len() } @@ -383,22 +442,22 @@ func XtlsUnpadding(b *buf.Buffer, s *TrafficState, ctx context.Context) *buf.Buf return newbuffer } newbuffer.Write(data) - s.RemainingContent -= len + *remainingContent -= len } else { // remainingPadding > 0 - len := s.RemainingPadding + len := *remainingPadding if b.Len() < len { len = b.Len() } b.Advance(len) - s.RemainingPadding -= len + *remainingPadding -= len } - if s.RemainingCommand <= 0 && s.RemainingContent <= 0 && s.RemainingPadding <= 0 { // this block done - if s.CurrentCommand == 0 { - s.RemainingCommand = 5 + if *remainingCommand <= 0 && *remainingContent <= 0 && *remainingPadding <= 0 { // this block done + if *currentCommand == 0 { + *remainingCommand = 5 } else { - s.RemainingCommand = -1 // set to initial state - s.RemainingContent = -1 - s.RemainingPadding = -1 + *remainingCommand = -1 // set to initial state + *remainingContent = -1 + *remainingPadding = -1 if b.Len() > 0 { // shouldn't happen newbuffer.Write(b.Bytes()) } @@ -465,7 +524,7 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte } } -// UnwrapRawConn support unwrap stats, tls, utls, reality and proxyproto conn and get raw tcp conn from it +// UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { var readCounter, writerCounter stats.Counter if conn != nil { @@ -488,6 +547,9 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { conn = pc.Raw() // 8192 > 4096, there is no need to process pc's bufReader } + if uc, ok := conn.(*internet.UnixConnWrapper); ok { + conn = uc.UnixConn + } } return conn, readCounter, writerCounter } diff --git a/xray-go/proxy/socks/client.go b/xray-go/proxy/socks/client.go index 2ed5740..232215e 100644 --- a/xray-go/proxy/socks/client.go +++ b/xray-go/proxy/socks/client.go @@ -146,6 +146,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) } responseFunc = func() error { + ob.CanSpliceCopy = 1 defer timer.SetTimeout(p.Timeouts.UplinkOnly) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } @@ -161,6 +162,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) } responseFunc = func() error { + ob.CanSpliceCopy = 1 defer timer.SetTimeout(p.Timeouts.UplinkOnly) reader := &UDPReader{Reader: udpConn} return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) diff --git a/xray-go/proxy/socks/server.go b/xray-go/proxy/socks/server.go index 472b23a..dd6f395 100644 --- a/xray-go/proxy/socks/server.go +++ b/xray-go/proxy/socks/server.go @@ -199,6 +199,7 @@ func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ } responseDone := func() error { + inbound.CanSpliceCopy = 1 defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) v2writer := buf.NewWriter(writer) @@ -256,6 +257,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis if inbound != nil && inbound.Source.IsValid() { errors.LogInfo(ctx, "client UDP connection from ", inbound.Source) } + inbound.CanSpliceCopy = 1 var dest *net.Destination diff --git a/xray-go/proxy/vless/encoding/encoding.go b/xray-go/proxy/vless/encoding/encoding.go index 3fce329..38043e6 100644 --- a/xray-go/proxy/vless/encoding/encoding.go +++ b/xray-go/proxy/vless/encoding/encoding.go @@ -175,16 +175,16 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { err := func() error { for { - if isUplink && trafficState.UplinkReaderDirectCopy || !isUplink && trafficState.DownlinkReaderDirectCopy { + if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { var writerConn net.Conn var inTimer *signal.ActivityTimer if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { writerConn = inbound.Conn inTimer = inbound.Timer - if inbound.CanSpliceCopy == 2 { + if isUplink && inbound.CanSpliceCopy == 2 { inbound.CanSpliceCopy = 1 } - if ob != nil && ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can change + if !isUplink && ob != nil && ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can change ob.CanSpliceCopy = 1 } } @@ -193,7 +193,7 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, buffer, err := reader.ReadMultiBuffer() if !buffer.IsEmpty() { timer.Update() - if isUplink && trafficState.UplinkReaderDirectCopy || !isUplink && trafficState.DownlinkReaderDirectCopy { + if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { // XTLS Vision processes struct TLS Conn's input and rawInput if inputBuffer, err := buf.ReadFrom(input); err == nil { if !inputBuffer.IsEmpty() { @@ -227,12 +227,12 @@ func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdate var ct stats.Counter for { buffer, err := reader.ReadMultiBuffer() - if isUplink && trafficState.UplinkWriterDirectCopy || !isUplink && trafficState.DownlinkWriterDirectCopy { + if isUplink && trafficState.Outbound.UplinkWriterDirectCopy || !isUplink && trafficState.Inbound.DownlinkWriterDirectCopy { if inbound := session.InboundFromContext(ctx); inbound != nil { - if inbound.CanSpliceCopy == 2 { + if !isUplink && inbound.CanSpliceCopy == 2 { inbound.CanSpliceCopy = 1 } - if ob != nil && ob.CanSpliceCopy == 2 { + if isUplink && ob != nil && ob.CanSpliceCopy == 2 { ob.CanSpliceCopy = 1 } } @@ -240,9 +240,9 @@ func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdate writer = buf.NewWriter(rawConn) ct = writerCounter if isUplink { - trafficState.UplinkWriterDirectCopy = false + trafficState.Outbound.UplinkWriterDirectCopy = false } else { - trafficState.DownlinkWriterDirectCopy = false + trafficState.Inbound.DownlinkWriterDirectCopy = false } } if !buffer.IsEmpty() { diff --git a/xray-go/transport/internet/reality/reality.go b/xray-go/transport/internet/reality/reality.go index a65507c..0efcd96 100644 --- a/xray-go/transport/internet/reality/reality.go +++ b/xray-go/transport/internet/reality/reality.go @@ -180,12 +180,12 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati prefix := []byte("https://" + uConn.ServerName) maps.Lock() if maps.maps == nil { - maps.maps = make(map[string]map[string]bool) + maps.maps = make(map[string]map[string]struct{}) } paths := maps.maps[uConn.ServerName] if paths == nil { - paths = make(map[string]bool) - paths[config.SpiderX] = true + paths = make(map[string]struct{}) + paths[config.SpiderX] = struct{}{} maps.maps[uConn.ServerName] = paths } firstURL := string(prefix) + getPathLocked(paths) @@ -232,7 +232,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati 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 + paths[string(m[1])] = struct{}{} } } req.URL.Path = getPathLocked(paths) @@ -267,10 +267,10 @@ var ( var maps struct { sync.Mutex - maps map[string]map[string]bool + maps map[string]map[string]struct{} } -func getPathLocked(paths map[string]bool) string { +func getPathLocked(paths map[string]struct{}) string { stopAt := int(randBetween(0, int64(len(paths)-1))) i := 0 for s := range paths { diff --git a/xray-go/transport/internet/splithttp/config.go b/xray-go/transport/internet/splithttp/config.go index f1ccc49..f160db3 100644 --- a/xray-go/transport/internet/splithttp/config.go +++ b/xray-go/transport/internet/splithttp/config.go @@ -34,13 +34,12 @@ func (c *Config) GetNormalizedQuery() string { query = pathAndQuery[1] } - if query != "" { - query += "&" - } - - // query += "x_version=" + core.Version() - - query += "x_padding=" + strings.Repeat("X", int(c.GetNormalizedXPaddingBytes().From)) + /* + if query != "" { + query += "&" + } + query += "x_version=" + core.Version() + */ return query } diff --git a/xray-go/transport/internet/splithttp/hub.go b/xray-go/transport/internet/splithttp/hub.go index a9aba35..179d696 100644 --- a/xray-go/transport/internet/splithttp/hub.go +++ b/xray-go/transport/internet/splithttp/hub.go @@ -3,9 +3,8 @@ package splithttp import ( "bytes" "context" - "crypto/tls" + gotls "crypto/tls" "io" - gonet "net" "net/http" "net/url" "strconv" @@ -24,7 +23,7 @@ import ( "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/stat" - v2tls "github.com/xtls/xray-core/transport/internet/tls" + "github.com/xtls/xray-core/transport/internet/tls" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) @@ -36,7 +35,7 @@ type requestHandler struct { ln *Listener sessionMu *sync.Mutex sessions sync.Map - localAddr gonet.TCPAddr + localAddr net.Addr } type httpSession struct { @@ -48,21 +47,6 @@ type httpSession struct { isFullyConnected *done.Instance } -func (h *requestHandler) maybeReapSession(isFullyConnected *done.Instance, sessionId string) { - shouldReap := done.New() - go func() { - time.Sleep(30 * time.Second) - shouldReap.Close() - }() - - select { - case <-isFullyConnected.Wait(): - return - case <-shouldReap.Wait(): - h.sessions.Delete(sessionId) - } -} - func (h *requestHandler) upsertSession(sessionId string) *httpSession { // fast path currentSessionAny, ok := h.sessions.Load(sessionId) @@ -85,7 +69,21 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession { } h.sessions.Store(sessionId, s) - go h.maybeReapSession(s.isFullyConnected, sessionId) + + shouldReap := done.New() + go func() { + time.Sleep(30 * time.Second) + shouldReap.Close() + }() + go func() { + select { + case <-shouldReap.Wait(): + h.sessions.Delete(sessionId) + s.uploadQueue.Close() + case <-s.isFullyConnected.Wait(): + } + }() + return s } @@ -144,14 +142,25 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } forwardedAddrs := http_proto.ParseXForwardedFor(request.Header) - remoteAddr, err := gonet.ResolveTCPAddr("tcp", request.RemoteAddr) + var remoteAddr net.Addr + var err error + remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr) if err != nil { - remoteAddr = &gonet.TCPAddr{} + remoteAddr = &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + } + } + if request.ProtoMajor == 3 { + remoteAddr = &net.UDPAddr{ + IP: remoteAddr.(*net.TCPAddr).IP, + Port: remoteAddr.(*net.TCPAddr).Port, + } } if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { remoteAddr = &net.TCPAddr{ IP: forwardedAddrs[0].IP(), - Port: int(0), + Port: 0, } } @@ -161,7 +170,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } scMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To) - if request.Method == "POST" && sessionId != "" { + if request.Method == "POST" && sessionId != "" { // stream-up, packet-up seq := "" if len(subpath) > 1 { seq = subpath[1] @@ -173,8 +182,13 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req writer.WriteHeader(http.StatusBadRequest) return } + httpSC := &httpServerConn{ + Instance: done.New(), + Reader: request.Body, + ResponseWriter: writer, + } err = currentSession.uploadQueue.Push(Packet{ - Reader: request.Body, + Reader: httpSC, }) if err != nil { errors.LogInfoInner(context.Background(), err, "failed to upload (PushReader)") @@ -186,21 +200,21 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs() if referrer != "" && scStreamUpServerSecs.To > 0 { go func() { - defer func() { - recover() - }() for { - _, err := writer.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand()))) + _, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand()))) if err != nil { break } - writer.(http.Flusher).Flush() time.Sleep(time.Duration(scStreamUpServerSecs.rand()) * time.Second) } }() } - <-request.Context().Done() + select { + case <-request.Context().Done(): + case <-httpSC.Wait(): + } } + httpSC.Close() return } @@ -243,12 +257,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } writer.WriteHeader(http.StatusOK) - } else if request.Method == "GET" || sessionId == "" { - responseFlusher, ok := writer.(http.Flusher) - if !ok { - panic("expected http.ResponseWriter to be an http.Flusher") - } - + } else if request.Method == "GET" || sessionId == "" { // stream-down, stream-one if sessionId != "" { // after GET is done, the connection is finished. disable automatic // session reaping, and handle it in defer @@ -269,21 +278,20 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() - responseFlusher.Flush() - - downloadDone := done.New() - + httpSC := &httpServerConn{ + Instance: done.New(), + Reader: request.Body, + ResponseWriter: writer, + } conn := splitConn{ - writer: &httpResponseBodyWriter{ - responseWriter: writer, - downloadDone: downloadDone, - responseFlusher: responseFlusher, - }, - reader: request.Body, + writer: httpSC, + reader: httpSC, remoteAddr: remoteAddr, + localAddr: h.localAddr, } - if sessionId != "" { + if sessionId != "" { // if not stream-one conn.reader = currentSession.uploadQueue } @@ -292,7 +300,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req // "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned." select { case <-request.Context().Done(): - case <-downloadDone.Wait(): + case <-httpSC.Wait(): } conn.Close() @@ -302,31 +310,30 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } } -type httpResponseBodyWriter struct { +type httpServerConn struct { sync.Mutex - responseWriter http.ResponseWriter - responseFlusher http.Flusher - downloadDone *done.Instance + *done.Instance + io.Reader // no need to Close request.Body + http.ResponseWriter } -func (c *httpResponseBodyWriter) Write(b []byte) (int, error) { +func (c *httpServerConn) Write(b []byte) (int, error) { c.Lock() defer c.Unlock() - if c.downloadDone.Done() { + if c.Done() { return 0, io.ErrClosedPipe } - n, err := c.responseWriter.Write(b) + n, err := c.ResponseWriter.Write(b) if err == nil { - c.responseFlusher.Flush() + c.ResponseWriter.(http.Flusher).Flush() } return n, err } -func (c *httpResponseBodyWriter) Close() error { +func (c *httpServerConn) Close() error { c.Lock() defer c.Unlock() - c.downloadDone.Close() - return nil + return c.Instance.Close() } type Listener struct { @@ -340,34 +347,30 @@ type Listener struct { isH3 bool } -func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) { +func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) { l := &Listener{ addConn: addConn, } - shSettings := streamSettings.ProtocolSettings.(*Config) - l.config = shSettings + l.config = streamSettings.ProtocolSettings.(*Config) if l.config != nil { if streamSettings.SocketSettings == nil { streamSettings.SocketSettings = &internet.SocketConfig{} } } - var listener net.Listener - var err error - var localAddr = gonet.TCPAddr{} handler := &requestHandler{ - config: shSettings, - host: shSettings.Host, - path: shSettings.GetNormalizedPath(), + config: l.config, + host: l.config.Host, + path: l.config.GetNormalizedPath(), ln: l, sessionMu: &sync.Mutex{}, sessions: sync.Map{}, - localAddr: localAddr, } tlsConfig := getTLSConfig(streamSettings) l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3" + var err error if port == net.Port(0) { // unix - listener, err = internet.ListenSystem(ctx, &net.UnixAddr{ + l.listener, err = internet.ListenSystem(ctx, &net.UnixAddr{ Name: address.Domain(), Net: "unix", }, streamSettings.SocketSettings) @@ -383,27 +386,24 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet if err != nil { return nil, errors.New("failed to listen UDP for XHTTP/3 on ", address, ":", port).Base(err) } - h3listener, err := quic.ListenEarly(Conn, tlsConfig, nil) + l.h3listener, err = quic.ListenEarly(Conn, tlsConfig, nil) if err != nil { return nil, errors.New("failed to listen QUIC for XHTTP/3 on ", address, ":", port).Base(err) } - l.h3listener = h3listener errors.LogInfo(ctx, "listening QUIC for XHTTP/3 on ", address, ":", port) + handler.localAddr = l.h3listener.Addr() + l.h3server = &http3.Server{ Handler: handler, } go func() { if err := l.h3server.ServeListener(l.h3listener); err != nil { - errors.LogWarningInner(ctx, err, "failed to serve HTTP/3 for XHTTP/3") + errors.LogErrorInner(ctx, err, "failed to serve HTTP/3 for XHTTP/3") } }() } else { // tcp - localAddr = gonet.TCPAddr{ - IP: address.IP(), - Port: int(port), - } - listener, err = internet.ListenSystem(ctx, &net.TCPAddr{ + l.listener, err = internet.ListenSystem(ctx, &net.TCPAddr{ IP: address.IP(), Port: int(port), }, streamSettings.SocketSettings) @@ -414,29 +414,27 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet } // tcp/unix (h1/h2) - if listener != nil { - if config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil { + if l.listener != nil { + if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { if tlsConfig := config.GetTLSConfig(); tlsConfig != nil { - listener = tls.NewListener(listener, tlsConfig) + l.listener = gotls.NewListener(l.listener, tlsConfig) } } - if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { - listener = goreality.NewListener(listener, config.GetREALITYConfig()) + l.listener = goreality.NewListener(l.listener, config.GetREALITYConfig()) } + handler.localAddr = l.listener.Addr() + // h2cHandler can handle both plaintext HTTP/1.1 and h2c - h2cHandler := h2c.NewHandler(handler, &http2.Server{}) - l.listener = listener l.server = http.Server{ - Handler: h2cHandler, + Handler: h2c.NewHandler(handler, &http2.Server{}), ReadHeaderTimeout: time.Second * 4, MaxHeaderBytes: 8192, } - go func() { if err := l.server.Serve(l.listener); err != nil { - errors.LogWarningInner(ctx, err, "failed to serve HTTP for XHTTP") + errors.LogErrorInner(ctx, err, "failed to serve HTTP for XHTTP") } }() } @@ -466,13 +464,13 @@ func (ln *Listener) Close() error { } return errors.New("listener does not have an HTTP/3 server or a net.listener") } -func getTLSConfig(streamSettings *internet.MemoryStreamConfig) *tls.Config { - config := v2tls.ConfigFromStreamSettings(streamSettings) +func getTLSConfig(streamSettings *internet.MemoryStreamConfig) *gotls.Config { + config := tls.ConfigFromStreamSettings(streamSettings) if config == nil { - return &tls.Config{} + return &gotls.Config{} } return config.GetTLSConfig() } func init() { - common.Must(internet.RegisterTransportListener(protocolName, ListenSH)) + common.Must(internet.RegisterTransportListener(protocolName, ListenXH)) } diff --git a/xray-go/transport/internet/splithttp/splithttp_test.go b/xray-go/transport/internet/splithttp/splithttp_test.go index 20043b4..f566ecc 100644 --- a/xray-go/transport/internet/splithttp/splithttp_test.go +++ b/xray-go/transport/internet/splithttp/splithttp_test.go @@ -26,9 +26,9 @@ import ( "golang.org/x/net/http2" ) -func Test_listenSHAndDial(t *testing.T) { +func Test_ListenXHAndDial(t *testing.T) { listenPort := tcp.PickPort() - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ ProtocolName: "splithttp", ProtocolSettings: &Config{ Path: "/sh", @@ -85,7 +85,7 @@ func Test_listenSHAndDial(t *testing.T) { func TestDialWithRemoteAddr(t *testing.T) { listenPort := tcp.PickPort() - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ ProtocolName: "splithttp", ProtocolSettings: &Config{ Path: "sh", @@ -125,7 +125,7 @@ func TestDialWithRemoteAddr(t *testing.T) { common.Must(listen.Close()) } -func Test_listenSHAndDial_TLS(t *testing.T) { +func Test_ListenXHAndDial_TLS(t *testing.T) { if runtime.GOARCH == "arm64" { return } @@ -145,7 +145,7 @@ func Test_listenSHAndDial_TLS(t *testing.T) { Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.CommonName("localhost")))}, }, } - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { go func() { defer conn.Close() @@ -180,7 +180,7 @@ func Test_listenSHAndDial_TLS(t *testing.T) { } } -func Test_listenSHAndDial_H2C(t *testing.T) { +func Test_ListenXHAndDial_H2C(t *testing.T) { if runtime.GOARCH == "arm64" { return } @@ -193,7 +193,7 @@ func Test_listenSHAndDial_H2C(t *testing.T) { Path: "shs", }, } - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { go func() { _ = conn.Close() }() @@ -227,7 +227,7 @@ func Test_listenSHAndDial_H2C(t *testing.T) { } } -func Test_listenSHAndDial_QUIC(t *testing.T) { +func Test_ListenXHAndDial_QUIC(t *testing.T) { if runtime.GOARCH == "arm64" { return } @@ -250,7 +250,7 @@ func Test_listenSHAndDial_QUIC(t *testing.T) { } serverClosed := false - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { go func() { defer conn.Close() @@ -309,11 +309,11 @@ func Test_listenSHAndDial_QUIC(t *testing.T) { } } -func Test_listenSHAndDial_Unix(t *testing.T) { +func Test_ListenXHAndDial_Unix(t *testing.T) { tempDir := t.TempDir() tempSocket := tempDir + "/server.sock" - listen, err := ListenSH(context.Background(), net.DomainAddress(tempSocket), 0, &internet.MemoryStreamConfig{ + listen, err := ListenXH(context.Background(), net.DomainAddress(tempSocket), 0, &internet.MemoryStreamConfig{ ProtocolName: "splithttp", ProtocolSettings: &Config{ Path: "/sh", @@ -373,7 +373,7 @@ func Test_listenSHAndDial_Unix(t *testing.T) { func Test_queryString(t *testing.T) { listenPort := tcp.PickPort() - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{ ProtocolName: "splithttp", ProtocolSettings: &Config{ // this querystring does not have any effect, but sometimes people blindly copy it from websocket config. make sure the outbound doesn't break @@ -431,7 +431,7 @@ func Test_maxUpload(t *testing.T) { } var uploadSize int - listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { + listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) { go func(c stat.Connection) { defer c.Close() var b [10240]byte diff --git a/xray-go/transport/internet/splithttp/upload_queue.go b/xray-go/transport/internet/splithttp/upload_queue.go index 382e381..69b9a97 100644 --- a/xray-go/transport/internet/splithttp/upload_queue.go +++ b/xray-go/transport/internet/splithttp/upload_queue.go @@ -20,6 +20,7 @@ type Packet struct { type uploadQueue struct { reader io.ReadCloser + nomore bool pushedPackets chan Packet writeCloseMutex sync.Mutex heap uploadHeap @@ -42,19 +43,15 @@ func (h *uploadQueue) Push(p Packet) error { h.writeCloseMutex.Lock() defer h.writeCloseMutex.Unlock() - runtime.Gosched() - if h.reader != nil && p.Reader != nil { - p.Reader.Close() - return errors.New("h.reader already exists") - } - if h.closed { - if p.Reader != nil { - p.Reader.Close() - } return errors.New("packet queue closed") } - + if h.nomore { + return errors.New("h.reader already exists") + } + if p.Reader != nil { + h.nomore = true + } h.pushedPackets <- p return nil } @@ -65,9 +62,20 @@ func (h *uploadQueue) Close() error { if !h.closed { h.closed = true + runtime.Gosched() // hope Read() gets the packet + f: + for { + select { + case p := <-h.pushedPackets: + if p.Reader != nil { + h.reader = p.Reader + } + default: + break f + } + } close(h.pushedPackets) } - runtime.Gosched() if h.reader != nil { return h.reader.Close() } diff --git a/xray-go/transport/internet/system_listener.go b/xray-go/transport/internet/system_listener.go index ad7ae89..fa3092a 100644 --- a/xray-go/transport/internet/system_listener.go +++ b/xray-go/transport/internet/system_listener.go @@ -21,19 +21,6 @@ type DefaultListener struct { 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 []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) { @@ -54,6 +41,40 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []co } } +// For some reason, other component of ray will assume the listener is a TCP listener and have valid remote address. +// But in fact it doesn't. So we need to wrap the listener to make it return 0.0.0.0(unspecified) as remote address. +// If other issues encountered, we should able to fix it here. +type UnixListenerWrapper struct { + *net.UnixListener + locker *FileLocker +} + +func (l *UnixListenerWrapper) Accept() (net.Conn, error) { + conn, err := l.UnixListener.Accept() + if err != nil { + return nil, err + } + return &UnixConnWrapper{UnixConn: conn.(*net.UnixConn)}, nil +} + +func (l *UnixListenerWrapper) Close() error { + if l.locker != nil { + l.locker.Release() + l.locker = nil + } + return l.UnixListener.Close() +} + +type UnixConnWrapper struct { + *net.UnixConn +} + +func (conn *UnixConnWrapper) RemoteAddr() net.Addr { + return &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + } +} + 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 @@ -113,9 +134,9 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S callback = func(l net.Listener, err error) (net.Listener, error) { if err != nil { locker.Release() - return l, err + return nil, err } - l = &combinedListener{Listener: l, locker: locker} + l = &UnixListenerWrapper{UnixListener: l.(*net.UnixListener), locker: locker} if filePerm == nil { return l, nil } @@ -129,9 +150,8 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S } } - l, err = lc.Listen(ctx, network, address) - l, err = callback(l, err) - if sockopt != nil && sockopt.AcceptProxyProtocol { + l, err = callback(lc.Listen(ctx, network, address)) + if err == nil && sockopt != nil && sockopt.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } l = &proxyproto.Listener{Listener: l, Policy: policyFunc} } diff --git a/xray-go/transport/internet/tcp/dialer.go b/xray-go/transport/internet/tcp/dialer.go index 19b3f6b..63b9d62 100644 --- a/xray-go/transport/internet/tcp/dialer.go +++ b/xray-go/transport/internet/tcp/dialer.go @@ -2,16 +2,23 @@ package tcp import ( "context" + "slices" + "strings" "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/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" ) +func IsFromMitm(str string) bool { + return strings.ToLower(str) == "frommitm" +} + // Dial dials a new TCP connection to the given destination. func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) { errors.LogInfo(ctx, "dialing TCP to ", dest) @@ -21,20 +28,63 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { + mitmServerName := session.MitmServerNameFromContext(ctx) + mitmAlpn11 := session.MitmAlpn11FromContext(ctx) tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) + if IsFromMitm(tlsConfig.ServerName) { + tlsConfig.ServerName = mitmServerName + } + isFromMitmVerify := false + if r, ok := tlsConfig.Rand.(*tls.RandCarrier); ok && len(r.VerifyPeerCertInNames) > 0 { + for i, name := range r.VerifyPeerCertInNames { + if IsFromMitm(name) { + isFromMitmVerify = true + r.VerifyPeerCertInNames[0], r.VerifyPeerCertInNames[i] = r.VerifyPeerCertInNames[i], r.VerifyPeerCertInNames[0] + r.VerifyPeerCertInNames = r.VerifyPeerCertInNames[1:] + after := mitmServerName + for { + if len(after) > 0 { + r.VerifyPeerCertInNames = append(r.VerifyPeerCertInNames, after) + } + _, after, _ = strings.Cut(after, ".") + if !strings.Contains(after, ".") { + break + } + } + slices.Reverse(r.VerifyPeerCertInNames) + break + } + } + } + isFromMitmAlpn := len(tlsConfig.NextProtos) == 1 && IsFromMitm(tlsConfig.NextProtos[0]) + if isFromMitmAlpn { + if mitmAlpn11 { + tlsConfig.NextProtos[0] = "http/1.1" + } else { + tlsConfig.NextProtos = []string{"h2", "http/1.1"} + } + } if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, tlsConfig, fingerprint) - if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { - if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil { - return nil, err - } + if len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "http/1.1" { // allow manually specify + err = conn.(*tls.UConn).WebsocketHandshakeContext(ctx) } else { - if err := conn.(*tls.UConn).HandshakeContext(ctx); err != nil { - return nil, err - } + err = conn.(*tls.UConn).HandshakeContext(ctx) } } else { conn = tls.Client(conn, tlsConfig) + err = conn.(*tls.Conn).HandshakeContext(ctx) + } + if err != nil { + if isFromMitmVerify { + return nil, errors.New("MITM freedom RAW TLS: failed to verify Domain Fronting certificate from " + mitmServerName).Base(err).AtWarning() + } + return nil, err + } + negotiatedProtocol := conn.(tls.Interface).NegotiatedProtocol() + if isFromMitmAlpn && !mitmAlpn11 && negotiatedProtocol != "h2" { + conn.Close() + return nil, errors.New("MITM freedom RAW TLS: unexpected Negotiated Protocol (" + negotiatedProtocol + ") with " + mitmServerName).AtWarning() } } else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil { if conn, err = reality.UClient(conn, config, ctx, dest); err != nil { diff --git a/xray-go/transport/internet/tls/config.go b/xray-go/transport/internet/tls/config.go index 8278caf..171b30b 100644 --- a/xray-go/transport/internet/tls/config.go +++ b/xray-go/transport/internet/tls/config.go @@ -9,6 +9,7 @@ import ( "crypto/x509" "encoding/base64" "os" + "slices" "strings" "sync" "time" @@ -277,10 +278,35 @@ func (c *Config) parseServerName() string { return c.ServerName } -func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - if c.PinnedPeerCertificateChainSha256 != nil { +func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + if r.VerifyPeerCertInNames != nil { + if len(r.VerifyPeerCertInNames) > 0 { + certs := make([]*x509.Certificate, len(rawCerts)) + for i, asn1Data := range rawCerts { + certs[i], _ = x509.ParseCertificate(asn1Data) + } + opts := x509.VerifyOptions{ + Roots: r.RootCAs, + CurrentTime: time.Now(), + Intermediates: x509.NewCertPool(), + } + for _, cert := range certs[1:] { + opts.Intermediates.AddCert(cert) + } + for _, opts.DNSName = range r.VerifyPeerCertInNames { + if _, err := certs[0].Verify(opts); err == nil { + return nil + } + } + } + if r.PinnedPeerCertificateChainSha256 == nil { + return errors.New("peer cert is invalid.") + } + } + + if r.PinnedPeerCertificateChainSha256 != nil { hashValue := GenerateCertChainHash(rawCerts) - for _, v := range c.PinnedPeerCertificateChainSha256 { + for _, v := range r.PinnedPeerCertificateChainSha256 { if hmac.Equal(hashValue, v) { return nil } @@ -288,11 +314,11 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue)) } - if c.PinnedPeerCertificatePublicKeySha256 != nil { + if r.PinnedPeerCertificatePublicKeySha256 != nil { for _, v := range verifiedChains { for _, cert := range v { publicHash := GenerateCertPublicKeyHash(cert) - for _, c := range c.PinnedPeerCertificatePublicKeySha256 { + for _, c := range r.PinnedPeerCertificatePublicKeySha256 { if hmac.Equal(publicHash, c) { return nil } @@ -305,7 +331,10 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert } type RandCarrier struct { - ServerNameToVerify string + RootCAs *x509.CertPool + VerifyPeerCertInNames []string + PinnedPeerCertificateChainSha256 [][]byte + PinnedPeerCertificatePublicKeySha256 [][]byte } func (r *RandCarrier) Read(p []byte) (n int, err error) { @@ -329,16 +358,25 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { } } + randCarrier := &RandCarrier{ + RootCAs: root, + VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames), + PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256, + PinnedPeerCertificatePublicKeySha256: c.PinnedPeerCertificatePublicKeySha256, + } config := &tls.Config{ - Rand: &RandCarrier{ - ServerNameToVerify: c.ServerNameToVerify, - }, + Rand: randCarrier, ClientSessionCache: globalSessionCache, RootCAs: root, InsecureSkipVerify: c.AllowInsecure, - NextProtos: c.NextProtocol, + NextProtos: slices.Clone(c.NextProtocol), SessionTicketsDisabled: !c.EnableSessionResumption, - VerifyPeerCertificate: c.verifyPeerCert, + VerifyPeerCertificate: randCarrier.verifyPeerCert, + } + if len(c.VerifyPeerCertInNames) > 0 { + config.InsecureSkipVerify = true + } else { + randCarrier.VerifyPeerCertInNames = nil } for _, opt := range opts { diff --git a/xray-go/transport/internet/tls/config.pb.go b/xray-go/transport/internet/tls/config.pb.go index e59fe50..43053c7 100644 --- a/xray-go/transport/internet/tls/config.pb.go +++ b/xray-go/transport/internet/tls/config.pb.go @@ -202,20 +202,21 @@ type Config struct { // TLS Client Hello fingerprint (uTLS). Fingerprint string `protobuf:"bytes,11,opt,name=fingerprint,proto3" json:"fingerprint,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. + // @Document Some certificate chain sha256 hashes. + // @Document After normal validation or allow_insecure, if the server's cert chain hash does not match any of these values, the connection will be aborted. // @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. + // @Document Some certificate public key sha256 hashes. + // @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted. // @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"` MasterKeyLog string `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"` // Lists of string as CurvePreferences values. - CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` - ServerNameToVerify string `protobuf:"bytes,17,opt,name=server_name_to_verify,json=serverNameToVerify,proto3" json:"server_name_to_verify,omitempty"` + CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"` + // @Document Replaces server_name to verify the peer cert. + // @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. + // @Critical + VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` } func (x *Config) Reset() { @@ -353,11 +354,11 @@ func (x *Config) GetCurvePreferences() []string { return nil } -func (x *Config) GetServerNameToVerify() string { +func (x *Config) GetVerifyPeerCertInNames() []string { if x != nil { - return x.ServerNameToVerify + return x.VerifyPeerCertInNames } - return "" + return nil } var File_transport_internet_tls_config_proto protoreflect.FileDescriptor @@ -391,7 +392,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 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, 0x93, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x9a, 0x06, 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, @@ -437,18 +438,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x76, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x76, 0x65, - 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x76, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 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, + 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x19, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, + 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, + 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 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/xray-go/transport/internet/tls/config.proto b/xray-go/transport/internet/tls/config.proto index bb2564a..c52d0be 100644 --- a/xray-go/transport/internet/tls/config.proto +++ b/xray-go/transport/internet/tls/config.proto @@ -69,16 +69,14 @@ message Config { 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. + /* @Document Some certificate chain sha256 hashes. + @Document After normal validation or allow_insecure, if the server's cert chain hash does not match any of these values, the connection will be aborted. @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. + /* @Document Some certificate public key sha256 hashes. + @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted. @Critical */ repeated bytes pinned_peer_certificate_public_key_sha256 = 14; @@ -88,5 +86,9 @@ message Config { // Lists of string as CurvePreferences values. repeated string curve_preferences = 16; - string server_name_to_verify = 17; + /* @Document Replaces server_name to verify the peer cert. + @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. + @Critical + */ + repeated string verify_peer_cert_in_names = 17; } diff --git a/xray-go/transport/internet/tls/tls.go b/xray-go/transport/internet/tls/tls.go index e15231f..bdf77d9 100644 --- a/xray-go/transport/internet/tls/tls.go +++ b/xray-go/transport/internet/tls/tls.go @@ -16,6 +16,7 @@ type Interface interface { net.Conn HandshakeContext(ctx context.Context) error VerifyHostname(host string) error + HandshakeContextServerName(ctx context.Context) string NegotiatedProtocol() string } @@ -43,15 +44,11 @@ func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error { return err } -func (c *Conn) HandshakeAddressContext(ctx context.Context) net.Address { +func (c *Conn) HandshakeContextServerName(ctx context.Context) string { if err := c.HandshakeContext(ctx); err != nil { - return nil + return "" } - state := c.ConnectionState() - if state.ServerName == "" { - return nil - } - return net.ParseAddress(state.ServerName) + return c.ConnectionState().ServerName } func (c *Conn) NegotiatedProtocol() string { @@ -85,15 +82,11 @@ func (c *UConn) Close() error { return c.Conn.Close() } -func (c *UConn) HandshakeAddressContext(ctx context.Context) net.Address { +func (c *UConn) HandshakeContextServerName(ctx context.Context) string { if err := c.HandshakeContext(ctx); err != nil { - return nil + return "" } - state := c.ConnectionState() - if state.ServerName == "" { - return nil - } - return net.ParseAddress(state.ServerName) + return c.ConnectionState().ServerName } // WebsocketHandshake basically calls UConn.Handshake inside it but it will only send @@ -134,17 +127,13 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne } func copyConfig(c *tls.Config) *utls.Config { - serverNameToVerify := "" - if r, ok := c.Rand.(*RandCarrier); ok { - serverNameToVerify = r.ServerNameToVerify - } return &utls.Config{ - RootCAs: c.RootCAs, - ServerName: c.ServerName, - InsecureSkipVerify: c.InsecureSkipVerify, - VerifyPeerCertificate: c.VerifyPeerCertificate, - KeyLogWriter: c.KeyLogWriter, - InsecureServerNameToVerify: serverNameToVerify, + Rand: c.Rand, + RootCAs: c.RootCAs, + ServerName: c.ServerName, + InsecureSkipVerify: c.InsecureSkipVerify, + VerifyPeerCertificate: c.VerifyPeerCertificate, + KeyLogWriter: c.KeyLogWriter, } }