mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-22 09:36:49 +08:00
Compare commits
45 Commits
fallback-h
...
v1.8.23
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4c82ef8a1b | ||
![]() |
30af792777 | ||
![]() |
33daa0c94b | ||
![]() |
70383c50cc | ||
![]() |
521d8ef6a1 | ||
![]() |
4531a7e228 | ||
![]() |
a342db3e28 | ||
![]() |
60553a6c26 | ||
![]() |
59f6685774 | ||
![]() |
4cb2a128db | ||
![]() |
8a4217fdf5 | ||
![]() |
7cf5ee8afd | ||
![]() |
2becdd6414 | ||
![]() |
edae38c620 | ||
![]() |
36f427f22b | ||
![]() |
c27d652d80 | ||
![]() |
0f65aa8ed8 | ||
![]() |
22535d8643 | ||
![]() |
529f206d33 | ||
![]() |
964859b4bc | ||
![]() |
8deb953aec | ||
![]() |
a0040f13dd | ||
![]() |
d8994b7603 | ||
![]() |
b277bacdf6 | ||
![]() |
9288a7c0dc | ||
![]() |
c40fc44a34 | ||
![]() |
02cd3b8c74 | ||
![]() |
a7e198e1e2 | ||
![]() |
9e6d7a3cb0 | ||
![]() |
a4bc422ed1 | ||
![]() |
59819e2a1b | ||
![]() |
573fb4f643 | ||
![]() |
558cfcc507 | ||
![]() |
39675b7ef7 | ||
![]() |
16de0937a8 | ||
![]() |
c69d38ae82 | ||
![]() |
73a001dd7a | ||
![]() |
c8f6ba9ff0 | ||
![]() |
308f0c64c3 | ||
![]() |
ce637c0c23 | ||
![]() |
0d130a0489 | ||
![]() |
01a3b4912b | ||
![]() |
b8c0768b16 | ||
![]() |
4c51636788 | ||
![]() |
1113ee7fa2 |
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Community Support and Questions
|
||||
url: https://github.com/XTLS/Xray-core/discussions
|
||||
about: Please ask and answer questions there. The issue tracker is for issues with core.
|
28
.github/docker/Dockerfile
vendored
28
.github/docker/Dockerfile
vendored
@@ -2,21 +2,27 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
ARG TARGETOS TARGETARCH
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
|
||||
ADD https://github.com/v2fly/geoip/releases/latest/download/geoip.dat /v2fly/geoip.dat
|
||||
ADD https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat /v2fly/geosite.dat
|
||||
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat /loyalsoldier/geoip.dat
|
||||
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat /loyalsoldier/geosite.dat
|
||||
|
||||
FROM --platform=${TARGETPLATFORM} alpine:latest
|
||||
WORKDIR /root
|
||||
# chainguard/static contains only tzdata and ca-certificates, can be built with multiarch static binaries.
|
||||
FROM --platform=linux/amd64 chainguard/static:latest
|
||||
WORKDIR /var/log/xray
|
||||
COPY .github/docker/files/config.json /etc/xray/config.json
|
||||
COPY --from=build /src/xray /usr/bin/xray
|
||||
RUN set -ex \
|
||||
&& apk add --no-cache tzdata ca-certificates \
|
||||
&& mkdir -p /var/log/xray /usr/share/xray \
|
||||
&& chmod +x /usr/bin/xray \
|
||||
&& wget -O /usr/share/xray/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat \
|
||||
&& wget -O /usr/share/xray/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
COPY --from=build --chmod=755 /src/xray /usr/bin/xray
|
||||
|
||||
USER root
|
||||
WORKDIR /root
|
||||
VOLUME /etc/xray
|
||||
ENV TZ=Asia/Shanghai
|
||||
ARG TZ=Asia/Shanghai
|
||||
ENV TZ=$TZ
|
||||
ENTRYPOINT [ "/usr/bin/xray" ]
|
||||
CMD [ "-config", "/etc/xray/config.json" ]
|
||||
|
||||
ARG flavor=v2fly
|
||||
COPY --from=build /$flavor /usr/share/xray
|
||||
|
42
.github/workflows/docker.yml
vendored
42
.github/workflows/docker.yml
vendored
@@ -19,7 +19,20 @@ jobs:
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/xray-core
|
||||
flavor: latest=true
|
||||
flavor: latest=auto
|
||||
tags: |
|
||||
type=sha
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
- name: Docker metadata Loyalsoldier flavor
|
||||
id: loyalsoldier
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/xray-core
|
||||
flavor: |
|
||||
latest=auto
|
||||
suffix=-ls,onlatest=true
|
||||
tags: |
|
||||
type=sha
|
||||
type=ref,event=branch
|
||||
@@ -31,18 +44,33 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- # Add support for more platforms with QEMU (optional)
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/loong64
|
||||
linux/riscv64
|
||||
provenance: false
|
||||
file: .github/docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Build and push Loyalsoldier flavor
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/loong64
|
||||
linux/riscv64
|
||||
provenance: false
|
||||
file: .github/docker/Dockerfile
|
||||
build-args: flavor=loyalsoldier
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.loyalsoldier.outputs.tags }}
|
||||
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -78,12 +78,15 @@ jobs:
|
||||
# Include amd64 on all platforms.
|
||||
goos: [windows, freebsd, openbsd, linux, darwin]
|
||||
goarch: [amd64, 386]
|
||||
gotoolchain: [""]
|
||||
patch-assetname: [""]
|
||||
|
||||
exclude:
|
||||
# Exclude i386 on darwin
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
include:
|
||||
# BEIGIN MacOS ARM64
|
||||
# BEGIN MacOS ARM64
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
# END MacOS ARM64
|
||||
@@ -152,6 +155,16 @@ jobs:
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# END OPENBSD ARM
|
||||
# BEGIN Windows 7
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
gotoolchain: 1.21.4
|
||||
patch-assetname: win7-64
|
||||
- goos: windows
|
||||
goarch: 386
|
||||
gotoolchain: 1.21.4
|
||||
patch-assetname: win7-32
|
||||
# END Windows 7
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -164,16 +177,17 @@ jobs:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Show workflow information
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||
_NAME=${{ matrix.patch-assetname }}
|
||||
[ -n "$_NAME" ] || _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
go-version: ${{ matrix.gotoolchain || '1.22' }}
|
||||
check-latest: true
|
||||
|
||||
- name: Get project dependencies
|
||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
go-version: '1.22'
|
||||
check-latest: true
|
||||
- name: Restore Cache
|
||||
uses: actions/cache/restore@v4
|
||||
|
4
Makefile
4
Makefile
@@ -3,13 +3,13 @@ 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 convinience in automatic building and functions as a part of it.
|
||||
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 accidently break the automatic building:\
|
||||
that the change will not accidentally break the automatic building:\
|
||||
.github/workflows/release.yml \
|
||||
Otherwise it is recommended to contact the project maintainers.
|
||||
|
||||
|
13
README.md
13
README.md
@@ -27,8 +27,7 @@
|
||||
- [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)
|
||||
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
|
||||
- Web Panel
|
||||
- [X-UI-English](https://github.com/NidukaAkalanka/x-ui-english), [3X-UI](https://github.com/MHSanaei/3x-ui), [X-UI](https://github.com/alireza0/x-ui), [X-UI](https://github.com/diditra/x-ui)
|
||||
- [Xray-UI](https://github.com/qist/xray-ui), [X-UI](https://github.com/sing-web/x-ui)
|
||||
- [3X-UI](https://github.com/MHSanaei/3x-ui), [X-UI](https://github.com/alireza0/x-ui), [Xray-UI](https://github.com/qist/xray-ui)
|
||||
- [Hiddify](https://github.com/hiddify/hiddify-config)
|
||||
- [Marzban](https://github.com/Gozargah/Marzban)
|
||||
- [Libertea](https://github.com/VZiChoushaDui/Libertea)
|
||||
@@ -67,13 +66,10 @@
|
||||
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
||||
- Windows
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [NekoRay](https://github.com/Matsuridayo/nekoray)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
- [HiddifyN](https://github.com/hiddify/HiddifyN)
|
||||
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
||||
- Android
|
||||
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||
- [HiddifyNG](https://github.com/hiddify/HiddifyNG)
|
||||
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||
- iOS & macOS arm64
|
||||
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
|
||||
@@ -85,7 +81,6 @@
|
||||
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
|
||||
- Linux
|
||||
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
||||
- [NekoRay](https://github.com/Matsuridayo/nekoray)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
@@ -98,21 +93,15 @@
|
||||
- [XTLS/libXray](https://github.com/XTLS/libXray)
|
||||
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
||||
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
||||
- [XrayKit](https://github.com/arror/XrayKit)
|
||||
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
|
||||
- [xray-api](https://github.com/XVGuardian/xray-api)
|
||||
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
|
||||
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
|
||||
- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
|
||||
- [Clash Verge](https://github.com/zzzgydi/clash-verge)
|
||||
- [clashN](https://github.com/2dust/clashN)
|
||||
- [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid)
|
||||
- [meta_for_ios](https://t.me/meta_for_ios)
|
||||
- [sing-box](https://github.com/SagerNet/sing-box)
|
||||
- [installReality](https://github.com/BoxXt/installReality)
|
||||
- [sbox-reality](https://github.com/Misaka-blog/sbox-reality)
|
||||
- [sing-box-for-ios](https://github.com/SagerNet/sing-box-for-ios)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@@ -85,7 +85,7 @@ func NewClient(
|
||||
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
// Priotize local domains with specific TLDs or without any dot to local DNS
|
||||
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
||||
ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
|
||||
|
@@ -259,7 +259,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
|
||||
})
|
||||
|
||||
// forced to use mux for DOH
|
||||
// dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true)
|
||||
// dnsCtx = session.ContextWithMuxPreferred(dnsCtx, true)
|
||||
|
||||
var cancel context.CancelFunc
|
||||
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||
|
@@ -30,8 +30,8 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
}
|
||||
log.RegisterHandler(g)
|
||||
|
||||
// start logger instantly on inited
|
||||
// other modules would log during init
|
||||
// Start logger instantly on initialization
|
||||
// Other modules would log during initialization
|
||||
if err := g.startInternal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -209,7 +209,7 @@ func (h *HealthPing) PutResult(tag string, rtt time.Duration) {
|
||||
if !ok {
|
||||
// validity is 2 times to sampling period, since the check are
|
||||
// distributed in the time line randomly, in extreme cases,
|
||||
// previous checks are distributed on the left, and latters
|
||||
// Previous checks are distributed on the left, and later ones
|
||||
// on the right
|
||||
validity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2
|
||||
r = NewHealthPingResult(h.Settings.SamplingCount, validity)
|
||||
|
@@ -272,7 +272,7 @@ func TestServiceSubscribeSubsetOfFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerivceTestRoute(t *testing.T) {
|
||||
func TestServiceTestRoute(t *testing.T) {
|
||||
c := stats.NewChannel(&stats.ChannelConfig{
|
||||
SubscriberLimit: 1,
|
||||
BufferSize: 16,
|
||||
|
@@ -95,7 +95,7 @@ func TestStatsChannel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsChannelUnsubcribe(t *testing.T) {
|
||||
func TestStatsChannelUnsubscribe(t *testing.T) {
|
||||
c := NewChannel(&ChannelConfig{Blocking: true})
|
||||
common.Must(c.Start())
|
||||
defer c.Close()
|
||||
|
@@ -18,7 +18,7 @@ func TestStatsCounter(t *testing.T) {
|
||||
common.Must(err)
|
||||
|
||||
if v := c.Add(1); v != 1 {
|
||||
t.Fatal("unpexcted Add(1) return: ", v, ", wanted ", 1)
|
||||
t.Fatal("unexpected Add(1) return: ", v, ", wanted ", 1)
|
||||
}
|
||||
|
||||
if v := c.Set(0); v != 1 {
|
||||
|
@@ -106,7 +106,7 @@ func TestMultiBufferReadAllToByte(t *testing.T) {
|
||||
common.Must(err)
|
||||
|
||||
if l := len(b); l != 8*1024 {
|
||||
t.Error("unexpceted length from ReadAllToBytes", l)
|
||||
t.Error("unexpected length from ReadAllToBytes", l)
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -139,7 +139,7 @@ func TestMultiBufferCopy(t *testing.T) {
|
||||
mb.Copy(lbdst)
|
||||
|
||||
if d := cmp.Diff(lb, lbdst); d != "" {
|
||||
t.Error("unexpceted different from MultiBufferCopy ", d)
|
||||
t.Error("unexpected different from MultiBufferCopy ", d)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -41,8 +41,8 @@ type BufferedReader struct {
|
||||
Reader Reader
|
||||
// Buffer is the internal buffer to be read from first
|
||||
Buffer MultiBuffer
|
||||
// Spliter is a function to read bytes from MultiBuffer
|
||||
Spliter func(MultiBuffer, []byte) (MultiBuffer, int)
|
||||
// Splitter is a function to read bytes from MultiBuffer
|
||||
Splitter func(MultiBuffer, []byte) (MultiBuffer, int)
|
||||
}
|
||||
|
||||
// BufferedBytes returns the number of bytes that is cached in this reader.
|
||||
@@ -59,7 +59,7 @@ func (r *BufferedReader) ReadByte() (byte, error) {
|
||||
|
||||
// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader.
|
||||
func (r *BufferedReader) Read(b []byte) (int, error) {
|
||||
spliter := r.Spliter
|
||||
spliter := r.Splitter
|
||||
if spliter == nil {
|
||||
spliter = SplitBytes
|
||||
}
|
||||
|
@@ -151,7 +151,7 @@ func LogInfo(ctx context.Context, msg ...interface{}) {
|
||||
}
|
||||
|
||||
func LogInfoInner(ctx context.Context, inner error, msg ...interface{}) {
|
||||
doLog(ctx, inner, log.Severity_Debug, msg...)
|
||||
doLog(ctx, inner, log.Severity_Info, msg...)
|
||||
}
|
||||
|
||||
func LogWarning(ctx context.Context, msg ...interface{}) {
|
||||
@@ -159,7 +159,7 @@ func LogWarning(ctx context.Context, msg ...interface{}) {
|
||||
}
|
||||
|
||||
func LogWarningInner(ctx context.Context, inner error, msg ...interface{}) {
|
||||
doLog(ctx, inner, log.Severity_Debug, msg...)
|
||||
doLog(ctx, inner, log.Severity_Warning, msg...)
|
||||
}
|
||||
|
||||
func LogError(ctx context.Context, msg ...interface{}) {
|
||||
@@ -167,7 +167,7 @@ func LogError(ctx context.Context, msg ...interface{}) {
|
||||
}
|
||||
|
||||
func LogErrorInner(ctx context.Context, inner error, msg ...interface{}) {
|
||||
doLog(ctx, inner, log.Severity_Debug, msg...)
|
||||
doLog(ctx, inner, log.Severity_Error, msg...)
|
||||
}
|
||||
|
||||
func doLog(ctx context.Context, inner error, severity log.Severity, msg ...interface{}) {
|
||||
|
@@ -51,8 +51,8 @@ func ConnectionOutputMulti(reader buf.Reader) ConnectionOption {
|
||||
func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption {
|
||||
return func(c *connection) {
|
||||
c.reader = &buf.BufferedReader{
|
||||
Reader: reader,
|
||||
Spliter: buf.SplitFirstBytes,
|
||||
Reader: reader,
|
||||
Splitter: buf.SplitFirstBytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ func GetToolLocation(file string) string {
|
||||
return filepath.Join(toolPath, file+".exe")
|
||||
}
|
||||
|
||||
// GetAssetLocation searches for `file` in the excutable dir
|
||||
// GetAssetLocation searches for `file` in the executable dir
|
||||
func GetAssetLocation(file string) string {
|
||||
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
|
||||
return filepath.Join(assetPath, file)
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
type Certificate struct {
|
||||
// Cerificate in ASN.1 DER format
|
||||
// certificate in ASN.1 DER format
|
||||
Certificate []byte
|
||||
// Private key in ASN.1 DER format
|
||||
PrivateKey []byte
|
||||
@@ -149,10 +149,6 @@ func Generate(parent *Certificate, opts ...Option) (*Certificate, error) {
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(template)
|
||||
}
|
||||
|
||||
parentCert := template
|
||||
if parent != nil {
|
||||
pCert, err := x509.ParseCertificate(parent.Certificate)
|
||||
@@ -162,6 +158,17 @@ func Generate(parent *Certificate, opts ...Option) (*Certificate, error) {
|
||||
parentCert = pCert
|
||||
}
|
||||
|
||||
if parentCert.NotAfter.Before(template.NotAfter) {
|
||||
template.NotAfter = parentCert.NotAfter
|
||||
}
|
||||
if parentCert.NotBefore.After(template.NotBefore) {
|
||||
template.NotBefore = parentCert.NotBefore
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(template)
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, publicKey(selfKey), parentKey)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create certificate").Base(err)
|
||||
|
@@ -147,7 +147,7 @@ func TestTLSHeaders(t *testing.T) {
|
||||
header, err := SniffTLS(test.input)
|
||||
if test.err {
|
||||
if err == nil {
|
||||
t.Errorf("Exepct error but nil in test %v", test)
|
||||
t.Errorf("Expect error but nil in test %v", test)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
|
@@ -21,7 +21,7 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// User is a generic user for all procotols.
|
||||
// User is a generic user for all protocols.
|
||||
type User struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@@ -8,7 +8,7 @@ option java_multiple_files = true;
|
||||
|
||||
import "common/serial/typed_message.proto";
|
||||
|
||||
// User is a generic user for all procotols.
|
||||
// User is a generic user for all protocols.
|
||||
message User {
|
||||
uint32 level = 1;
|
||||
string email = 2;
|
||||
|
@@ -13,16 +13,16 @@ import (
|
||||
func IndependentCancelCtx(parent context.Context) context.Context
|
||||
|
||||
const (
|
||||
inboundSessionKey ctx.SessionKey = 1
|
||||
outboundSessionKey ctx.SessionKey = 2
|
||||
contentSessionKey ctx.SessionKey = 3
|
||||
muxPreferedSessionKey ctx.SessionKey = 4
|
||||
sockoptSessionKey ctx.SessionKey = 5
|
||||
inboundSessionKey ctx.SessionKey = 1
|
||||
outboundSessionKey ctx.SessionKey = 2
|
||||
contentSessionKey ctx.SessionKey = 3
|
||||
muxPreferredSessionKey ctx.SessionKey = 4
|
||||
sockoptSessionKey ctx.SessionKey = 5
|
||||
trackedConnectionErrorKey ctx.SessionKey = 6
|
||||
dispatcherKey ctx.SessionKey = 7
|
||||
timeoutOnlyKey ctx.SessionKey = 8
|
||||
allowedNetworkKey ctx.SessionKey = 9
|
||||
handlerSessionKey ctx.SessionKey = 10
|
||||
dispatcherKey ctx.SessionKey = 7
|
||||
timeoutOnlyKey ctx.SessionKey = 8
|
||||
allowedNetworkKey ctx.SessionKey = 9
|
||||
handlerSessionKey ctx.SessionKey = 10
|
||||
)
|
||||
|
||||
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
|
||||
@@ -58,14 +58,14 @@ func ContentFromContext(ctx context.Context) *Content {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextWithMuxPrefered returns a new context with the given bool
|
||||
func ContextWithMuxPrefered(ctx context.Context, forced bool) context.Context {
|
||||
return context.WithValue(ctx, muxPreferedSessionKey, forced)
|
||||
// ContextWithMuxPreferred returns a new context with the given bool
|
||||
func ContextWithMuxPreferred(ctx context.Context, forced bool) context.Context {
|
||||
return context.WithValue(ctx, muxPreferredSessionKey, forced)
|
||||
}
|
||||
|
||||
// MuxPreferedFromContext returns value in this context, or false if not contained.
|
||||
func MuxPreferedFromContext(ctx context.Context) bool {
|
||||
if val, ok := ctx.Value(muxPreferedSessionKey).(bool); ok {
|
||||
// MuxPreferredFromContext returns value in this context, or false if not contained.
|
||||
func MuxPreferredFromContext(ctx context.Context) bool {
|
||||
if val, ok := ctx.Value(muxPreferredSessionKey).(bool); ok {
|
||||
return val
|
||||
}
|
||||
return false
|
||||
|
@@ -29,7 +29,7 @@ func TestActivityTimerUpdate(t *testing.T) {
|
||||
timer.SetTimeout(time.Second * 1)
|
||||
time.Sleep(time.Second * 2)
|
||||
if ctx.Err() == nil {
|
||||
t.Error("expcted some error, but got nil")
|
||||
t.Error("expected some error, but got nil")
|
||||
}
|
||||
runtime.KeepAlive(timer)
|
||||
}
|
||||
|
@@ -174,7 +174,7 @@ func init() {
|
||||
common.Must(err)
|
||||
return loadProtobufConfig(data)
|
||||
default:
|
||||
return nil, errors.New("unknow type")
|
||||
return nil, errors.New("unknown type")
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
@@ -21,7 +21,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 1
|
||||
Version_y byte = 8
|
||||
Version_z byte = 16
|
||||
Version_z byte = 23
|
||||
)
|
||||
|
||||
var (
|
||||
|
17
go.mod
17
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/xtls/xray-core
|
||||
|
||||
go 1.22
|
||||
go 1.21.4
|
||||
|
||||
require (
|
||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
|
||||
@@ -13,19 +13,19 @@ require (
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/quic-go/quic-go v0.45.1
|
||||
github.com/refraction-networking/utls v1.6.6
|
||||
github.com/refraction-networking/utls v1.6.7
|
||||
github.com/sagernet/sing v0.4.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3
|
||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/net v0.27.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.org/x/sys v0.22.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/grpc v1.65.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
@@ -46,6 +46,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
|
30
go.sum
30
go.sum
@@ -110,19 +110,21 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA=
|
||||
github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
|
||||
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
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=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
|
||||
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@@ -163,8 +165,8 @@ github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mo
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
|
||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
@@ -177,8 +179,8 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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=
|
||||
@@ -199,8 +201,8 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -226,8 +228,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@@ -2,6 +2,7 @@ package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -242,3 +243,33 @@ func (v *User) Build() *protocol.User {
|
||||
Level: uint32(v.LevelByte),
|
||||
}
|
||||
}
|
||||
|
||||
// Int32Range deserializes from "1-2" or 1, so can deserialize from both int and number.
|
||||
// Negative integers can be passed as sentinel values, but do not parse as ranges.
|
||||
type Int32Range struct {
|
||||
From int32
|
||||
To int32
|
||||
}
|
||||
|
||||
func (v *Int32Range) UnmarshalJSON(data []byte) error {
|
||||
var stringrange string
|
||||
var rawint int32
|
||||
if err := json.Unmarshal(data, &stringrange); err == nil {
|
||||
pair := strings.SplitN(stringrange, "-", 2)
|
||||
if len(pair) == 2 {
|
||||
from, err := strconv.Atoi(pair[0])
|
||||
to, err2 := strconv.Atoi(pair[1])
|
||||
if err == nil && err2 == nil {
|
||||
v.From = int32(from)
|
||||
v.To = int32(to)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else if err := json.Unmarshal(data, &rawint); err == nil {
|
||||
v.From = rawint
|
||||
v.To = rawint
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("Invalid integer range, expected either string of form \"1-2\" or plain integer.")
|
||||
}
|
||||
|
@@ -229,8 +229,10 @@ type SplitHTTPConfig struct {
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
MaxConcurrentUploads int32 `json:"maxConcurrentUploads"`
|
||||
MaxUploadSize int32 `json:"maxUploadSize"`
|
||||
ScMaxConcurrentPosts Int32Range `json:"scMaxConcurrentPosts"`
|
||||
ScMaxEachPostBytes Int32Range `json:"scMaxEachPostBytes"`
|
||||
ScMinPostsIntervalMs Int32Range `json:"scMinPostsIntervalMs"`
|
||||
NoSSEHeader bool `json:"noSSEHeader"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@@ -244,11 +246,22 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
c.Host = c.Headers["Host"]
|
||||
}
|
||||
config := &splithttp.Config{
|
||||
Path: c.Path,
|
||||
Host: c.Host,
|
||||
Header: c.Headers,
|
||||
MaxConcurrentUploads: c.MaxConcurrentUploads,
|
||||
MaxUploadSize: c.MaxUploadSize,
|
||||
Path: c.Path,
|
||||
Host: c.Host,
|
||||
Header: c.Headers,
|
||||
ScMaxConcurrentPosts: &splithttp.RandRangeConfig{
|
||||
From: c.ScMaxConcurrentPosts.From,
|
||||
To: c.ScMaxConcurrentPosts.To,
|
||||
},
|
||||
ScMaxEachPostBytes: &splithttp.RandRangeConfig{
|
||||
From: c.ScMaxEachPostBytes.From,
|
||||
To: c.ScMaxEachPostBytes.To,
|
||||
},
|
||||
ScMinPostsIntervalMs: &splithttp.RandRangeConfig{
|
||||
From: c.ScMinPostsIntervalMs.From,
|
||||
To: c.ScMinPostsIntervalMs.To,
|
||||
},
|
||||
NoSSEHeader: c.NoSSEHeader,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -372,6 +385,7 @@ type TLSCertConfig struct {
|
||||
Usage string `json:"usage"`
|
||||
OcspStapling uint64 `json:"ocspStapling"`
|
||||
OneTimeLoading bool `json:"oneTimeLoading"`
|
||||
BuildChain bool `json:"buildChain"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@@ -410,6 +424,7 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
||||
certificate.OneTimeLoading = c.OneTimeLoading
|
||||
}
|
||||
certificate.OcspStapling = c.OcspStapling
|
||||
certificate.BuildChain = c.BuildChain
|
||||
|
||||
return certificate, nil
|
||||
}
|
||||
@@ -685,23 +700,31 @@ func (p TransportProtocol) Build() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
type CustomSockoptConfig struct {
|
||||
Level string `json:"level"`
|
||||
Opt string `json:"opt"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type SocketConfig struct {
|
||||
Mark int32 `json:"mark"`
|
||||
TFO interface{} `json:"tcpFastOpen"`
|
||||
TProxy string `json:"tproxy"`
|
||||
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
DialerProxy string `json:"dialerProxy"`
|
||||
TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"`
|
||||
TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"`
|
||||
TCPCongestion string `json:"tcpCongestion"`
|
||||
TCPWindowClamp int32 `json:"tcpWindowClamp"`
|
||||
TCPMaxSeg int32 `json:"tcpMaxSeg"`
|
||||
TcpNoDelay bool `json:"tcpNoDelay"`
|
||||
TCPUserTimeout int32 `json:"tcpUserTimeout"`
|
||||
V6only bool `json:"v6only"`
|
||||
Interface string `json:"interface"`
|
||||
TcpMptcp bool `json:"tcpMptcp"`
|
||||
Mark int32 `json:"mark"`
|
||||
TFO interface{} `json:"tcpFastOpen"`
|
||||
TProxy string `json:"tproxy"`
|
||||
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
DialerProxy string `json:"dialerProxy"`
|
||||
TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"`
|
||||
TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"`
|
||||
TCPCongestion string `json:"tcpCongestion"`
|
||||
TCPWindowClamp int32 `json:"tcpWindowClamp"`
|
||||
TCPMaxSeg int32 `json:"tcpMaxSeg"`
|
||||
TcpNoDelay bool `json:"tcpNoDelay"`
|
||||
TCPUserTimeout int32 `json:"tcpUserTimeout"`
|
||||
V6only bool `json:"v6only"`
|
||||
Interface string `json:"interface"`
|
||||
TcpMptcp bool `json:"tcpMptcp"`
|
||||
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
||||
}
|
||||
|
||||
// Build implements Buildable.
|
||||
@@ -759,6 +782,18 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
||||
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
|
||||
}
|
||||
|
||||
var customSockopts []*internet.CustomSockopt
|
||||
|
||||
for _, copt := range c.CustomSockopt {
|
||||
customSockopt := &internet.CustomSockopt{
|
||||
Level: copt.Level,
|
||||
Opt: copt.Opt,
|
||||
Value: copt.Value,
|
||||
Type: copt.Type,
|
||||
}
|
||||
customSockopts = append(customSockopts, customSockopt)
|
||||
}
|
||||
|
||||
return &internet.SocketConfig{
|
||||
Mark: c.Mark,
|
||||
Tfo: tfo,
|
||||
@@ -776,6 +811,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
||||
V6Only: c.V6only,
|
||||
Interface: c.Interface,
|
||||
TcpMptcp: c.TcpMptcp,
|
||||
CustomSockopt: customSockopts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
// CommandEnvHolder is a struct holds the environment info of commands
|
||||
type CommandEnvHolder struct {
|
||||
// Excutable name of current binary
|
||||
// Executable name of current binary
|
||||
Exec string
|
||||
// commands column width of current command
|
||||
CommandsWidth int
|
||||
|
@@ -43,7 +43,7 @@ func init() {
|
||||
case io.Reader:
|
||||
return serial.LoadJSONConfig(v)
|
||||
default:
|
||||
return nil, errors.New("unknow type")
|
||||
return nil, errors.New("unknown type")
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
@@ -43,7 +43,7 @@ func init() {
|
||||
case io.Reader:
|
||||
return serial.LoadTOMLConfig(v)
|
||||
default:
|
||||
return nil, errors.New("unknow type")
|
||||
return nil, errors.New("unknown type")
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
@@ -43,7 +43,7 @@ func init() {
|
||||
case io.Reader:
|
||||
return serial.LoadYAMLConfig(v)
|
||||
default:
|
||||
return nil, errors.New("unknow type")
|
||||
return nil, errors.New("unknown type")
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
var useSplice bool
|
||||
@@ -225,9 +226,16 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
writeConn = inbound.Conn
|
||||
inTimer = inbound.Timer
|
||||
}
|
||||
return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer)
|
||||
if !isTLSConn(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic
|
||||
return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer)
|
||||
}
|
||||
}
|
||||
var reader buf.Reader
|
||||
if destination.Network == net.Network_TCP {
|
||||
reader = buf.NewReader(conn)
|
||||
} else {
|
||||
reader = NewPacketReader(conn, UDPOverride)
|
||||
}
|
||||
reader := NewPacketReader(conn, UDPOverride)
|
||||
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to process response").Base(err)
|
||||
}
|
||||
@@ -245,6 +253,19 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
return nil
|
||||
}
|
||||
|
||||
func isTLSConn(conn stat.Connection) bool {
|
||||
if conn != nil {
|
||||
statConn, ok := conn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
conn = statConn.Connection
|
||||
}
|
||||
if _, ok := conn.(*tls.Conn); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
|
@@ -243,7 +243,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
return w.Writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
|
||||
// ReshapeMultiBuffer prepare multi buffer for padding stucture (max 21 bytes)
|
||||
// ReshapeMultiBuffer prepare multi buffer for padding structure (max 21 bytes)
|
||||
func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer {
|
||||
needReshape := 0
|
||||
for _, b := range buffer {
|
||||
@@ -278,7 +278,7 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu
|
||||
return mb2
|
||||
}
|
||||
|
||||
// XtlsPadding add padding to eliminate length siganature during tls handshake
|
||||
// XtlsPadding add padding to eliminate length signature during tls handshake
|
||||
func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer {
|
||||
var contentLen int32 = 0
|
||||
var paddingLen int32 = 0
|
||||
|
@@ -26,7 +26,7 @@ const (
|
||||
type AuthType int32
|
||||
|
||||
const (
|
||||
// NO_AUTH is for anounymous authentication.
|
||||
// NO_AUTH is for anonymous authentication.
|
||||
AuthType_NO_AUTH AuthType = 0
|
||||
// PASSWORD is for username/password authentication.
|
||||
AuthType_PASSWORD AuthType = 1
|
||||
|
@@ -17,7 +17,7 @@ message Account {
|
||||
|
||||
// AuthType is the authentication type of Socks proxy.
|
||||
enum AuthType {
|
||||
// NO_AUTH is for anounymous authentication.
|
||||
// NO_AUTH is for anonymous authentication.
|
||||
NO_AUTH = 0;
|
||||
// PASSWORD is for username/password authentication.
|
||||
PASSWORD = 1;
|
||||
|
@@ -68,7 +68,7 @@ func TestReadUsernamePassword(t *testing.T) {
|
||||
t.Error("for input: ", testCase.Input, " expect username ", testCase.Username, " but actually ", username)
|
||||
}
|
||||
if testCase.Password != password {
|
||||
t.Error("for input: ", testCase.Input, " expect passowrd ", testCase.Password, " but actually ", password)
|
||||
t.Error("for input: ", testCase.Input, " expect password ", testCase.Password, " but actually ", password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
In the sock implementation of * ray, UDP authentication is flawed and can be bypassed.
|
||||
Tracking a UDP connection may be a bit troublesome.
|
||||
Here is a simple solution.
|
||||
We creat a filter, add remote IP to the pool when it try to establish a UDP connection with auth.
|
||||
We create a filter, add remote IP to the pool when it try to establish a UDP connection with auth.
|
||||
And drop UDP packets from unauthorized IP.
|
||||
After discussion, we believe it is not necessary to add a timeout mechanism to this filter.
|
||||
*/
|
||||
|
@@ -124,7 +124,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
||||
return errors.New("failed to write A request payload").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
|
||||
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
|
||||
if err = bufferWriter.SetBuffered(false); err != nil {
|
||||
return errors.New("failed to flush payload").Base(err).AtWarning()
|
||||
}
|
||||
|
@@ -223,14 +223,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
cs := tlsConn.ConnectionState()
|
||||
name = cs.ServerName
|
||||
alpn = cs.NegotiatedProtocol
|
||||
errors.LogInfo(ctx, "realName = " + name)
|
||||
errors.LogInfo(ctx, "realAlpn = " + alpn)
|
||||
errors.LogInfo(ctx, "realName = "+name)
|
||||
errors.LogInfo(ctx, "realAlpn = "+alpn)
|
||||
} else if realityConn, ok := iConn.(*reality.Conn); ok {
|
||||
cs := realityConn.ConnectionState()
|
||||
name = cs.ServerName
|
||||
alpn = cs.NegotiatedProtocol
|
||||
errors.LogInfo(ctx, "realName = " + name)
|
||||
errors.LogInfo(ctx, "realAlpn = " + alpn)
|
||||
errors.LogInfo(ctx, "realName = "+name)
|
||||
errors.LogInfo(ctx, "realAlpn = "+alpn)
|
||||
}
|
||||
name = strings.ToLower(name)
|
||||
alpn = strings.ToLower(alpn)
|
||||
@@ -295,7 +295,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
}
|
||||
if k == '?' || k == ' ' {
|
||||
path = string(firstBytes[i:j])
|
||||
errors.LogInfo(ctx, "realPath = " + path)
|
||||
errors.LogInfo(ctx, "realPath = "+path)
|
||||
if pfb[path] == nil {
|
||||
path = ""
|
||||
}
|
||||
@@ -524,7 +524,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
clientReader = proxy.NewVisionReader(clientReader, trafficState, ctx1)
|
||||
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, ctx1)
|
||||
} else {
|
||||
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
|
||||
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
|
||||
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil {
|
||||
return err // ...
|
||||
}
|
||||
// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
|
||||
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
|
||||
if err := bufferWriter.SetBuffered(false); err != nil {
|
||||
return errors.New("failed to write A response payload").Base(err).AtWarning()
|
||||
}
|
||||
@@ -561,7 +561,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
if requestAddons.Flow == vless.XRV {
|
||||
err = encoding.XtlsWrite(serverReader, clientWriter, timer, connection, trafficState, nil, ctx)
|
||||
} else {
|
||||
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
|
||||
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
|
||||
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
|
||||
}
|
||||
if err != nil {
|
||||
|
@@ -219,7 +219,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
} else {
|
||||
errors.LogDebug(ctx, "Reader is not timeout reader, will send out vless header separately from first payload")
|
||||
}
|
||||
// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
|
||||
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
|
||||
if err := bufferWriter.SetBuffered(false); err != nil {
|
||||
return errors.New("failed to write A request payload").Base(err).AtWarning()
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
|
||||
err = encoding.XtlsWrite(clientReader, serverWriter, timer, conn, trafficState, ob, ctx1)
|
||||
} else {
|
||||
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
|
||||
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
|
||||
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
|
||||
}
|
||||
if err != nil {
|
||||
@@ -276,7 +276,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
if requestAddons.Flow == vless.XRV {
|
||||
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, ctx)
|
||||
} else {
|
||||
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
|
||||
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
|
||||
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
|
||||
}
|
||||
|
||||
|
@@ -100,7 +100,7 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
|
||||
}
|
||||
|
||||
// bind := conn.NewStdNetBind() // TODO: conn.Bind wrapper for dialer
|
||||
bind := &netBindClient{
|
||||
h.bind = &netBindClient{
|
||||
netBind: netBind{
|
||||
dns: h.dns,
|
||||
dnsOption: dns.IPOption{
|
||||
@@ -115,15 +115,14 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = bind.Close()
|
||||
_ = h.bind.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
h.net, err = h.makeVirtualTun(bind)
|
||||
h.net, err = h.makeVirtualTun()
|
||||
if err != nil {
|
||||
return errors.New("failed to create virtual tun interface").Base(err)
|
||||
}
|
||||
h.bind = bind
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -238,16 +237,16 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
}
|
||||
|
||||
// creates a tun interface on netstack given a configuration
|
||||
func (h *Handler) makeVirtualTun(bind *netBindClient) (Tunnel, error) {
|
||||
func (h *Handler) makeVirtualTun() (Tunnel, error) {
|
||||
t, err := h.conf.createTun()(h.endpoints, int(h.conf.Mtu), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bind.dnsOption.IPv4Enable = h.hasIPv4
|
||||
bind.dnsOption.IPv6Enable = h.hasIPv6
|
||||
h.bind.dnsOption.IPv4Enable = h.hasIPv4
|
||||
h.bind.dnsOption.IPv6Enable = h.hasIPv6
|
||||
|
||||
if err = t.BuildDevice(h.createIPCRequest(bind, h.conf), bind); err != nil {
|
||||
if err = t.BuildDevice(h.createIPCRequest(), h.bind); err != nil {
|
||||
_ = t.Close()
|
||||
return nil, err
|
||||
}
|
||||
@@ -255,17 +254,17 @@ func (h *Handler) makeVirtualTun(bind *netBindClient) (Tunnel, error) {
|
||||
}
|
||||
|
||||
// serialize the config into an IPC request
|
||||
func (h *Handler) createIPCRequest(bind *netBindClient, conf *DeviceConfig) string {
|
||||
func (h *Handler) createIPCRequest() string {
|
||||
var request strings.Builder
|
||||
|
||||
request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey))
|
||||
request.WriteString(fmt.Sprintf("private_key=%s\n", h.conf.SecretKey))
|
||||
|
||||
if !conf.IsClient {
|
||||
if !h.conf.IsClient {
|
||||
// placeholder, we'll handle actual port listening on Xray
|
||||
request.WriteString("listen_port=1337\n")
|
||||
}
|
||||
|
||||
for _, peer := range conf.Peers {
|
||||
for _, peer := range h.conf.Peers {
|
||||
if peer.PublicKey != "" {
|
||||
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
|
||||
}
|
||||
@@ -280,7 +279,7 @@ func (h *Handler) createIPCRequest(bind *netBindClient, conf *DeviceConfig) stri
|
||||
}
|
||||
addr := net.ParseAddress(address)
|
||||
if addr.Family().IsDomain() {
|
||||
dialerIp := bind.dialer.DestIpAddress()
|
||||
dialerIp := h.bind.dialer.DestIpAddress()
|
||||
if dialerIp != nil {
|
||||
addr = net.ParseAddress(dialerIp.String())
|
||||
errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
|
||||
|
121
transport/internet/browser_dialer/dialer.go
Normal file
121
transport/internet/browser_dialer/dialer.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package browser_dialer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
//go:embed dialer.html
|
||||
var webpage []byte
|
||||
|
||||
var conns chan *websocket.Conn
|
||||
|
||||
var upgrader = &websocket.Upgrader{
|
||||
ReadBufferSize: 0,
|
||||
WriteBufferSize: 0,
|
||||
HandshakeTimeout: time.Second * 4,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
addr := platform.NewEnvFlag(platform.BrowserDialerAddress).GetValue(func() string { return "" })
|
||||
if addr != "" {
|
||||
token := uuid.New()
|
||||
csrfToken := token.String()
|
||||
webpage = bytes.ReplaceAll(webpage, []byte("csrfToken"), []byte(csrfToken))
|
||||
conns = make(chan *websocket.Conn, 256)
|
||||
go http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/websocket" {
|
||||
if r.URL.Query().Get("token") == csrfToken {
|
||||
if conn, err := upgrader.Upgrade(w, r, nil); err == nil {
|
||||
conns <- conn
|
||||
} else {
|
||||
errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.Write(webpage)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func HasBrowserDialer() bool {
|
||||
return conns != nil
|
||||
}
|
||||
|
||||
func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
|
||||
data := []byte("WS " + uri)
|
||||
if ed != nil {
|
||||
data = append(data, " "+base64.RawURLEncoding.EncodeToString(ed)...)
|
||||
}
|
||||
|
||||
return dialRaw(data)
|
||||
}
|
||||
|
||||
func DialGet(uri string) (*websocket.Conn, error) {
|
||||
data := []byte("GET " + uri)
|
||||
return dialRaw(data)
|
||||
}
|
||||
|
||||
func DialPost(uri string, payload []byte) error {
|
||||
data := []byte("POST " + uri)
|
||||
conn, err := dialRaw(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(websocket.BinaryMessage, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = CheckOK(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func dialRaw(data []byte) (*websocket.Conn, error) {
|
||||
var conn *websocket.Conn
|
||||
for {
|
||||
conn = <-conns
|
||||
if conn.WriteMessage(websocket.TextMessage, data) != nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
err := CheckOK(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func CheckOK(conn *websocket.Conn) error {
|
||||
if _, p, err := conn.ReadMessage(); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
} else if s := string(p); s != "ok" {
|
||||
conn.Close()
|
||||
return errors.New(s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
136
transport/internet/browser_dialer/dialer.html
Normal file
136
transport/internet/browser_dialer/dialer.html
Normal file
@@ -0,0 +1,136 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Browser Dialer</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// Copyright (c) 2021 XRAY. Mozilla Public License 2.0.
|
||||
var url = "ws://" + window.location.host + "/websocket?token=csrfToken";
|
||||
var clientIdleCount = 0;
|
||||
var upstreamGetCount = 0;
|
||||
var upstreamWsCount = 0;
|
||||
var upstreamPostCount = 0;
|
||||
setInterval(check, 1000);
|
||||
function check() {
|
||||
if (clientIdleCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
clientIdleCount += 1;
|
||||
console.log("Prepare", url);
|
||||
var ws = new WebSocket(url);
|
||||
// arraybuffer is significantly faster in chrome than default
|
||||
// blob, tested with chrome 123
|
||||
ws.binaryType = "arraybuffer";
|
||||
ws.onmessage = function (event) {
|
||||
clientIdleCount -= 1;
|
||||
let [method, url, protocol] = event.data.split(" ");
|
||||
if (method == "WS") {
|
||||
upstreamWsCount += 1;
|
||||
console.log("Dial WS", url, protocol);
|
||||
const wss = new WebSocket(url, protocol);
|
||||
wss.binaryType = "arraybuffer";
|
||||
var opened = false;
|
||||
ws.onmessage = function (event) {
|
||||
wss.send(event.data)
|
||||
}
|
||||
wss.onopen = function (event) {
|
||||
opened = true;
|
||||
ws.send("ok")
|
||||
}
|
||||
wss.onmessage = function (event) {
|
||||
ws.send(event.data)
|
||||
}
|
||||
wss.onclose = function (event) {
|
||||
upstreamWsCount -= 1;
|
||||
console.log("Dial WS DONE, remaining: ", upstreamWsCount);
|
||||
ws.close()
|
||||
}
|
||||
wss.onerror = function (event) {
|
||||
!opened && ws.send("fail")
|
||||
wss.close()
|
||||
}
|
||||
ws.onclose = function (event) {
|
||||
wss.close()
|
||||
}
|
||||
} else if (method == "GET") {
|
||||
(async () => {
|
||||
console.log("Dial GET", url);
|
||||
ws.send("ok");
|
||||
const controller = new AbortController();
|
||||
|
||||
/*
|
||||
Aborting a streaming response in JavaScript
|
||||
requires two levers to be pulled:
|
||||
|
||||
First, the streaming read itself has to be cancelled using
|
||||
reader.cancel(), only then controller.abort() will actually work.
|
||||
|
||||
If controller.abort() alone is called while a
|
||||
reader.read() is ongoing, it will block until the server closes the
|
||||
response, the page is refreshed or the network connection is lost.
|
||||
*/
|
||||
|
||||
let reader = null;
|
||||
ws.onclose = (event) => {
|
||||
try {
|
||||
reader && reader.cancel();
|
||||
} catch(e) {}
|
||||
|
||||
try {
|
||||
controller.abort();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
try {
|
||||
upstreamGetCount += 1;
|
||||
const response = await fetch(url, {signal: controller.signal});
|
||||
|
||||
const body = await response.body;
|
||||
reader = body.getReader();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
ws.send(value);
|
||||
if (done) break;
|
||||
}
|
||||
} finally {
|
||||
upstreamGetCount -= 1;
|
||||
console.log("Dial GET DONE, remaining: ", upstreamGetCount);
|
||||
ws.close();
|
||||
}
|
||||
})()
|
||||
} else if (method == "POST") {
|
||||
upstreamPostCount += 1;
|
||||
console.log("Dial POST", url);
|
||||
ws.send("ok");
|
||||
ws.onmessage = async (event) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
url,
|
||||
{method: "POST", body: event.data}
|
||||
);
|
||||
if (response.ok) {
|
||||
ws.send("ok");
|
||||
} else {
|
||||
console.error("bad status code");
|
||||
ws.send("fail");
|
||||
}
|
||||
} finally {
|
||||
upstreamPostCount -= 1;
|
||||
console.log("Dial POST DONE, remaining: ", upstreamPostCount);
|
||||
ws.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check()
|
||||
}
|
||||
ws.onerror = function (event) {
|
||||
ws.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -207,7 +207,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
|
||||
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{3, 0}
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0}
|
||||
}
|
||||
|
||||
type TransportConfig struct {
|
||||
@@ -429,6 +429,77 @@ func (x *ProxyConfig) GetTransportLayerProxy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type CustomSockopt struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Level string `protobuf:"bytes,1,opt,name=level,proto3" json:"level,omitempty"`
|
||||
Opt string `protobuf:"bytes,2,opt,name=opt,proto3" json:"opt,omitempty"`
|
||||
Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) Reset() {
|
||||
*x = CustomSockopt{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CustomSockopt) ProtoMessage() {}
|
||||
|
||||
func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
|
||||
func (*CustomSockopt) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) GetLevel() string {
|
||||
if x != nil {
|
||||
return x.Level
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) GetOpt() string {
|
||||
if x != nil {
|
||||
return x.Opt
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CustomSockopt) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SocketConfig is options to be applied on network sockets.
|
||||
type SocketConfig struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -443,28 +514,29 @@ type SocketConfig struct {
|
||||
Tproxy SocketConfig_TProxyMode `protobuf:"varint,3,opt,name=tproxy,proto3,enum=xray.transport.internet.SocketConfig_TProxyMode" json:"tproxy,omitempty"`
|
||||
// ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket
|
||||
// option. This option is for UDP only.
|
||||
ReceiveOriginalDestAddress bool `protobuf:"varint,4,opt,name=receive_original_dest_address,json=receiveOriginalDestAddress,proto3" json:"receive_original_dest_address,omitempty"`
|
||||
BindAddress []byte `protobuf:"bytes,5,opt,name=bind_address,json=bindAddress,proto3" json:"bind_address,omitempty"`
|
||||
BindPort uint32 `protobuf:"varint,6,opt,name=bind_port,json=bindPort,proto3" json:"bind_port,omitempty"`
|
||||
AcceptProxyProtocol bool `protobuf:"varint,7,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"`
|
||||
DomainStrategy DomainStrategy `protobuf:"varint,8,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
|
||||
DialerProxy string `protobuf:"bytes,9,opt,name=dialer_proxy,json=dialerProxy,proto3" json:"dialer_proxy,omitempty"`
|
||||
TcpKeepAliveInterval int32 `protobuf:"varint,10,opt,name=tcp_keep_alive_interval,json=tcpKeepAliveInterval,proto3" json:"tcp_keep_alive_interval,omitempty"`
|
||||
TcpKeepAliveIdle int32 `protobuf:"varint,11,opt,name=tcp_keep_alive_idle,json=tcpKeepAliveIdle,proto3" json:"tcp_keep_alive_idle,omitempty"`
|
||||
TcpCongestion string `protobuf:"bytes,12,opt,name=tcp_congestion,json=tcpCongestion,proto3" json:"tcp_congestion,omitempty"`
|
||||
Interface string `protobuf:"bytes,13,opt,name=interface,proto3" json:"interface,omitempty"`
|
||||
V6Only bool `protobuf:"varint,14,opt,name=v6only,proto3" json:"v6only,omitempty"`
|
||||
TcpWindowClamp int32 `protobuf:"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3" json:"tcp_window_clamp,omitempty"`
|
||||
TcpUserTimeout int32 `protobuf:"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3" json:"tcp_user_timeout,omitempty"`
|
||||
TcpMaxSeg int32 `protobuf:"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3" json:"tcp_max_seg,omitempty"`
|
||||
TcpNoDelay bool `protobuf:"varint,18,opt,name=tcp_no_delay,json=tcpNoDelay,proto3" json:"tcp_no_delay,omitempty"`
|
||||
TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"`
|
||||
ReceiveOriginalDestAddress bool `protobuf:"varint,4,opt,name=receive_original_dest_address,json=receiveOriginalDestAddress,proto3" json:"receive_original_dest_address,omitempty"`
|
||||
BindAddress []byte `protobuf:"bytes,5,opt,name=bind_address,json=bindAddress,proto3" json:"bind_address,omitempty"`
|
||||
BindPort uint32 `protobuf:"varint,6,opt,name=bind_port,json=bindPort,proto3" json:"bind_port,omitempty"`
|
||||
AcceptProxyProtocol bool `protobuf:"varint,7,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"`
|
||||
DomainStrategy DomainStrategy `protobuf:"varint,8,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
|
||||
DialerProxy string `protobuf:"bytes,9,opt,name=dialer_proxy,json=dialerProxy,proto3" json:"dialer_proxy,omitempty"`
|
||||
TcpKeepAliveInterval int32 `protobuf:"varint,10,opt,name=tcp_keep_alive_interval,json=tcpKeepAliveInterval,proto3" json:"tcp_keep_alive_interval,omitempty"`
|
||||
TcpKeepAliveIdle int32 `protobuf:"varint,11,opt,name=tcp_keep_alive_idle,json=tcpKeepAliveIdle,proto3" json:"tcp_keep_alive_idle,omitempty"`
|
||||
TcpCongestion string `protobuf:"bytes,12,opt,name=tcp_congestion,json=tcpCongestion,proto3" json:"tcp_congestion,omitempty"`
|
||||
Interface string `protobuf:"bytes,13,opt,name=interface,proto3" json:"interface,omitempty"`
|
||||
V6Only bool `protobuf:"varint,14,opt,name=v6only,proto3" json:"v6only,omitempty"`
|
||||
TcpWindowClamp int32 `protobuf:"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3" json:"tcp_window_clamp,omitempty"`
|
||||
TcpUserTimeout int32 `protobuf:"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3" json:"tcp_user_timeout,omitempty"`
|
||||
TcpMaxSeg int32 `protobuf:"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3" json:"tcp_max_seg,omitempty"`
|
||||
TcpNoDelay bool `protobuf:"varint,18,opt,name=tcp_no_delay,json=tcpNoDelay,proto3" json:"tcp_no_delay,omitempty"`
|
||||
TcpMptcp bool `protobuf:"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3" json:"tcp_mptcp,omitempty"`
|
||||
CustomSockopt []*CustomSockopt `protobuf:"bytes,20,rep,name=customSockopt,proto3" json:"customSockopt,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SocketConfig) Reset() {
|
||||
*x = SocketConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[3]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -477,7 +549,7 @@ func (x *SocketConfig) String() string {
|
||||
func (*SocketConfig) ProtoMessage() {}
|
||||
|
||||
func (x *SocketConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_config_proto_msgTypes[3]
|
||||
mi := &file_transport_internet_config_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -490,7 +562,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
|
||||
func (*SocketConfig) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
|
||||
return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *SocketConfig) GetMark() int32 {
|
||||
@@ -626,6 +698,13 @@ func (x *SocketConfig) GetTcpMptcp() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SocketConfig) GetCustomSockopt() []*CustomSockopt {
|
||||
if x != nil {
|
||||
return x.CustomSockopt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_transport_internet_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_transport_internet_config_proto_rawDesc = []byte{
|
||||
@@ -678,85 +757,96 @@ var file_transport_internet_config_proto_rawDesc = []byte{
|
||||
0x12, 0x30, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79,
|
||||
0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74,
|
||||
0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f,
|
||||
0x78, 0x79, 0x22, 0xd1, 0x06, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 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, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74, 0x70, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f,
|
||||
0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65,
|
||||
0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x69,
|
||||
0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x69, 0x6e,
|
||||
0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x62, 0x69,
|
||||
0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74,
|
||||
0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f,
|
||||
0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f, 0x64, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x0e, 0x32, 0x27, 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, 0x44, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12,
|
||||
0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76,
|
||||
0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x49, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65,
|
||||
0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18, 0x0b, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76,
|
||||
0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e,
|
||||
0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74,
|
||||
0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x36,
|
||||
0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36, 0x6f, 0x6e,
|
||||
0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77,
|
||||
0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63,
|
||||
0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28, 0x0a, 0x10,
|
||||
0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
|
||||
0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65, 0x72, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x61,
|
||||
0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x63, 0x70,
|
||||
0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x63, 0x70, 0x5f, 0x6e, 0x6f,
|
||||
0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x63,
|
||||
0x70, 0x4e, 0x6f, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f,
|
||||
0x6d, 0x70, 0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70,
|
||||
0x4d, 0x70, 0x74, 0x63, 0x70, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d,
|
||||
0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06,
|
||||
0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69,
|
||||
0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x7a, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x54,
|
||||
0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x65, 0x62, 0x53, 0x6f,
|
||||
0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x04,
|
||||
0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74,
|
||||
0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x48, 0x54, 0x54, 0x50, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64,
|
||||
0x65, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x54, 0x54, 0x50,
|
||||
0x10, 0x07, 0x2a, 0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
|
||||
0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00,
|
||||
0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
|
||||
0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
|
||||
0x34, 0x36, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34,
|
||||
0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x06,
|
||||
0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07, 0x12,
|
||||
0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12, 0x0e,
|
||||
0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12, 0x0e,
|
||||
0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x42, 0x67,
|
||||
0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a,
|
||||
0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73,
|
||||
0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17,
|
||||
0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x78, 0x79, 0x22, 0x61, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b,
|
||||
0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x9f, 0x07, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66,
|
||||
0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06,
|
||||
0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 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, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06,
|
||||
0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
|
||||
0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72,
|
||||
0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65,
|
||||
0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e,
|
||||
0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x0b, 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63,
|
||||
0x65, 0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||
0x6f, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74,
|
||||
0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a,
|
||||
0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 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, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52,
|
||||
0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12,
|
||||
0x21, 0x0a, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18,
|
||||
0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f,
|
||||
0x78, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61,
|
||||
0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76,
|
||||
0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70,
|
||||
0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65,
|
||||
0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41,
|
||||
0x6c, 0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f,
|
||||
0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0d, 0x74, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x76, 0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76,
|
||||
0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e,
|
||||
0x64, 0x6f, 0x77, 0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52,
|
||||
0x0e, 0x74, 0x63, 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12,
|
||||
0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x6f, 0x75, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73,
|
||||
0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70,
|
||||
0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09,
|
||||
0x74, 0x63, 0x70, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x63, 0x70,
|
||||
0x5f, 0x6e, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x0a, 0x74, 0x63, 0x70, 0x4e, 0x6f, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74,
|
||||
0x63, 0x70, 0x5f, 0x6d, 0x70, 0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
|
||||
0x74, 0x63, 0x70, 0x4d, 0x70, 0x74, 0x63, 0x70, 0x12, 0x4c, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74,
|
||||
0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x26, 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, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
||||
0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53,
|
||||
0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64,
|
||||
0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x7a, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03,
|
||||
0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x65, 0x62, 0x53,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10,
|
||||
0x04, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65,
|
||||
0x74, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x48, 0x54, 0x54, 0x50, 0x55, 0x70, 0x67, 0x72, 0x61,
|
||||
0x64, 0x65, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x54, 0x54,
|
||||
0x50, 0x10, 0x07, 0x2a, 0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74,
|
||||
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10,
|
||||
0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53,
|
||||
0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49,
|
||||
0x50, 0x34, 0x36, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36,
|
||||
0x34, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10,
|
||||
0x06, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07,
|
||||
0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12,
|
||||
0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12,
|
||||
0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x42,
|
||||
0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
||||
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01,
|
||||
0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
|
||||
0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e,
|
||||
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02,
|
||||
0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -772,7 +862,7 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_transport_internet_config_proto_goTypes = []interface{}{
|
||||
(TransportProtocol)(0), // 0: xray.transport.internet.TransportProtocol
|
||||
(DomainStrategy)(0), // 1: xray.transport.internet.DomainStrategy
|
||||
@@ -780,23 +870,25 @@ var file_transport_internet_config_proto_goTypes = []interface{}{
|
||||
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
|
||||
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
|
||||
(*ProxyConfig)(nil), // 5: xray.transport.internet.ProxyConfig
|
||||
(*SocketConfig)(nil), // 6: xray.transport.internet.SocketConfig
|
||||
(*serial.TypedMessage)(nil), // 7: xray.common.serial.TypedMessage
|
||||
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt
|
||||
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig
|
||||
(*serial.TypedMessage)(nil), // 8: xray.common.serial.TypedMessage
|
||||
}
|
||||
var file_transport_internet_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.transport.internet.TransportConfig.protocol:type_name -> xray.transport.internet.TransportProtocol
|
||||
7, // 1: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
||||
8, // 1: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
|
||||
0, // 2: xray.transport.internet.StreamConfig.protocol:type_name -> xray.transport.internet.TransportProtocol
|
||||
3, // 3: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
|
||||
7, // 4: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||
6, // 5: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||
8, // 4: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
|
||||
7, // 5: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
|
||||
2, // 6: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
|
||||
1, // 7: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
6, // 8: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_config_proto_init() }
|
||||
@@ -842,6 +934,18 @@ func file_transport_internet_config_proto_init() {
|
||||
}
|
||||
}
|
||||
file_transport_internet_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CustomSockopt); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SocketConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -860,7 +964,7 @@ func file_transport_internet_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_transport_internet_config_proto_rawDesc,
|
||||
NumEnums: 3,
|
||||
NumMessages: 4,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
@@ -68,6 +68,13 @@ message ProxyConfig {
|
||||
bool transportLayerProxy = 2;
|
||||
}
|
||||
|
||||
message CustomSockopt {
|
||||
string level = 1;
|
||||
string opt = 2;
|
||||
string value = 3;
|
||||
string type = 4;
|
||||
}
|
||||
|
||||
// SocketConfig is options to be applied on network sockets.
|
||||
message SocketConfig {
|
||||
// Mark of the connection. If non-zero, the value will be set to SO_MARK.
|
||||
@@ -121,4 +128,6 @@ message SocketConfig {
|
||||
bool tcp_no_delay = 18;
|
||||
|
||||
bool tcp_mptcp = 19;
|
||||
|
||||
repeated CustomSockopt customSockopt = 20;
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ type Config struct {
|
||||
// Path of the domain socket. This overrides the IP/Port parameter from
|
||||
// upstream caller.
|
||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||
// Abstract speicifies whether to use abstract namespace or not.
|
||||
// Abstract specifies whether to use abstract namespace or not.
|
||||
// Traditionally Unix domain socket is file system based. Abstract domain
|
||||
// socket can be used without acquiring file lock.
|
||||
Abstract bool `protobuf:"varint,2,opt,name=abstract,proto3" json:"abstract,omitempty"`
|
||||
|
@@ -10,7 +10,7 @@ message Config {
|
||||
// Path of the domain socket. This overrides the IP/Port parameter from
|
||||
// upstream caller.
|
||||
string path = 1;
|
||||
// Abstract speicifies whether to use abstract namespace or not.
|
||||
// Abstract specifies whether to use abstract namespace or not.
|
||||
// Traditionally Unix domain socket is file system based. Abstract domain
|
||||
// socket can be used without acquiring file lock.
|
||||
bool abstract = 2;
|
||||
|
@@ -374,7 +374,7 @@ type Config struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Settings for authenticating requests. If not set, client side will not send
|
||||
// authenication header, and server side will bypass authentication.
|
||||
// authentication header, and server side will bypass authentication.
|
||||
Request *RequestConfig `protobuf:"bytes,1,opt,name=request,proto3" json:"request,omitempty"`
|
||||
// Settings for authenticating responses. If not set, client side will bypass
|
||||
// authentication, and server side will not send authentication header.
|
||||
|
@@ -56,7 +56,7 @@ message ResponseConfig {
|
||||
|
||||
message Config {
|
||||
// Settings for authenticating requests. If not set, client side will not send
|
||||
// authenication header, and server side will bypass authentication.
|
||||
// authentication header, and server side will bypass authentication.
|
||||
RequestConfig request = 1;
|
||||
|
||||
// Settings for authenticating responses. If not set, client side will bypass
|
||||
|
@@ -16,9 +16,12 @@ func (c *Config) getHosts() []string {
|
||||
}
|
||||
|
||||
func (c *Config) isValidHost(host string) bool {
|
||||
if len(c.Host) == 0 {
|
||||
return true
|
||||
}
|
||||
hosts := c.getHosts()
|
||||
for _, h := range hosts {
|
||||
if h == host {
|
||||
if internet.IsValidHTTPHost(host, h) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@@ -2,18 +2,18 @@ package httpupgrade
|
||||
|
||||
import "net"
|
||||
|
||||
type connnection struct {
|
||||
type connection struct {
|
||||
net.Conn
|
||||
remoteAddr net.Addr
|
||||
}
|
||||
|
||||
func newConnection(conn net.Conn, remoteAddr net.Addr) *connnection {
|
||||
return &connnection{
|
||||
func newConnection(conn net.Conn, remoteAddr net.Addr) *connection {
|
||||
return &connection{
|
||||
Conn: conn,
|
||||
remoteAddr: remoteAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connnection) RemoteAddr() net.Addr {
|
||||
func (c *connection) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
@@ -58,7 +58,6 @@ func Test_listenHTTPUpgradeAndDial(t *testing.T) {
|
||||
}
|
||||
|
||||
common.Must(conn.Close())
|
||||
<-time.After(time.Second * 5)
|
||||
conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 2"))
|
||||
@@ -118,7 +117,6 @@ func Test_listenHTTPUpgradeAndDialWithHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
common.Must(conn.Close())
|
||||
<-time.After(time.Second * 5)
|
||||
conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 2"))
|
||||
@@ -151,7 +149,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.Write([]byte("Response"))
|
||||
_, err = c.Write([]byte(c.RemoteAddr().String()))
|
||||
common.Must(err)
|
||||
}(conn)
|
||||
})
|
||||
@@ -169,7 +167,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
var b [1024]byte
|
||||
n, err := conn.Read(b[:])
|
||||
common.Must(err)
|
||||
if string(b[:n]) != "Response" {
|
||||
if string(b[:n]) != "1.1.1.1:0" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,7 @@ func (s *server) Handle(conn net.Conn) (stat.Connection, error) {
|
||||
|
||||
if s.config != nil {
|
||||
host := req.Host
|
||||
if len(s.config.Host) > 0 && host != s.config.Host {
|
||||
if len(s.config.Host) > 0 && !internet.IsValidHTTPHost(host, s.config.Host) {
|
||||
return nil, errors.New("bad host: ", host)
|
||||
}
|
||||
path := s.config.GetNormalizedPath()
|
||||
|
@@ -1,3 +1,18 @@
|
||||
package internet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
func IsValidHTTPHost(request string, config string) bool {
|
||||
r := strings.ToLower(request)
|
||||
c := strings.ToLower(config)
|
||||
if strings.Contains(r, ":") {
|
||||
h, _, _ := net.SplitHostPort(r)
|
||||
return h == c
|
||||
}
|
||||
return r == c
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package internet
|
||||
|
||||
// MemoryStreamConfig is a parsed form of StreamConfig. This is used to reduce number of Protobuf parsing.
|
||||
// MemoryStreamConfig is a parsed form of StreamConfig. This is used to reduce the number of Protobuf parsings.
|
||||
type MemoryStreamConfig struct {
|
||||
ProtocolName string
|
||||
ProtocolSettings interface{}
|
||||
|
@@ -2,6 +2,7 @@ package internet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -107,7 +108,32 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
||||
return errors.New("failed to set TCP_NODELAY", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.CustomSockopt) > 0 {
|
||||
for _, custom := range config.CustomSockopt {
|
||||
var level = 0x6 // default TCP
|
||||
var opt int
|
||||
if len(custom.Opt) == 0 {
|
||||
return errors.New("No opt!")
|
||||
} else {
|
||||
opt, _ = strconv.Atoi(custom.Opt)
|
||||
}
|
||||
if custom.Level != "" {
|
||||
level, _ = strconv.Atoi(custom.Level)
|
||||
}
|
||||
if custom.Type == "int" {
|
||||
value, _ := strconv.Atoi(custom.Value)
|
||||
if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
|
||||
return errors.New("failed to set CustomSockoptInt", opt, value, err)
|
||||
}
|
||||
} else if custom.Type == "str" {
|
||||
if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
|
||||
return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("unknown CustomSockopt type:", custom.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Tproxy.IsEnabled() {
|
||||
@@ -176,6 +202,32 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
|
||||
return errors.New("failed to set TCP_MAXSEG", err)
|
||||
}
|
||||
}
|
||||
if len(config.CustomSockopt) > 0 {
|
||||
for _, custom := range config.CustomSockopt {
|
||||
var level = 0x6 // default TCP
|
||||
var opt int
|
||||
if len(custom.Opt) == 0 {
|
||||
return errors.New("No opt!")
|
||||
} else {
|
||||
opt, _ = strconv.Atoi(custom.Opt)
|
||||
}
|
||||
if custom.Level != "" {
|
||||
level, _ = strconv.Atoi(custom.Level)
|
||||
}
|
||||
if custom.Type == "int" {
|
||||
value, _ := strconv.Atoi(custom.Value)
|
||||
if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
|
||||
return errors.New("failed to set CustomSockoptInt", opt, value, err)
|
||||
}
|
||||
} else if custom.Type == "str" {
|
||||
if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
|
||||
return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("unknown CustomSockopt type:", custom.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Tproxy.IsEnabled() {
|
||||
|
39
transport/internet/splithttp/browser_client.go
Normal file
39
transport/internet/splithttp/browser_client.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package splithttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
gonet "net"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||
"github.com/xtls/xray-core/transport/internet/websocket"
|
||||
)
|
||||
|
||||
// implements splithttp.DialerClient in terms of browser dialer
|
||||
// has no fields because everything is global state :O)
|
||||
type BrowserDialerClient struct{}
|
||||
|
||||
func (c *BrowserDialerClient) OpenDownload(ctx context.Context, baseURL string) (io.ReadCloser, gonet.Addr, gonet.Addr, error) {
|
||||
conn, err := browser_dialer.DialGet(baseURL)
|
||||
dummyAddr := &gonet.IPAddr{}
|
||||
if err != nil {
|
||||
return nil, dummyAddr, dummyAddr, err
|
||||
}
|
||||
|
||||
return websocket.NewConnection(conn, dummyAddr, nil), conn.RemoteAddr(), conn.LocalAddr(), nil
|
||||
}
|
||||
|
||||
func (c *BrowserDialerClient) SendUploadRequest(ctx context.Context, url string, payload io.ReadWriteCloser, contentLength int64) error {
|
||||
bytes, err := ioutil.ReadAll(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = browser_dialer.DialPost(url, bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
174
transport/internet/splithttp/client.go
Normal file
174
transport/internet/splithttp/client.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package splithttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
gonet "net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"sync"
|
||||
|
||||
"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/signal/done"
|
||||
)
|
||||
|
||||
// interface to abstract between use of browser dialer, vs net/http
|
||||
type DialerClient interface {
|
||||
// (ctx, baseURL, payload) -> err
|
||||
// baseURL already contains sessionId and seq
|
||||
SendUploadRequest(context.Context, string, io.ReadWriteCloser, int64) error
|
||||
|
||||
// (ctx, baseURL) -> (downloadReader, remoteAddr, localAddr)
|
||||
// baseURL already contains sessionId
|
||||
OpenDownload(context.Context, string) (io.ReadCloser, net.Addr, net.Addr, error)
|
||||
}
|
||||
|
||||
// implements splithttp.DialerClient in terms of direct network connections
|
||||
type DefaultDialerClient struct {
|
||||
transportConfig *Config
|
||||
download *http.Client
|
||||
upload *http.Client
|
||||
isH2 bool
|
||||
isH3 bool
|
||||
// pool of net.Conn, created using dialUploadConn
|
||||
uploadRawPool *sync.Pool
|
||||
dialUploadConn func(ctxInner context.Context) (net.Conn, error)
|
||||
}
|
||||
|
||||
func (c *DefaultDialerClient) OpenDownload(ctx context.Context, baseURL string) (io.ReadCloser, gonet.Addr, gonet.Addr, error) {
|
||||
var remoteAddr gonet.Addr
|
||||
var localAddr gonet.Addr
|
||||
// this is done when the TCP/UDP connection to the server was established,
|
||||
// and we can unblock the Dial function and print correct net addresses in
|
||||
// logs
|
||||
gotConn := done.New()
|
||||
|
||||
var downResponse io.ReadCloser
|
||||
gotDownResponse := done.New()
|
||||
|
||||
go func() {
|
||||
trace := &httptrace.ClientTrace{
|
||||
GotConn: func(connInfo httptrace.GotConnInfo) {
|
||||
remoteAddr = connInfo.Conn.RemoteAddr()
|
||||
localAddr = connInfo.Conn.LocalAddr()
|
||||
gotConn.Close()
|
||||
},
|
||||
}
|
||||
|
||||
// in case we hit an error, we want to unblock this part
|
||||
defer gotConn.Close()
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
httptrace.WithClientTrace(ctx, trace),
|
||||
"GET",
|
||||
baseURL,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to construct download http request")
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
|
||||
req.Header = c.transportConfig.GetRequestHeader()
|
||||
|
||||
response, err := c.download.Do(req)
|
||||
gotConn.Close()
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to send download http request")
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if response.StatusCode != 200 {
|
||||
response.Body.Close()
|
||||
errors.LogInfo(ctx, "invalid status code on download:", response.Status)
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
|
||||
downResponse = response.Body
|
||||
gotDownResponse.Close()
|
||||
}()
|
||||
|
||||
if c.isH3 {
|
||||
gotConn.Close()
|
||||
}
|
||||
|
||||
// we want to block Dial until we know the remote address of the server,
|
||||
// for logging purposes
|
||||
<-gotConn.Wait()
|
||||
|
||||
lazyDownload := &LazyReader{
|
||||
CreateReader: func() (io.ReadCloser, error) {
|
||||
<-gotDownResponse.Wait()
|
||||
if downResponse == nil {
|
||||
return nil, errors.New("downResponse failed")
|
||||
}
|
||||
return downResponse, nil
|
||||
},
|
||||
}
|
||||
|
||||
return lazyDownload, remoteAddr, localAddr, nil
|
||||
}
|
||||
|
||||
func (c *DefaultDialerClient) SendUploadRequest(ctx context.Context, url string, payload io.ReadWriteCloser, contentLength int64) error {
|
||||
req, err := http.NewRequest("POST", url, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = contentLength
|
||||
req.Header = c.transportConfig.GetRequestHeader()
|
||||
|
||||
if c.isH2 || c.isH3 {
|
||||
resp, err := c.upload.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("bad status code:", resp.Status)
|
||||
}
|
||||
} else {
|
||||
// stringify the entire HTTP/1.1 request so it can be
|
||||
// safely retried. if instead req.Write is called multiple
|
||||
// times, the body is already drained after the first
|
||||
// request
|
||||
requestBytes := new(bytes.Buffer)
|
||||
common.Must(req.Write(requestBytes))
|
||||
|
||||
var uploadConn any
|
||||
|
||||
for {
|
||||
uploadConn = c.uploadRawPool.Get()
|
||||
newConnection := uploadConn == nil
|
||||
if newConnection {
|
||||
uploadConn, err = c.dialUploadConn(context.WithoutCancel(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = uploadConn.(net.Conn).Write(requestBytes.Bytes())
|
||||
|
||||
// if the write failed, we try another connection from
|
||||
// the pool, until the write on a new connection fails.
|
||||
// failed writes to a pooled connection are normal when
|
||||
// the connection has been closed in the meantime.
|
||||
if err == nil {
|
||||
break
|
||||
} else if newConnection {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.uploadRawPool.Put(uploadConn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,24 +1,31 @@
|
||||
package splithttp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
func (c *Config) GetNormalizedPath() string {
|
||||
path := c.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
func (c *Config) GetNormalizedPath(addPath string, addQuery bool) string {
|
||||
pathAndQuery := strings.SplitN(c.Path, "?", 2)
|
||||
path := pathAndQuery[0]
|
||||
query := ""
|
||||
if len(pathAndQuery) > 1 && addQuery {
|
||||
query = "?" + pathAndQuery[1]
|
||||
}
|
||||
if path[0] != '/' {
|
||||
|
||||
if path == "" || path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
if path[len(path)-1] != '/' {
|
||||
path = path + "/"
|
||||
}
|
||||
return path
|
||||
|
||||
return path + addPath + query
|
||||
}
|
||||
|
||||
func (c *Config) GetRequestHeader() http.Header {
|
||||
@@ -29,20 +36,37 @@ func (c *Config) GetRequestHeader() http.Header {
|
||||
return header
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedMaxConcurrentUploads() int32 {
|
||||
if c.MaxConcurrentUploads == 0 {
|
||||
return 10
|
||||
func (c *Config) GetNormalizedScMaxConcurrentPosts() RandRangeConfig {
|
||||
if c.ScMaxConcurrentPosts == nil || c.ScMaxConcurrentPosts.To == 0 {
|
||||
return RandRangeConfig{
|
||||
From: 100,
|
||||
To: 100,
|
||||
}
|
||||
}
|
||||
|
||||
return c.MaxConcurrentUploads
|
||||
return *c.ScMaxConcurrentPosts
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedMaxUploadSize() int32 {
|
||||
if c.MaxUploadSize == 0 {
|
||||
return 1000000
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() RandRangeConfig {
|
||||
if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
|
||||
return RandRangeConfig{
|
||||
From: 1000000,
|
||||
To: 1000000,
|
||||
}
|
||||
}
|
||||
|
||||
return c.MaxUploadSize
|
||||
return *c.ScMaxEachPostBytes
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMinPostsIntervalMs() RandRangeConfig {
|
||||
if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
|
||||
return RandRangeConfig{
|
||||
From: 30,
|
||||
To: 30,
|
||||
}
|
||||
}
|
||||
|
||||
return *c.ScMinPostsIntervalMs
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -50,3 +74,11 @@ func init() {
|
||||
return new(Config)
|
||||
}))
|
||||
}
|
||||
|
||||
func (c RandRangeConfig) roll() int32 {
|
||||
if c.From == c.To {
|
||||
return c.From
|
||||
}
|
||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(c.To-c.From)))
|
||||
return c.From + int32(bigInt.Int64())
|
||||
}
|
||||
|
@@ -28,8 +28,10 @@ type Config struct {
|
||||
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
|
||||
Header map[string]string `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
MaxConcurrentUploads int32 `protobuf:"varint,4,opt,name=maxConcurrentUploads,proto3" json:"maxConcurrentUploads,omitempty"`
|
||||
MaxUploadSize int32 `protobuf:"varint,5,opt,name=maxUploadSize,proto3" json:"maxUploadSize,omitempty"`
|
||||
ScMaxConcurrentPosts *RandRangeConfig `protobuf:"bytes,4,opt,name=scMaxConcurrentPosts,proto3" json:"scMaxConcurrentPosts,omitempty"`
|
||||
ScMaxEachPostBytes *RandRangeConfig `protobuf:"bytes,5,opt,name=scMaxEachPostBytes,proto3" json:"scMaxEachPostBytes,omitempty"`
|
||||
ScMinPostsIntervalMs *RandRangeConfig `protobuf:"bytes,6,opt,name=scMinPostsIntervalMs,proto3" json:"scMinPostsIntervalMs,omitempty"`
|
||||
NoSSEHeader bool `protobuf:"varint,7,opt,name=noSSEHeader,proto3" json:"noSSEHeader,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
@@ -85,16 +87,85 @@ func (x *Config) GetHeader() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxConcurrentUploads() int32 {
|
||||
func (x *Config) GetScMaxConcurrentPosts() *RandRangeConfig {
|
||||
if x != nil {
|
||||
return x.MaxConcurrentUploads
|
||||
return x.ScMaxConcurrentPosts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetScMaxEachPostBytes() *RandRangeConfig {
|
||||
if x != nil {
|
||||
return x.ScMaxEachPostBytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetScMinPostsIntervalMs() *RandRangeConfig {
|
||||
if x != nil {
|
||||
return x.ScMinPostsIntervalMs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetNoSSEHeader() bool {
|
||||
if x != nil {
|
||||
return x.NoSSEHeader
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type RandRangeConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
From int32 `protobuf:"varint,1,opt,name=from,proto3" json:"from,omitempty"`
|
||||
To int32 `protobuf:"varint,2,opt,name=to,proto3" json:"to,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RandRangeConfig) Reset() {
|
||||
*x = RandRangeConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_splithttp_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RandRangeConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RandRangeConfig) ProtoMessage() {}
|
||||
|
||||
func (x *RandRangeConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_splithttp_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RandRangeConfig.ProtoReflect.Descriptor instead.
|
||||
func (*RandRangeConfig) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RandRangeConfig) GetFrom() int32 {
|
||||
if x != nil {
|
||||
return x.From
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxUploadSize() int32 {
|
||||
func (x *RandRangeConfig) GetTo() int32 {
|
||||
if x != nil {
|
||||
return x.MaxUploadSize
|
||||
return x.To
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -106,8 +177,8 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x22, 0x94,
|
||||
0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x22, 0x86,
|
||||
0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73,
|
||||
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
|
||||
0x68, 0x12, 0x4d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
@@ -115,25 +186,43 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{
|
||||
0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69,
|
||||
0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x65, 0x61,
|
||||
0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
|
||||
0x12, 0x32, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||
0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14,
|
||||
0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x6c,
|
||||
0x6f, 0x61, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61,
|
||||
0x64, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78,
|
||||
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65,
|
||||
0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x85, 0x01, 0x0a, 0x25, 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, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x50,
|
||||
0x01, 0x5a, 0x36, 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,
|
||||
0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79,
|
||||
0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x66, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||
0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32,
|
||||
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, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74,
|
||||
0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||
0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x12, 0x5a, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x55,
|
||||
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x32, 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, 0x73, 0x70, 0x6c, 0x69, 0x74,
|
||||
0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x42,
|
||||
0x79, 0x74, 0x65, 0x73, 0x12, 0x64, 0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61,
|
||||
0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x32, 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, 0x73, 0x70, 0x6c, 0x69,
|
||||
0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64,
|
||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x6f,
|
||||
0x53, 0x53, 0x45, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x0b, 0x6e, 0x6f, 0x53, 0x53, 0x45, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x39, 0x0a, 0x0b,
|
||||
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x35, 0x0a, 0x0f, 0x52, 0x61, 0x6e, 0x64, 0x52,
|
||||
0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x74, 0x6f, 0x42, 0x85,
|
||||
0x01, 0x0a, 0x25, 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, 0x73,
|
||||
0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36, 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, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74,
|
||||
0x74, 0x70, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c,
|
||||
0x69, 0x74, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -148,18 +237,22 @@ func file_transport_internet_splithttp_config_proto_rawDescGZIP() []byte {
|
||||
return file_transport_internet_splithttp_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_splithttp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_transport_internet_splithttp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_transport_internet_splithttp_config_proto_goTypes = []interface{}{
|
||||
(*Config)(nil), // 0: xray.transport.internet.splithttp.Config
|
||||
nil, // 1: xray.transport.internet.splithttp.Config.HeaderEntry
|
||||
(*Config)(nil), // 0: xray.transport.internet.splithttp.Config
|
||||
(*RandRangeConfig)(nil), // 1: xray.transport.internet.splithttp.RandRangeConfig
|
||||
nil, // 2: xray.transport.internet.splithttp.Config.HeaderEntry
|
||||
}
|
||||
var file_transport_internet_splithttp_config_proto_depIdxs = []int32{
|
||||
1, // 0: xray.transport.internet.splithttp.Config.header:type_name -> xray.transport.internet.splithttp.Config.HeaderEntry
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
2, // 0: xray.transport.internet.splithttp.Config.header:type_name -> xray.transport.internet.splithttp.Config.HeaderEntry
|
||||
1, // 1: xray.transport.internet.splithttp.Config.scMaxConcurrentPosts:type_name -> xray.transport.internet.splithttp.RandRangeConfig
|
||||
1, // 2: xray.transport.internet.splithttp.Config.scMaxEachPostBytes:type_name -> xray.transport.internet.splithttp.RandRangeConfig
|
||||
1, // 3: xray.transport.internet.splithttp.Config.scMinPostsIntervalMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_splithttp_config_proto_init() }
|
||||
@@ -180,6 +273,18 @@ func file_transport_internet_splithttp_config_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_splithttp_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RandRangeConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -187,7 +292,7 @@ func file_transport_internet_splithttp_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_transport_internet_splithttp_config_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
@@ -10,6 +10,13 @@ message Config {
|
||||
string host = 1;
|
||||
string path = 2;
|
||||
map<string, string> header = 3;
|
||||
int32 maxConcurrentUploads = 4;
|
||||
int32 maxUploadSize = 5;
|
||||
RandRangeConfig scMaxConcurrentPosts = 4;
|
||||
RandRangeConfig scMaxEachPostBytes = 5;
|
||||
RandRangeConfig scMinPostsIntervalMs = 6;
|
||||
bool noSSEHeader = 7;
|
||||
}
|
||||
|
||||
message RandRangeConfig {
|
||||
int32 from = 1;
|
||||
int32 to = 2;
|
||||
}
|
||||
|
51
transport/internet/splithttp/config_test.go
Normal file
51
transport/internet/splithttp/config_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package splithttp_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
||||
)
|
||||
|
||||
func Test_GetNormalizedPath(t *testing.T) {
|
||||
c := Config{
|
||||
Path: "/?world",
|
||||
}
|
||||
|
||||
path := c.GetNormalizedPath("hello", true)
|
||||
if path != "/hello?world" {
|
||||
t.Error("Unexpected: ", path)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetNormalizedPath2(t *testing.T) {
|
||||
c := Config{
|
||||
Path: "?world",
|
||||
}
|
||||
|
||||
path := c.GetNormalizedPath("hello", true)
|
||||
if path != "/hello?world" {
|
||||
t.Error("Unexpected: ", path)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetNormalizedPath3(t *testing.T) {
|
||||
c := Config{
|
||||
Path: "hello?world",
|
||||
}
|
||||
|
||||
path := c.GetNormalizedPath("", true)
|
||||
if path != "/hello/?world" {
|
||||
t.Error("Unexpected: ", path)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetNormalizedPath4(t *testing.T) {
|
||||
c := Config{
|
||||
Path: "hello?world",
|
||||
}
|
||||
|
||||
path := c.GetNormalizedPath("", false)
|
||||
if path != "/hello/" {
|
||||
t.Error("Unexpected: ", path)
|
||||
}
|
||||
}
|
@@ -1,26 +1,25 @@
|
||||
package splithttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"io"
|
||||
gonet "net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/common/signal/semaphore"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
@@ -32,34 +31,33 @@ type dialerConf struct {
|
||||
*internet.MemoryStreamConfig
|
||||
}
|
||||
|
||||
type reusedClient struct {
|
||||
download *http.Client
|
||||
upload *http.Client
|
||||
isH2 bool
|
||||
// pool of net.Conn, created using dialUploadConn
|
||||
uploadRawPool *sync.Pool
|
||||
dialUploadConn func(ctxInner context.Context) (net.Conn, error)
|
||||
}
|
||||
|
||||
var (
|
||||
globalDialerMap map[dialerConf]reusedClient
|
||||
globalDialerMap map[dialerConf]DialerClient
|
||||
globalDialerAccess sync.Mutex
|
||||
)
|
||||
|
||||
func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) reusedClient {
|
||||
globalDialerAccess.Lock()
|
||||
defer globalDialerAccess.Unlock()
|
||||
|
||||
if globalDialerMap == nil {
|
||||
globalDialerMap = make(map[dialerConf]reusedClient)
|
||||
}
|
||||
|
||||
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found {
|
||||
return client
|
||||
func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {
|
||||
if browser_dialer.HasBrowserDialer() {
|
||||
return &BrowserDialerClient{}
|
||||
}
|
||||
|
||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||
isH2 := tlsConfig != nil && !(len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "http/1.1")
|
||||
isH3 := tlsConfig != nil && (len(tlsConfig.NextProtocol) == 1 && tlsConfig.NextProtocol[0] == "h3")
|
||||
|
||||
globalDialerAccess.Lock()
|
||||
defer globalDialerAccess.Unlock()
|
||||
|
||||
if globalDialerMap == nil {
|
||||
globalDialerMap = make(map[dialerConf]DialerClient)
|
||||
}
|
||||
|
||||
if isH3 {
|
||||
dest.Network = net.Network_UDP
|
||||
}
|
||||
if client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found {
|
||||
return client
|
||||
}
|
||||
|
||||
var gotlsConfig *gotls.Config
|
||||
|
||||
@@ -87,10 +85,48 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
var uploadTransport http.RoundTripper
|
||||
var downloadTransport http.RoundTripper
|
||||
var uploadTransport http.RoundTripper
|
||||
|
||||
if isH2 {
|
||||
if isH3 {
|
||||
roundTripper := &http3.RoundTripper{
|
||||
TLSClientConfig: gotlsConfig,
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var udpConn *net.UDPConn
|
||||
var udpAddr *net.UDPAddr
|
||||
|
||||
switch c := conn.(type) {
|
||||
case *internet.PacketConnWrapper:
|
||||
var ok bool
|
||||
udpConn, ok = c.Conn.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("PacketConnWrapper does not contain a UDP connection")
|
||||
}
|
||||
udpAddr, err = net.ResolveUDPAddr("udp", c.Dest.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *net.UDPConn:
|
||||
udpConn = c
|
||||
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported connection type: %T", conn)
|
||||
}
|
||||
|
||||
return quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
|
||||
},
|
||||
}
|
||||
downloadTransport = roundTripper
|
||||
uploadTransport = roundTripper
|
||||
} else if isH2 {
|
||||
downloadTransport = &http2.Transport{
|
||||
DialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {
|
||||
return dialContext(ctxInner)
|
||||
@@ -111,12 +147,12 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
// http.Client and our custom dial context.
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
// we use uploadRawPool for that
|
||||
uploadTransport = nil
|
||||
}
|
||||
|
||||
client := reusedClient{
|
||||
client := &DefaultDialerClient{
|
||||
transportConfig: streamSettings.ProtocolSettings.(*Config),
|
||||
download: &http.Client{
|
||||
Transport: downloadTransport,
|
||||
},
|
||||
@@ -124,6 +160,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
Transport: uploadTransport,
|
||||
},
|
||||
isH2: isH2,
|
||||
isH3: isH3,
|
||||
uploadRawPool: &sync.Pool{},
|
||||
dialUploadConn: dialContext,
|
||||
}
|
||||
@@ -144,8 +181,9 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
transportConfiguration := streamSettings.ProtocolSettings.(*Config)
|
||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||
|
||||
maxConcurrentUploads := transportConfiguration.GetNormalizedMaxConcurrentUploads()
|
||||
maxUploadSize := transportConfiguration.GetNormalizedMaxUploadSize()
|
||||
scMaxConcurrentPosts := transportConfiguration.GetNormalizedScMaxConcurrentPosts()
|
||||
scMaxEachPostBytes := transportConfiguration.GetNormalizedScMaxEachPostBytes()
|
||||
scMinPostsIntervalMs := transportConfiguration.GetNormalizedScMinPostsIntervalMs()
|
||||
|
||||
if tlsConfig != nil {
|
||||
requestURL.Scheme = "https"
|
||||
@@ -156,91 +194,21 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
if requestURL.Host == "" {
|
||||
requestURL.Host = dest.NetAddr()
|
||||
}
|
||||
requestURL.Path = transportConfiguration.GetNormalizedPath()
|
||||
|
||||
sessionIdUuid := uuid.New()
|
||||
requestURL.Path = transportConfiguration.GetNormalizedPath(sessionIdUuid.String(), true)
|
||||
baseURL := requestURL.String()
|
||||
|
||||
httpClient := getHTTPClient(ctx, dest, streamSettings)
|
||||
|
||||
var remoteAddr gonet.Addr
|
||||
var localAddr gonet.Addr
|
||||
// this is done when the TCP/UDP connection to the server was established,
|
||||
// and we can unblock the Dial function and print correct net addresses in
|
||||
// logs
|
||||
gotConn := done.New()
|
||||
|
||||
var downResponse io.ReadCloser
|
||||
gotDownResponse := done.New()
|
||||
|
||||
sessionIdUuid := uuid.New()
|
||||
sessionId := sessionIdUuid.String()
|
||||
uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(scMaxEachPostBytes.roll()))
|
||||
|
||||
go func() {
|
||||
trace := &httptrace.ClientTrace{
|
||||
GotConn: func(connInfo httptrace.GotConnInfo) {
|
||||
remoteAddr = connInfo.Conn.RemoteAddr()
|
||||
localAddr = connInfo.Conn.LocalAddr()
|
||||
gotConn.Close()
|
||||
},
|
||||
}
|
||||
|
||||
// in case we hit an error, we want to unblock this part
|
||||
defer gotConn.Close()
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
httptrace.WithClientTrace(context.WithoutCancel(ctx), trace),
|
||||
"GET",
|
||||
requestURL.String()+sessionId,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to construct download http request")
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
|
||||
req.Header = transportConfiguration.GetRequestHeader()
|
||||
|
||||
response, err := httpClient.download.Do(req)
|
||||
gotConn.Close()
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to send download http request")
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if response.StatusCode != 200 {
|
||||
response.Body.Close()
|
||||
errors.LogInfo(ctx, "invalid status code on download:", response.Status)
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// skip "ooooooooook" response
|
||||
trashHeader := []byte{0}
|
||||
for {
|
||||
_, err = io.ReadFull(response.Body, trashHeader)
|
||||
if err != nil {
|
||||
response.Body.Close()
|
||||
errors.LogInfoInner(ctx, err, "failed to read initial response")
|
||||
gotDownResponse.Close()
|
||||
return
|
||||
}
|
||||
if trashHeader[0] == 'k' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
downResponse = response.Body
|
||||
gotDownResponse.Close()
|
||||
}()
|
||||
|
||||
uploadUrl := requestURL.String() + sessionId + "/"
|
||||
|
||||
uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize))
|
||||
|
||||
go func() {
|
||||
requestsLimiter := semaphore.New(int(maxConcurrentUploads))
|
||||
requestsLimiter := semaphore.New(int(scMaxConcurrentPosts.roll()))
|
||||
var requestCounter int64
|
||||
|
||||
lastWrite := time.Now()
|
||||
|
||||
// by offloading the uploads into a buffered pipe, multiple conn.Write
|
||||
// calls get automatically batched together into larger POST requests.
|
||||
// without batching, bandwidth is extremely limited.
|
||||
@@ -252,97 +220,63 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
|
||||
<-requestsLimiter.Wait()
|
||||
|
||||
url := uploadUrl + strconv.FormatInt(requestCounter, 10)
|
||||
seq := requestCounter
|
||||
requestCounter += 1
|
||||
|
||||
go func() {
|
||||
defer requestsLimiter.Signal()
|
||||
req, err := http.NewRequest("POST", url, &buf.MultiBufferContainer{MultiBuffer: chunk})
|
||||
|
||||
err := httpClient.SendUploadRequest(
|
||||
context.WithoutCancel(ctx),
|
||||
baseURL+"/"+strconv.FormatInt(seq, 10),
|
||||
&buf.MultiBufferContainer{MultiBuffer: chunk},
|
||||
int64(chunk.Len()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to send upload")
|
||||
uploadPipeReader.Interrupt()
|
||||
return
|
||||
}
|
||||
|
||||
req.ContentLength = int64(chunk.Len())
|
||||
req.Header = transportConfiguration.GetRequestHeader()
|
||||
|
||||
if httpClient.isH2 {
|
||||
resp, err := httpClient.upload.Do(req)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to send upload")
|
||||
uploadPipeReader.Interrupt()
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
errors.LogInfo(ctx, "failed to send upload, bad status code:", resp.Status)
|
||||
uploadPipeReader.Interrupt()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var uploadConn any
|
||||
|
||||
// stringify the entire HTTP/1.1 request so it can be
|
||||
// safely retried. if instead req.Write is called multiple
|
||||
// times, the body is already drained after the first
|
||||
// request
|
||||
requestBytes := new(bytes.Buffer)
|
||||
common.Must(req.Write(requestBytes))
|
||||
|
||||
for {
|
||||
uploadConn = httpClient.uploadRawPool.Get()
|
||||
newConnection := uploadConn == nil
|
||||
if newConnection {
|
||||
uploadConn, err = httpClient.dialUploadConn(context.WithoutCancel(ctx))
|
||||
if err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to connect upload")
|
||||
uploadPipeReader.Interrupt()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = uploadConn.(net.Conn).Write(requestBytes.Bytes())
|
||||
|
||||
// if the write failed, we try another connection from
|
||||
// the pool, until the write on a new connection fails.
|
||||
// failed writes to a pooled connection are normal when
|
||||
// the connection has been closed in the meantime.
|
||||
if err == nil {
|
||||
break
|
||||
} else if newConnection {
|
||||
errors.LogInfoInner(ctx, err, "failed to send upload")
|
||||
uploadPipeReader.Interrupt()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
httpClient.uploadRawPool.Put(uploadConn)
|
||||
}
|
||||
}()
|
||||
|
||||
if scMinPostsIntervalMs.From > 0 {
|
||||
roll := time.Duration(scMinPostsIntervalMs.roll()) * time.Millisecond
|
||||
if time.Since(lastWrite) < roll {
|
||||
time.Sleep(roll)
|
||||
}
|
||||
|
||||
lastWrite = time.Now()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// we want to block Dial until we know the remote address of the server,
|
||||
// for logging purposes
|
||||
<-gotConn.Wait()
|
||||
lazyRawDownload, remoteAddr, localAddr, err := httpClient.OpenDownload(context.WithoutCancel(ctx), baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lazyDownload := &LazyReader{
|
||||
CreateReader: func() (io.ReadCloser, error) {
|
||||
// skip "ooooooooook" response
|
||||
trashHeader := []byte{0}
|
||||
for {
|
||||
_, err := io.ReadFull(lazyRawDownload, trashHeader)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read initial response").Base(err)
|
||||
}
|
||||
if trashHeader[0] == 'k' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return lazyRawDownload, nil
|
||||
},
|
||||
}
|
||||
|
||||
// necessary in order to send larger chunks in upload
|
||||
bufferedUploadPipeWriter := buf.NewBufferedWriter(uploadPipeWriter)
|
||||
bufferedUploadPipeWriter.SetBuffered(false)
|
||||
|
||||
lazyDownload := &LazyReader{
|
||||
CreateReader: func() (io.ReadCloser, error) {
|
||||
<-gotDownResponse.Wait()
|
||||
if downResponse == nil {
|
||||
return nil, errors.New("downResponse failed")
|
||||
}
|
||||
return downResponse, nil
|
||||
},
|
||||
}
|
||||
|
||||
conn := splitConn{
|
||||
writer: bufferedUploadPipeWriter,
|
||||
reader: lazyDownload,
|
||||
|
@@ -11,6 +11,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
@@ -24,15 +26,17 @@ import (
|
||||
)
|
||||
|
||||
type requestHandler struct {
|
||||
config *Config
|
||||
host string
|
||||
path string
|
||||
ln *Listener
|
||||
sessionMu *sync.Mutex
|
||||
sessions sync.Map
|
||||
localAddr gonet.TCPAddr
|
||||
}
|
||||
|
||||
type httpSession struct {
|
||||
uploadQueue *UploadQueue
|
||||
uploadQueue *uploadQueue
|
||||
// for as long as the GET request is not opened by the client, this will be
|
||||
// open ("undone"), and the session may be expired within a certain TTL.
|
||||
// after the client connects, this becomes "done" and the session lives as
|
||||
@@ -56,13 +60,23 @@ func (h *requestHandler) maybeReapSession(isFullyConnected *done.Instance, sessi
|
||||
}
|
||||
|
||||
func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
||||
// fast path
|
||||
currentSessionAny, ok := h.sessions.Load(sessionId)
|
||||
if ok {
|
||||
return currentSessionAny.(*httpSession)
|
||||
}
|
||||
|
||||
// slow path
|
||||
h.sessionMu.Lock()
|
||||
defer h.sessionMu.Unlock()
|
||||
|
||||
currentSessionAny, ok = h.sessions.Load(sessionId)
|
||||
if ok {
|
||||
return currentSessionAny.(*httpSession)
|
||||
}
|
||||
|
||||
s := &httpSession{
|
||||
uploadQueue: NewUploadQueue(int(2 * h.ln.config.GetNormalizedMaxConcurrentUploads())),
|
||||
uploadQueue: NewUploadQueue(int(h.ln.config.GetNormalizedScMaxConcurrentPosts().To)),
|
||||
isFullyConnected: done.New(),
|
||||
}
|
||||
|
||||
@@ -72,7 +86,7 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
||||
}
|
||||
|
||||
func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if len(h.host) > 0 && request.Host != h.host {
|
||||
if len(h.host) > 0 && !internet.IsValidHTTPHost(request.Host, h.host) {
|
||||
errors.LogInfo(context.Background(), "failed to validate host, request:", request.Host, ", config:", h.host)
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
@@ -109,6 +123,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
}
|
||||
|
||||
currentSession := h.upsertSession(sessionId)
|
||||
scMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To)
|
||||
|
||||
if request.Method == "POST" {
|
||||
seq := ""
|
||||
@@ -123,6 +138,13 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
}
|
||||
|
||||
payload, err := io.ReadAll(request.Body)
|
||||
|
||||
if len(payload) > scMaxEachPostBytes {
|
||||
errors.LogInfo(context.Background(), "Too large upload. scMaxEachPostBytes is set to ", scMaxEachPostBytes, "but request had size ", len(payload), ". Adjust scMaxEachPostBytes on the server to be at least as large as client.")
|
||||
writer.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to upload")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -161,9 +183,11 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
// magic header instructs nginx + apache to not buffer response body
|
||||
writer.Header().Set("X-Accel-Buffering", "no")
|
||||
// magic header to make the HTTP middle box consider this as SSE to disable buffer
|
||||
writer.Header().Set("Content-Type", "text/event-stream")
|
||||
|
||||
if !h.config.NoSSEHeader {
|
||||
// magic header to make the HTTP middle box consider this as SSE to disable buffer
|
||||
writer.Header().Set("Content-Type", "text/event-stream")
|
||||
}
|
||||
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
// send a chunk immediately to enable CDN streaming.
|
||||
// many CDN buffer the response headers until the origin starts sending
|
||||
@@ -222,10 +246,13 @@ func (c *httpResponseBodyWriter) Close() error {
|
||||
|
||||
type Listener struct {
|
||||
sync.Mutex
|
||||
server http.Server
|
||||
listener net.Listener
|
||||
config *Config
|
||||
addConn internet.ConnHandler
|
||||
server http.Server
|
||||
h3server *http3.Server
|
||||
listener net.Listener
|
||||
h3listener *quic.EarlyListener
|
||||
config *Config
|
||||
addConn internet.ConnHandler
|
||||
isH3 bool
|
||||
}
|
||||
|
||||
func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
|
||||
@@ -242,6 +269,17 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
||||
var listener net.Listener
|
||||
var err error
|
||||
var localAddr = gonet.TCPAddr{}
|
||||
handler := &requestHandler{
|
||||
config: shSettings,
|
||||
host: shSettings.Host,
|
||||
path: shSettings.GetNormalizedPath("", false),
|
||||
ln: l,
|
||||
sessionMu: &sync.Mutex{},
|
||||
sessions: sync.Map{},
|
||||
localAddr: localAddr,
|
||||
}
|
||||
tlsConfig := getTLSConfig(streamSettings)
|
||||
l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3"
|
||||
|
||||
if port == net.Port(0) { // unix
|
||||
listener, err = internet.ListenSystem(ctx, &net.UnixAddr{
|
||||
@@ -252,6 +290,29 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
||||
return nil, errors.New("failed to listen unix domain socket(for SH) on ", address).Base(err)
|
||||
}
|
||||
errors.LogInfo(ctx, "listening unix domain socket(for SH) on ", address)
|
||||
} else if l.isH3 { // quic
|
||||
Conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
|
||||
IP: address.IP(),
|
||||
Port: int(port),
|
||||
}, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to listen UDP(for SH3) on ", address, ":", port).Base(err)
|
||||
}
|
||||
h3listener, err := quic.ListenEarly(Conn, tlsConfig, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to listen QUIC(for SH3) on ", address, ":", port).Base(err)
|
||||
}
|
||||
l.h3listener = h3listener
|
||||
errors.LogInfo(ctx, "listening QUIC(for SH3) on ", address, ":", port)
|
||||
|
||||
l.h3server = &http3.Server{
|
||||
Handler: handler,
|
||||
}
|
||||
go func() {
|
||||
if err := l.h3server.ServeListener(l.h3listener); err != nil {
|
||||
errors.LogWarningInner(ctx, err, "failed to serve http3 for splithttp")
|
||||
}
|
||||
}()
|
||||
} else { // tcp
|
||||
localAddr = gonet.TCPAddr{
|
||||
IP: address.IP(),
|
||||
@@ -267,36 +328,29 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
||||
errors.LogInfo(ctx, "listening TCP(for SH) on ", address, ":", port)
|
||||
}
|
||||
|
||||
if config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||
if tlsConfig := config.GetTLSConfig(); tlsConfig != nil {
|
||||
listener = tls.NewListener(listener, tlsConfig)
|
||||
// tcp/unix (h1/h2)
|
||||
if listener != nil {
|
||||
if config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||
if tlsConfig := config.GetTLSConfig(); tlsConfig != nil {
|
||||
listener = tls.NewListener(listener, tlsConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler := &requestHandler{
|
||||
host: shSettings.Host,
|
||||
path: shSettings.GetNormalizedPath(),
|
||||
ln: l,
|
||||
sessions: sync.Map{},
|
||||
localAddr: localAddr,
|
||||
}
|
||||
|
||||
// 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,
|
||||
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 splithttp")
|
||||
// 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,
|
||||
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 splithttp")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return l, err
|
||||
}
|
||||
@@ -308,9 +362,22 @@ func (ln *Listener) Addr() net.Addr {
|
||||
|
||||
// Close implements net.Listener.Close().
|
||||
func (ln *Listener) Close() error {
|
||||
return ln.listener.Close()
|
||||
if ln.h3server != nil {
|
||||
if err := ln.h3server.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if ln.listener != nil {
|
||||
return ln.listener.Close()
|
||||
}
|
||||
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)
|
||||
if config == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return config.GetTLSConfig()
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterTransportListener(protocolName, ListenSH))
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package splithttp_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
gotls "crypto/tls"
|
||||
"fmt"
|
||||
gonet "net"
|
||||
@@ -10,10 +11,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||
"github.com/xtls/xray-core/testing/servers/udp"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
. "github.com/xtls/xray-core/transport/internet/splithttp"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
@@ -63,8 +67,8 @@ func Test_listenSHAndDial(t *testing.T) {
|
||||
}
|
||||
|
||||
common.Must(conn.Close())
|
||||
<-time.After(time.Second * 5)
|
||||
conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 2"))
|
||||
common.Must(err)
|
||||
@@ -96,7 +100,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.Write([]byte("Response"))
|
||||
_, err = c.Write([]byte(c.RemoteAddr().String()))
|
||||
common.Must(err)
|
||||
}(conn)
|
||||
})
|
||||
@@ -113,7 +117,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
|
||||
var b [1024]byte
|
||||
n, _ := conn.Read(b[:])
|
||||
if string(b[:n]) != "Response" {
|
||||
if string(b[:n]) != "1.1.1.1:0" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
|
||||
@@ -142,7 +146,16 @@ func Test_listenSHAndDial_TLS(t *testing.T) {
|
||||
}
|
||||
listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
|
||||
go func() {
|
||||
_ = conn.Close()
|
||||
defer conn.Close()
|
||||
|
||||
var b [1024]byte
|
||||
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
_, err := conn.Read(b[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.Must2(conn.Write([]byte("Response")))
|
||||
}()
|
||||
})
|
||||
common.Must(err)
|
||||
@@ -150,7 +163,15 @@ func Test_listenSHAndDial_TLS(t *testing.T) {
|
||||
|
||||
conn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
common.Must(err)
|
||||
_ = conn.Close()
|
||||
|
||||
_, err = conn.Write([]byte("Test connection 1"))
|
||||
common.Must(err)
|
||||
|
||||
var b [1024]byte
|
||||
n, _ := conn.Read(b[:])
|
||||
if string(b[:n]) != "Response" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
|
||||
end := time.Now()
|
||||
if !end.Before(start.Add(time.Second * 5)) {
|
||||
@@ -204,3 +225,184 @@ func Test_listenSHAndDial_H2C(t *testing.T) {
|
||||
t.Error("Expected h2 but got:", resp.ProtoMajor)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listenSHAndDial_QUIC(t *testing.T) {
|
||||
if runtime.GOARCH == "arm64" {
|
||||
return
|
||||
}
|
||||
|
||||
listenPort := udp.PickPort()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
streamSettings := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{
|
||||
Path: "shs",
|
||||
},
|
||||
SecurityType: "tls",
|
||||
SecuritySettings: &tls.Config{
|
||||
AllowInsecure: true,
|
||||
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.CommonName("localhost")))},
|
||||
NextProtocol: []string{"h3"},
|
||||
},
|
||||
}
|
||||
listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
b := buf.New()
|
||||
defer b.Release()
|
||||
|
||||
for {
|
||||
b.Clear()
|
||||
if _, err := b.ReadFrom(conn); err != nil {
|
||||
return
|
||||
}
|
||||
common.Must2(conn.Write(b.Bytes()))
|
||||
}
|
||||
}()
|
||||
})
|
||||
common.Must(err)
|
||||
defer listen.Close()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
conn, err := Dial(context.Background(), net.UDPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
common.Must(err)
|
||||
defer conn.Close()
|
||||
|
||||
const N = 1024
|
||||
b1 := make([]byte, N)
|
||||
common.Must2(rand.Read(b1))
|
||||
b2 := buf.New()
|
||||
|
||||
common.Must2(conn.Write(b1))
|
||||
|
||||
b2.Clear()
|
||||
common.Must2(b2.ReadFullFrom(conn, N))
|
||||
if r := cmp.Diff(b2.Bytes(), b1); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
|
||||
common.Must2(conn.Write(b1))
|
||||
|
||||
b2.Clear()
|
||||
common.Must2(b2.ReadFullFrom(conn, N))
|
||||
if r := cmp.Diff(b2.Bytes(), b1); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
|
||||
end := time.Now()
|
||||
if !end.Before(start.Add(time.Second * 5)) {
|
||||
t.Error("end: ", end, " start: ", start)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listenSHAndDial_Unix(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
tempSocket := tempDir + "/server.sock"
|
||||
|
||||
listen, err := ListenSH(context.Background(), net.DomainAddress(tempSocket), 0, &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{
|
||||
Path: "/sh",
|
||||
},
|
||||
}, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
|
||||
var b [1024]byte
|
||||
c.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
_, err := c.Read(b[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.Must2(c.Write([]byte("Response")))
|
||||
}(conn)
|
||||
})
|
||||
common.Must(err)
|
||||
ctx := context.Background()
|
||||
streamSettings := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{
|
||||
Host: "example.com",
|
||||
Path: "sh",
|
||||
},
|
||||
}
|
||||
conn, err := Dial(ctx, net.UnixDestination(net.DomainAddress(tempSocket)), streamSettings)
|
||||
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 1"))
|
||||
common.Must(err)
|
||||
|
||||
var b [1024]byte
|
||||
fmt.Println("test2")
|
||||
n, _ := conn.Read(b[:])
|
||||
fmt.Println("string is", n)
|
||||
if string(b[:n]) != "Response" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
|
||||
common.Must(conn.Close())
|
||||
conn, err = Dial(ctx, net.UnixDestination(net.DomainAddress(tempSocket)), streamSettings)
|
||||
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 2"))
|
||||
common.Must(err)
|
||||
n, _ = conn.Read(b[:])
|
||||
common.Must(err)
|
||||
if string(b[:n]) != "Response" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
common.Must(conn.Close())
|
||||
|
||||
common.Must(listen.Close())
|
||||
}
|
||||
|
||||
func Test_queryString(t *testing.T) {
|
||||
listenPort := tcp.PickPort()
|
||||
listen, err := ListenSH(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
|
||||
Path: "/sh?ed=2048",
|
||||
},
|
||||
}, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
|
||||
var b [1024]byte
|
||||
c.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
_, err := c.Read(b[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.Must2(c.Write([]byte("Response")))
|
||||
}(conn)
|
||||
})
|
||||
common.Must(err)
|
||||
ctx := context.Background()
|
||||
streamSettings := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{Path: "sh"},
|
||||
}
|
||||
conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 1"))
|
||||
common.Must(err)
|
||||
|
||||
var b [1024]byte
|
||||
fmt.Println("test2")
|
||||
n, _ := conn.Read(b[:])
|
||||
fmt.Println("string is", n)
|
||||
if string(b[:n]) != "Response" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
|
||||
common.Must(conn.Close())
|
||||
common.Must(listen.Close())
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ package splithttp
|
||||
import (
|
||||
"container/heap"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
@@ -15,16 +16,17 @@ type Packet struct {
|
||||
Seq uint64
|
||||
}
|
||||
|
||||
type UploadQueue struct {
|
||||
pushedPackets chan Packet
|
||||
heap uploadHeap
|
||||
nextSeq uint64
|
||||
closed bool
|
||||
maxPackets int
|
||||
type uploadQueue struct {
|
||||
pushedPackets chan Packet
|
||||
writeCloseMutex sync.Mutex
|
||||
heap uploadHeap
|
||||
nextSeq uint64
|
||||
closed bool
|
||||
maxPackets int
|
||||
}
|
||||
|
||||
func NewUploadQueue(maxPackets int) *UploadQueue {
|
||||
return &UploadQueue{
|
||||
func NewUploadQueue(maxPackets int) *uploadQueue {
|
||||
return &uploadQueue{
|
||||
pushedPackets: make(chan Packet, maxPackets),
|
||||
heap: uploadHeap{},
|
||||
nextSeq: 0,
|
||||
@@ -33,7 +35,10 @@ func NewUploadQueue(maxPackets int) *UploadQueue {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *UploadQueue) Push(p Packet) error {
|
||||
func (h *uploadQueue) Push(p Packet) error {
|
||||
h.writeCloseMutex.Lock()
|
||||
defer h.writeCloseMutex.Unlock()
|
||||
|
||||
if h.closed {
|
||||
return errors.New("splithttp packet queue closed")
|
||||
}
|
||||
@@ -42,13 +47,16 @@ func (h *UploadQueue) Push(p Packet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *UploadQueue) Close() error {
|
||||
func (h *uploadQueue) Close() error {
|
||||
h.writeCloseMutex.Lock()
|
||||
defer h.writeCloseMutex.Unlock()
|
||||
|
||||
h.closed = true
|
||||
close(h.pushedPackets)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *UploadQueue) Read(b []byte) (int, error) {
|
||||
func (h *uploadQueue) Read(b []byte) (int, error) {
|
||||
if h.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ func ListenUnix(ctx context.Context, address net.Address, settings *MemoryStream
|
||||
protocol := settings.ProtocolName
|
||||
listenFunc := transportListenerCache[protocol]
|
||||
if listenFunc == nil {
|
||||
return nil, errors.New(protocol, " unix istener not registered.").AtError()
|
||||
return nil, errors.New(protocol, " unix listener not registered.").AtError()
|
||||
}
|
||||
listener, err := listenFunc(ctx, address, net.Port(0), settings, handler)
|
||||
if err != nil {
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"bytes"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
@@ -50,72 +51,84 @@ func (c *Config) BuildCertificates() []*tls.Certificate {
|
||||
if entry.Usage != Certificate_ENCIPHERMENT {
|
||||
continue
|
||||
}
|
||||
keyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key)
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||
continue
|
||||
}
|
||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
||||
continue
|
||||
}
|
||||
certs = append(certs, &keyPair)
|
||||
if !entry.OneTimeLoading {
|
||||
var isOcspstapling bool
|
||||
hotReloadCertInterval := uint64(3600)
|
||||
if entry.OcspStapling != 0 {
|
||||
hotReloadCertInterval = entry.OcspStapling
|
||||
isOcspstapling = true
|
||||
getX509KeyPair := func() *tls.Certificate {
|
||||
keyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key)
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||
return nil
|
||||
}
|
||||
index := len(certs) - 1
|
||||
go func(entry *Certificate, cert *tls.Certificate, index int) {
|
||||
t := time.NewTicker(time.Duration(hotReloadCertInterval) * time.Second)
|
||||
for {
|
||||
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
||||
newCert, err := filesystem.ReadFile(entry.CertificatePath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
||||
<-t.C
|
||||
continue
|
||||
}
|
||||
newKey, err := filesystem.ReadFile(entry.KeyPath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
||||
<-t.C
|
||||
continue
|
||||
}
|
||||
if string(newCert) != string(entry.Certificate) && string(newKey) != string(entry.Key) {
|
||||
newKeyPair, err := tls.X509KeyPair(newCert, newKey)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||
<-t.C
|
||||
continue
|
||||
}
|
||||
if newKeyPair.Leaf, err = x509.ParseCertificate(newKeyPair.Certificate[0]); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "ignoring invalid certificate")
|
||||
<-t.C
|
||||
continue
|
||||
}
|
||||
cert = &newKeyPair
|
||||
}
|
||||
}
|
||||
if isOcspstapling {
|
||||
if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
||||
} else if string(newOCSPData) != string(cert.OCSPStaple) {
|
||||
cert.OCSPStaple = newOCSPData
|
||||
}
|
||||
}
|
||||
certs[index] = cert
|
||||
<-t.C
|
||||
}
|
||||
}(entry, certs[index], index)
|
||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
||||
return nil
|
||||
}
|
||||
return &keyPair
|
||||
}
|
||||
if keyPair := getX509KeyPair(); keyPair != nil {
|
||||
certs = append(certs, keyPair)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
index := len(certs) - 1
|
||||
setupOcspTicker(entry, func(isReloaded, isOcspstapling bool){
|
||||
cert := certs[index]
|
||||
if isReloaded {
|
||||
if newKeyPair := getX509KeyPair(); newKeyPair != nil {
|
||||
cert = newKeyPair
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if isOcspstapling {
|
||||
if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
||||
} else if string(newOCSPData) != string(cert.OCSPStaple) {
|
||||
cert.OCSPStaple = newOCSPData
|
||||
}
|
||||
}
|
||||
certs[index] = cert
|
||||
})
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstapling bool)) {
|
||||
go func() {
|
||||
if entry.OneTimeLoading {
|
||||
return
|
||||
}
|
||||
var isOcspstapling bool
|
||||
hotReloadCertInterval := uint64(3600)
|
||||
if entry.OcspStapling != 0 {
|
||||
hotReloadCertInterval = entry.OcspStapling
|
||||
isOcspstapling = true
|
||||
}
|
||||
t := time.NewTicker(time.Duration(hotReloadCertInterval) * time.Second)
|
||||
for {
|
||||
var isReloaded bool
|
||||
if entry.CertificatePath != "" && entry.KeyPath != "" {
|
||||
newCert, err := filesystem.ReadFile(entry.CertificatePath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse certificate")
|
||||
return
|
||||
}
|
||||
newKey, err := filesystem.ReadFile(entry.KeyPath)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to parse key")
|
||||
return
|
||||
}
|
||||
if string(newCert) != string(entry.Certificate) || string(newKey) != string(entry.Key) {
|
||||
entry.Certificate = newCert
|
||||
entry.Key = newKey
|
||||
isReloaded = true
|
||||
}
|
||||
}
|
||||
callback(isReloaded, isOcspstapling)
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func isCertificateExpired(c *tls.Certificate) bool {
|
||||
if c.Leaf == nil && len(c.Certificate) > 0 {
|
||||
if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
|
||||
@@ -137,6 +150,9 @@ func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, erro
|
||||
return nil, errors.New("failed to generate new certificate for ", domain).Base(err)
|
||||
}
|
||||
newCertPEM, newKeyPEM := newCert.ToPEM()
|
||||
if rawCA.BuildChain {
|
||||
newCertPEM = bytes.Join([][]byte{newCertPEM, rawCA.Certificate}, []byte("\n"))
|
||||
}
|
||||
cert, err := tls.X509KeyPair(newCertPEM, newKeyPEM)
|
||||
return &cert, err
|
||||
}
|
||||
@@ -146,6 +162,7 @@ func (c *Config) getCustomCA() []*Certificate {
|
||||
for _, certificate := range c.Certificate {
|
||||
if certificate.Usage == Certificate_AUTHORITY_ISSUE {
|
||||
certs = append(certs, certificate)
|
||||
setupOcspTicker(certificate, func(isReloaded, isOcspstapling bool){ })
|
||||
}
|
||||
}
|
||||
return certs
|
||||
|
@@ -86,6 +86,7 @@ type Certificate struct {
|
||||
KeyPath string `protobuf:"bytes,6,opt,name=key_path,json=keyPath,proto3" json:"key_path,omitempty"`
|
||||
// If true, one-Time Loading
|
||||
OneTimeLoading bool `protobuf:"varint,7,opt,name=One_time_loading,json=OneTimeLoading,proto3" json:"One_time_loading,omitempty"`
|
||||
BuildChain bool `protobuf:"varint,8,opt,name=build_chain,json=buildChain,proto3" json:"build_chain,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Certificate) Reset() {
|
||||
@@ -169,6 +170,13 @@ func (x *Certificate) GetOneTimeLoading() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Certificate) GetBuildChain() bool {
|
||||
if x != nil {
|
||||
return x.BuildChain
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -359,7 +367,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 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, 0x22, 0xe2, 0x02, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x6c, 0x73, 0x22, 0x83, 0x03, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
@@ -377,66 +385,68 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||
0x6b, 0x65, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x28, 0x0a, 0x10, 0x4f, 0x6e, 0x65, 0x5f, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0e, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e,
|
||||
0x67, 0x22, 0x44, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x4e,
|
||||
0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10,
|
||||
0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59,
|
||||
0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f,
|
||||
0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xf6, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65,
|
||||
0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f,
|
||||
0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, 0x72,
|
||||
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28,
|
||||
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, 0x2e, 0x43, 0x65, 0x72,
|
||||
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6e,
|
||||
0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3a, 0x0a, 0x19, 0x65,
|
||||
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65,
|
||||
0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17,
|
||||
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x79, 0x73,
|
||||
0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x69,
|
||||
0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d,
|
||||
0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x69, 0x70,
|
||||
0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x12, 0x41,
|
||||
0x0a, 0x1b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
|
||||
0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20,
|
||||
0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x18, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65,
|
||||
0x73, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74,
|
||||
0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72,
|
||||
0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x6e,
|
||||
0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x6e, 0x69, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x10, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53, 0x6e,
|
||||
0x69, 0x12, 0x4e, 0x0a, 0x24, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72,
|
||||
0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61,
|
||||
0x69, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52,
|
||||
0x20, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x68, 0x61, 0x32, 0x35,
|
||||
0x36, 0x12, 0x57, 0x0a, 0x29, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72,
|
||||
0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0e,
|
||||
0x20, 0x03, 0x28, 0x0c, 0x52, 0x24, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72,
|
||||
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69,
|
||||
0x63, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61,
|
||||
0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x0f, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x67,
|
||||
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,
|
||||
0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x68, 0x61,
|
||||
0x69, 0x6e, 0x22, 0x44, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x45,
|
||||
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
||||
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
||||
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
||||
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xf6, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
||||
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
||||
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
||||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x28, 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, 0x2e, 0x43, 0x65,
|
||||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x5f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,
|
||||
0x6e, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3a, 0x0a, 0x19,
|
||||
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72,
|
||||
0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x17, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
|
||||
0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x79,
|
||||
0x73, 0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d,
|
||||
0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78,
|
||||
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
|
||||
0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x69,
|
||||
0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x73, 0x12,
|
||||
0x41, 0x0a, 0x1b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, 0x0a,
|
||||
0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x18, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74,
|
||||
0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e,
|
||||
0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70,
|
||||
0x72, 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x75,
|
||||
0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x6e, 0x69, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x10, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x53,
|
||||
0x6e, 0x69, 0x12, 0x4e, 0x0a, 0x24, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65,
|
||||
0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68,
|
||||
0x61, 0x69, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c,
|
||||
0x52, 0x20, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74,
|
||||
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x53, 0x68, 0x61, 0x32,
|
||||
0x35, 0x36, 0x12, 0x57, 0x0a, 0x29, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65,
|
||||
0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x75,
|
||||
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18,
|
||||
0x0e, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x24, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65,
|
||||
0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x63, 0x4b, 0x65, 0x79, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x24, 0x0a, 0x0e, 0x6d,
|
||||
0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x0f, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x4c, 0x6f,
|
||||
0x67, 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 (
|
||||
|
@@ -31,6 +31,8 @@ message Certificate {
|
||||
|
||||
// If true, one-Time Loading
|
||||
bool One_time_loading = 7;
|
||||
|
||||
bool build_chain = 8;
|
||||
}
|
||||
|
||||
message Config {
|
||||
|
@@ -14,13 +14,15 @@ import (
|
||||
var _ buf.Writer = (*connection)(nil)
|
||||
|
||||
// connection is a wrapper for net.Conn over WebSocket connection.
|
||||
// remoteAddr is used to pass "virtual" remote IP addresses in X-Forwarded-For.
|
||||
// so we shouldn't directly read it form conn.
|
||||
type connection struct {
|
||||
conn *websocket.Conn
|
||||
reader io.Reader
|
||||
remoteAddr net.Addr
|
||||
}
|
||||
|
||||
func newConnection(conn *websocket.Conn, remoteAddr net.Addr, extraReader io.Reader) *connection {
|
||||
func NewConnection(conn *websocket.Conn, remoteAddr net.Addr, extraReader io.Reader) *connection {
|
||||
return &connection{
|
||||
conn: conn,
|
||||
remoteAddr: remoteAddr,
|
||||
|
@@ -1,54 +1,23 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
gonet "net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"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/platform"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/browser_dialer"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
//go:embed dialer.html
|
||||
var webpage []byte
|
||||
|
||||
var conns chan *websocket.Conn
|
||||
|
||||
func init() {
|
||||
addr := platform.NewEnvFlag(platform.BrowserDialerAddress).GetValue(func() string { return "" })
|
||||
if addr != "" {
|
||||
token := uuid.New()
|
||||
csrfToken := token.String()
|
||||
webpage = bytes.ReplaceAll(webpage, []byte("csrfToken"), []byte(csrfToken))
|
||||
conns = make(chan *websocket.Conn, 256)
|
||||
go http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/websocket" {
|
||||
if r.URL.Query().Get("token") == csrfToken {
|
||||
if conn, err := upgrader.Upgrade(w, r, nil); err == nil {
|
||||
conns <- conn
|
||||
} else {
|
||||
errors.LogError(context.Background(), "Browser dialer http upgrade unexpected error")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.Write(webpage)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// Dial dials a WebSocket connection to the given destination.
|
||||
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
|
||||
errors.LogInfo(ctx, "creating connection to ", dest)
|
||||
@@ -98,18 +67,18 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
// Like the NetDial in the dialer
|
||||
pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to dial to " + addr)
|
||||
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
|
||||
return nil, err
|
||||
}
|
||||
// TLS and apply the handshake
|
||||
cn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn)
|
||||
if err := cn.WebsocketHandshakeContext(ctx); err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to dial to " + addr)
|
||||
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
|
||||
return nil, err
|
||||
}
|
||||
if !tlsConfig.InsecureSkipVerify {
|
||||
if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to dial to " + addr)
|
||||
errors.LogErrorInner(ctx, err, "failed to dial to "+addr)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -124,28 +93,13 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
}
|
||||
uri := protocol + "://" + host + wsSettings.GetNormalizedPath()
|
||||
|
||||
if conns != nil {
|
||||
data := []byte(uri)
|
||||
if ed != nil {
|
||||
data = append(data, " "+base64.RawURLEncoding.EncodeToString(ed)...)
|
||||
}
|
||||
var conn *websocket.Conn
|
||||
for {
|
||||
conn = <-conns
|
||||
if conn.WriteMessage(websocket.TextMessage, data) != nil {
|
||||
conn.Close()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if _, p, err := conn.ReadMessage(); err != nil {
|
||||
conn.Close()
|
||||
if browser_dialer.HasBrowserDialer() {
|
||||
conn, err := browser_dialer.DialWS(uri, ed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if s := string(p); s != "ok" {
|
||||
conn.Close()
|
||||
return nil, errors.New(s)
|
||||
}
|
||||
return newConnection(conn, conn.RemoteAddr(), nil), nil
|
||||
|
||||
return NewConnection(conn, conn.RemoteAddr(), nil), nil
|
||||
}
|
||||
|
||||
header := wsSettings.GetRequestHeader()
|
||||
@@ -163,7 +117,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
return nil, errors.New("failed to dial to (", uri, "): ", reason).Base(err)
|
||||
}
|
||||
|
||||
return newConnection(conn, conn.RemoteAddr(), nil), nil
|
||||
return NewConnection(conn, conn.RemoteAddr(), nil), nil
|
||||
}
|
||||
|
||||
type delayDialConn struct {
|
||||
|
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Browser Dialer</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// Copyright (c) 2021 XRAY. Mozilla Public License 2.0.
|
||||
var url = "ws://" + window.location.host + "/websocket?token=csrfToken"
|
||||
var count = 0
|
||||
setInterval(check, 1000)
|
||||
function check() {
|
||||
if (count <= 0) {
|
||||
count += 1
|
||||
console.log("Prepare", url)
|
||||
var ws = new WebSocket(url)
|
||||
// arraybuffer is significantly faster in chrome than default
|
||||
// blob, tested with chrome 123
|
||||
ws.binaryType = "arraybuffer";
|
||||
var wss = undefined
|
||||
var first = true
|
||||
ws.onmessage = function (event) {
|
||||
if (first) {
|
||||
first = false
|
||||
count -= 1
|
||||
var arr = event.data.split(" ")
|
||||
console.log("Dial", arr[0], arr[1])
|
||||
wss = new WebSocket(arr[0], arr[1])
|
||||
wss.binaryType = "arraybuffer";
|
||||
var opened = false
|
||||
wss.onopen = function (event) {
|
||||
opened = true
|
||||
ws.send("ok")
|
||||
}
|
||||
wss.onmessage = function (event) {
|
||||
ws.send(event.data)
|
||||
}
|
||||
wss.onclose = function (event) {
|
||||
ws.close()
|
||||
}
|
||||
wss.onerror = function (event) {
|
||||
!opened && ws.send("fail")
|
||||
wss.close()
|
||||
}
|
||||
check()
|
||||
} else wss.send(event.data)
|
||||
}
|
||||
ws.onclose = function (event) {
|
||||
if (first) count -= 1
|
||||
else wss.close()
|
||||
}
|
||||
ws.onerror = function (event) {
|
||||
ws.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -38,7 +38,7 @@ var upgrader = &websocket.Upgrader{
|
||||
}
|
||||
|
||||
func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if len(h.host) > 0 && request.Host != h.host {
|
||||
if len(h.host) > 0 && !internet.IsValidHTTPHost(request.Host, h.host) {
|
||||
errors.LogInfo(context.Background(), "failed to validate host, request:", request.Host, ", config:", h.host)
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
@@ -73,7 +73,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
h.ln.addConn(newConnection(conn, remoteAddr, extraReader))
|
||||
h.ln.addConn(NewConnection(conn, remoteAddr, extraReader))
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
|
@@ -58,7 +58,6 @@ func Test_listenWSAndDial(t *testing.T) {
|
||||
}
|
||||
|
||||
common.Must(conn.Close())
|
||||
<-time.After(time.Second * 5)
|
||||
conn, err = Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
|
||||
common.Must(err)
|
||||
_, err = conn.Write([]byte("Test connection 2"))
|
||||
@@ -91,7 +90,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.Write([]byte("Response"))
|
||||
_, err = c.Write([]byte(c.RemoteAddr().String()))
|
||||
common.Must(err)
|
||||
}(conn)
|
||||
})
|
||||
@@ -109,7 +108,7 @@ func TestDialWithRemoteAddr(t *testing.T) {
|
||||
var b [1024]byte
|
||||
n, err := conn.Read(b[:])
|
||||
common.Must(err)
|
||||
if string(b[:n]) != "Response" {
|
||||
if string(b[:n]) != "1.1.1.1:0" {
|
||||
t.Error("response: ", string(b[:n]))
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user