mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-22 22:58:03 +03:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c862aa8dd1 | ||
|
|
a28e215bf0 | ||
|
|
e6207e3a97 | ||
|
|
f0f765f9eb | ||
|
|
efdf21efb5 | ||
|
|
07374ae5a5 | ||
|
|
b6a7609c87 | ||
|
|
b43276c6d3 | ||
|
|
6a909b2507 | ||
|
|
7abad3fac0 | ||
|
|
1fe6d4a0f5 | ||
|
|
d100be5ad5 | ||
|
|
7dc3f87cd8 | ||
|
|
a079890ef0 | ||
|
|
fb0fa80c8c | ||
|
|
e3e7b28c08 | ||
|
|
12ee51e4bb | ||
|
|
957e5a6b15 | ||
|
|
0710c2b195 | ||
|
|
4632984b66 | ||
|
|
b7a22c729b | ||
|
|
8c3f246dcb | ||
|
|
888c0d2e1f | ||
|
|
74c726ff62 |
@@ -29,6 +29,5 @@
|
||||
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
|
||||
"windows-386": { "friendlyName": "windows-32" },
|
||||
"windows-amd64": { "friendlyName": "windows-64" },
|
||||
"windows-arm64": { "friendlyName": "windows-arm64-v8a" },
|
||||
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
|
||||
"windows-arm64": { "friendlyName": "windows-arm64-v8a" }
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
break
|
||||
fi
|
||||
done
|
||||
LIST=('amd64' 'x86' 'arm64' 'arm')
|
||||
LIST=('amd64' 'x86' 'arm64')
|
||||
for ARCHITECTURE in "${LIST[@]}"
|
||||
do
|
||||
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
|
||||
@@ -114,9 +114,6 @@ jobs:
|
||||
# Windows ARM
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# BEGIN Other architectures
|
||||
# BEGIN riscv64 & ARM64 & LOONG64
|
||||
- goos: linux
|
||||
@@ -250,9 +247,6 @@ jobs:
|
||||
if [[ ${GOARCH} == 'arm64' ]]; then
|
||||
mv resources/wintun/bin/arm64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == 'arm' ]]; then
|
||||
mv resources/wintun/bin/arm/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
max_attempts: 60
|
||||
command: |
|
||||
[ -d 'resources' ] || mkdir resources
|
||||
LIST=('amd64' 'x86' 'arm64' 'arm')
|
||||
LIST=('amd64' 'x86' 'arm64')
|
||||
for ARCHITECTURE in "${LIST[@]}"
|
||||
do
|
||||
FILE_PATH="resources/wintun/bin/${ARCHITECTURE}/wintun.dll"
|
||||
|
||||
@@ -34,6 +34,22 @@ jobs:
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
run: sleep 90
|
||||
|
||||
check-proto:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
- name: Check Proto Version Header
|
||||
run: |
|
||||
head -n 4 core/config.pb.go > ref.txt
|
||||
find . -name "*.pb.go" ! -name "*_grpc.pb.go" -print0 | while IFS= read -r -d '' file; do
|
||||
if ! cmp -s ref.txt <(head -n 4 "$file"); then
|
||||
echo "Error: Header mismatch in $file"
|
||||
head -n 4 "$file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
test:
|
||||
needs: check-assets
|
||||
permissions:
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||
- Magisk
|
||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||
- Homebrew
|
||||
@@ -106,12 +107,15 @@
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- Android
|
||||
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
@@ -125,6 +129,7 @@
|
||||
- [GoXRay](https://github.com/goxray/desktop)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- Linux
|
||||
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
@@ -132,6 +137,7 @@
|
||||
- [GoXRay](https://github.com/goxray/desktop)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
|
||||
|
||||
req.Header.Add("Accept", "application/dns-message")
|
||||
req.Header.Add("Content-Type", "application/dns-message")
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))
|
||||
|
||||
hc := s.httpClient
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||
)
|
||||
@@ -61,6 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {
|
||||
if err != nil {
|
||||
return rttFailed, err
|
||||
}
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
|
||||
start := time.Now()
|
||||
resp, err := s.httpClient.Do(req)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/extension"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
@@ -162,7 +163,9 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
if o.config.ProbeUrl != "" {
|
||||
probeURL = o.config.ProbeUrl
|
||||
}
|
||||
response, err := httpClient.Get(probeURL)
|
||||
req, _ := http.NewRequest(http.MethodGet, probeURL, nil)
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("outbound failed to relay connection").Base(err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tcp"
|
||||
@@ -138,6 +140,13 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
|
||||
|
||||
func (w *tcpWorker) Start() error {
|
||||
ctx := context.Background()
|
||||
|
||||
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
|
||||
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
|
||||
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
|
||||
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
||||
}
|
||||
|
||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
||||
go w.callback(conn)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AccessField can used to access unexported field of a struct
|
||||
// valueType must be the exact type of the field or it will panic
|
||||
func AccessField[valueType any](obj any, fieldName string) *valueType {
|
||||
field := reflect.ValueOf(obj).Elem().FieldByName(fieldName)
|
||||
if field.Type() != reflect.TypeOf(*new(valueType)) {
|
||||
panic("field type: " + field.Type().String() + ", valueType: " + reflect.TypeOf(*new(valueType)).String())
|
||||
}
|
||||
v := (*valueType)(unsafe.Pointer(field.UnsafeAddr()))
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
func ChromeVersion() int {
|
||||
// Use only CPU info as seed for PRNG
|
||||
seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine)
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
// Start from Chrome 144 released on 2026.1.13
|
||||
releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC)
|
||||
version := 144
|
||||
now := time.Now()
|
||||
// Each version has random 25-45 day interval
|
||||
for releaseDate.Before(now) {
|
||||
releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25)
|
||||
version++
|
||||
}
|
||||
return version - 1
|
||||
}
|
||||
|
||||
// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG.
|
||||
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36"
|
||||
+30
-1
@@ -12,6 +12,7 @@ package core
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
)
|
||||
@@ -19,7 +20,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 2
|
||||
Version_z byte = 2
|
||||
Version_z byte = 6
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -28,6 +29,34 @@ var (
|
||||
intro = "A unified platform for anti-censorship."
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Manually injected
|
||||
if build != "Custom" {
|
||||
return
|
||||
}
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var isDirty bool
|
||||
var foundBuild bool
|
||||
for _, setting := range info.Settings {
|
||||
switch setting.Key {
|
||||
case "vcs.revision":
|
||||
if len(setting.Value) < 7 {
|
||||
return
|
||||
}
|
||||
build = setting.Value[:7]
|
||||
foundBuild = true
|
||||
case "vcs.modified":
|
||||
isDirty = setting.Value == "true"
|
||||
}
|
||||
}
|
||||
if isDirty && foundBuild {
|
||||
build += "-dirty"
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns Xray's version as a string, in the form of "x.y.z" where x, y and z are numbers.
|
||||
// ".z" part may be omitted in regular releases.
|
||||
func Version() string {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/xtls/xray-core
|
||||
|
||||
go 1.25.6
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178
|
||||
@@ -9,9 +9,10 @@ require (
|
||||
github.com/golang/mock v1.7.0-rc.1
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pires/go-proxyproto v0.9.2
|
||||
github.com/pires/go-proxyproto v0.11.0
|
||||
github.com/refraction-networking/utls v1.8.2
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
@@ -19,14 +20,14 @@ require (
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/grpc v1.79.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||
h12.io/socks v1.0.3
|
||||
@@ -39,16 +40,17 @@ require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/juju/ratelimit v1.0.2 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/refraction-networking/utls => github.com/fangliding/utls v0.0.0-20260223095720-1ba92b0f6acd
|
||||
|
||||
@@ -2,11 +2,15 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fangliding/utls v0.0.0-20260223095720-1ba92b0f6acd h1:gOZhm1IpArbRLXkLzT5m11HU/HLJmp1aI6/WQLZ85+Y=
|
||||
github.com/fangliding/utls v0.0.0-20260223095720-1ba92b0f6acd/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
@@ -31,8 +35,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -43,14 +47,12 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
|
||||
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||
@@ -68,34 +70,34 @@ github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUv
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
@@ -107,21 +109,21 @@ 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -131,10 +133,10 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -3,7 +3,9 @@ package conf
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/proxy/hysteria"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -27,3 +29,33 @@ func (c *HysteriaClientConfig) Build() (proto.Message, error) {
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type HysteriaUserConfig struct {
|
||||
Auth string `json:"auth"`
|
||||
Level uint32 `json:"level"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type HysteriaServerConfig struct {
|
||||
Version int32 `json:"version"`
|
||||
Users []*HysteriaUserConfig `json:"clients"`
|
||||
}
|
||||
|
||||
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
|
||||
config := new(hysteria.ServerConfig)
|
||||
|
||||
if c.Users != nil {
|
||||
for _, user := range c.Users {
|
||||
account := &account.Account{
|
||||
Auth: user.Auth,
|
||||
}
|
||||
config.Users = append(config.Users, &protocol.User{
|
||||
Email: user.Email,
|
||||
Level: user.Level,
|
||||
Account: serial.ToTypedMessage(account),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/transport/internet/headers/http"
|
||||
"github.com/xtls/xray-core/transport/internet/headers/noop"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -40,11 +41,8 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
||||
Value: []string{"www.baidu.com", "www.bing.com"},
|
||||
},
|
||||
{
|
||||
Name: "User-Agent",
|
||||
Value: []string{
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46",
|
||||
},
|
||||
Name: "User-Agent",
|
||||
Value: []string{utils.ChromeUA},
|
||||
},
|
||||
{
|
||||
Name: "Accept-Encoding",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
@@ -506,6 +508,20 @@ type UdpHop struct {
|
||||
Interval *Int32Range `json:"interval"`
|
||||
}
|
||||
|
||||
type Masquerade struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Dir string `json:"dir"`
|
||||
|
||||
Url string `json:"url"`
|
||||
RewriteHost bool `json:"rewriteHost"`
|
||||
Insecure bool `json:"insecure"`
|
||||
|
||||
Content string `json:"content"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
StatusCode int32 `json:"statusCode"`
|
||||
}
|
||||
|
||||
type HysteriaConfig struct {
|
||||
Version int32 `json:"version"`
|
||||
Auth string `json:"auth"`
|
||||
@@ -521,6 +537,10 @@ type HysteriaConfig struct {
|
||||
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
|
||||
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
|
||||
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
|
||||
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
|
||||
|
||||
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
|
||||
Masquerade Masquerade `json:"masquerade"`
|
||||
}
|
||||
|
||||
func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
@@ -554,10 +574,10 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
if up > 0 && up < 65536 {
|
||||
return nil, errors.New("Up must be at least 65536 Bps")
|
||||
return nil, errors.New("Up must be at least 65536 bytes per second")
|
||||
}
|
||||
if down > 0 && down < 65536 {
|
||||
return nil, errors.New("Down must be at least 65536 Bps")
|
||||
return nil, errors.New("Down must be at least 65536 bytes per second")
|
||||
}
|
||||
if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
|
||||
return nil, errors.New("Interval must be at least 5")
|
||||
@@ -581,6 +601,12 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
if c.KeepAlivePeriod != 0 && (c.KeepAlivePeriod < 2 || c.KeepAlivePeriod > 60) {
|
||||
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
|
||||
}
|
||||
if c.MaxIncomingStreams != 0 && c.MaxIncomingStreams < 8 {
|
||||
return nil, errors.New("MaxIncomingStreams must be at least 8")
|
||||
}
|
||||
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
|
||||
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
|
||||
}
|
||||
|
||||
config := &hysteria.Config{}
|
||||
config.Version = c.Version
|
||||
@@ -598,6 +624,16 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
config.MaxIdleTimeout = c.MaxIdleTimeout
|
||||
config.KeepAlivePeriod = c.KeepAlivePeriod
|
||||
config.DisablePathMtuDiscovery = c.DisablePathMTUDiscovery
|
||||
config.MaxIncomingStreams = c.MaxIncomingStreams
|
||||
config.UdpIdleTimeout = c.UdpIdleTimeout
|
||||
config.MasqType = c.Masquerade.Type
|
||||
config.MasqFile = c.Masquerade.Dir
|
||||
config.MasqUrl = c.Masquerade.Url
|
||||
config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
|
||||
config.MasqUrlInsecure = c.Masquerade.Insecure
|
||||
config.MasqString = c.Masquerade.Content
|
||||
config.MasqStringHeaders = c.Masquerade.Headers
|
||||
config.MasqStringStatusCode = c.Masquerade.StatusCode
|
||||
|
||||
if config.InitStreamReceiveWindow == 0 {
|
||||
config.InitStreamReceiveWindow = 8388608
|
||||
@@ -617,6 +653,12 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
// if config.KeepAlivePeriod == 0 {
|
||||
// config.KeepAlivePeriod = 10
|
||||
// }
|
||||
if config.MaxIncomingStreams == 0 {
|
||||
config.MaxIncomingStreams = 1024
|
||||
}
|
||||
if config.UdpIdleTimeout == 0 {
|
||||
config.UdpIdleTimeout = 60
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
@@ -747,7 +789,12 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config.MasterKeyLog = c.MasterKeyLog
|
||||
|
||||
if c.AllowInsecure {
|
||||
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
|
||||
if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
|
||||
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
|
||||
} else {
|
||||
errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
|
||||
config.AllowInsecure = true
|
||||
}
|
||||
}
|
||||
if c.PinnedPeerCertSha256 != "" {
|
||||
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
|
||||
|
||||
@@ -33,6 +33,7 @@ var (
|
||||
"vmess": func() interface{} { return new(VMessInboundConfig) },
|
||||
"trojan": func() interface{} { return new(TrojanServerConfig) },
|
||||
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: false} },
|
||||
"hysteria": func() interface{} { return new(HysteriaServerConfig) },
|
||||
"tun": func() interface{} { return new(TunConfig) },
|
||||
}, "protocol", "settings")
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/hpke"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/reality/hpke"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
@@ -40,15 +43,15 @@ func executeECH(cmd *base.Command, args []string) {
|
||||
// if *input_pqSignatureSchemesEnabled {
|
||||
// kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
|
||||
// } else {
|
||||
kem = hpke.DHKEM_X25519_HKDF_SHA256
|
||||
kem = hpke.DHKEM(ecdh.X25519()).ID()
|
||||
// }
|
||||
|
||||
echConfig, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem)
|
||||
echConfig, priv, err := generateECHKeySet(0, *input_serverName, kem)
|
||||
common.Must(err)
|
||||
|
||||
var configBuffer, keyBuffer []byte
|
||||
if *input_echServerKeys == "" {
|
||||
configBytes, _ := tls.MarshalBinary(echConfig)
|
||||
configBytes, _ := marshalBinary(echConfig)
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
child.AddBytes(configBytes)
|
||||
@@ -91,3 +94,86 @@ func executeECH(cmd *base.Command, args []string) {
|
||||
os.Stdout.WriteString("ECH server keys: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
type EchConfig struct {
|
||||
Version uint16
|
||||
ConfigID uint8
|
||||
KemID uint16
|
||||
PublicKey []byte
|
||||
SymmetricCipherSuite []EchCipher
|
||||
MaxNameLength uint8
|
||||
PublicName []byte
|
||||
Extensions []Extension
|
||||
}
|
||||
|
||||
type EchCipher struct {
|
||||
KDFID uint16
|
||||
AEADID uint16
|
||||
}
|
||||
|
||||
type Extension struct {
|
||||
Type uint16
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// reference github.com/OmarTariq612/goech
|
||||
func marshalBinary(ech EchConfig) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(ech.Version)
|
||||
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
child.AddUint8(ech.ConfigID)
|
||||
child.AddUint16(ech.KemID)
|
||||
child.AddUint16(uint16(len(ech.PublicKey)))
|
||||
child.AddBytes(ech.PublicKey)
|
||||
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
for _, cipherSuite := range ech.SymmetricCipherSuite {
|
||||
child.AddUint16(cipherSuite.KDFID)
|
||||
child.AddUint16(cipherSuite.AEADID)
|
||||
}
|
||||
})
|
||||
child.AddUint8(ech.MaxNameLength)
|
||||
child.AddUint8(uint8(len(ech.PublicName)))
|
||||
child.AddBytes(ech.PublicName)
|
||||
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
for _, extention := range ech.Extensions {
|
||||
child.AddUint16(extention.Type)
|
||||
child.AddBytes(extention.Data)
|
||||
}
|
||||
})
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
const ExtensionEncryptedClientHello = 0xfe0d
|
||||
|
||||
func generateECHKeySet(configID uint8, domain string, kem uint16) (EchConfig, []byte, error) {
|
||||
config := EchConfig{
|
||||
Version: ExtensionEncryptedClientHello,
|
||||
ConfigID: configID,
|
||||
PublicName: []byte(domain),
|
||||
KemID: kem,
|
||||
SymmetricCipherSuite: []EchCipher{
|
||||
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES128GCM().ID()},
|
||||
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES256GCM().ID()},
|
||||
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
|
||||
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES128GCM().ID()},
|
||||
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES256GCM().ID()},
|
||||
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
|
||||
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES128GCM().ID()},
|
||||
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES256GCM().ID()},
|
||||
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
|
||||
},
|
||||
MaxNameLength: 0,
|
||||
Extensions: nil,
|
||||
}
|
||||
// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {
|
||||
curve := ecdh.X25519()
|
||||
priv := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, priv)
|
||||
if err != nil {
|
||||
return config, nil, err
|
||||
}
|
||||
privKey, _ := curve.NewPrivateKey(priv)
|
||||
config.PublicKey = privKey.PublicKey().Bytes()
|
||||
return config, priv, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
. "github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
var cmdHash = &base.Command{
|
||||
UsageLine: "{{.Exec}} tls hash",
|
||||
Short: "Calculate TLS certificate hash.",
|
||||
Long: `
|
||||
xray tls hash --cert <cert.pem>
|
||||
Calculate TLS certificate hash.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdHash.Run = executeHash // break init loop
|
||||
}
|
||||
|
||||
var input = cmdHash.Flag.String("cert", "fullchain.pem", "The file path of the certificate")
|
||||
|
||||
func executeHash(cmd *base.Command, args []string) {
|
||||
fs := flag.NewFlagSet("hash", flag.ContinueOnError)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
certContent, err := os.ReadFile(*input)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
var certs []*x509.Certificate
|
||||
if bytes.Contains(certContent, []byte("BEGIN")) {
|
||||
for {
|
||||
block, remain := pem.Decode(certContent)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
fmt.Println("Unable to decode certificate:", err)
|
||||
return
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
certContent = remain
|
||||
}
|
||||
} else {
|
||||
certs, err = x509.ParseCertificates(certContent)
|
||||
if err != nil {
|
||||
fmt.Println("Unable to parse certificates:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(certs) == 0 {
|
||||
fmt.Println("No certificates found")
|
||||
return
|
||||
}
|
||||
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
for i, cert := range certs {
|
||||
hash := GenerateCertHashHex(cert)
|
||||
if i == 0 {
|
||||
fmt.Fprintf(tabWriter, "Leaf SHA256:\t%s\n", hash)
|
||||
} else {
|
||||
fmt.Fprintf(tabWriter, "CA <%s> SHA256:\t%s\n", cert.Subject.CommonName, hash)
|
||||
}
|
||||
}
|
||||
tabWriter.Flush()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
var cmdLeafCertHash = &base.Command{
|
||||
UsageLine: "{{.Exec}} tls leafCertHash",
|
||||
Short: "Calculate TLS leaf certificate hash.",
|
||||
Long: `
|
||||
xray tls leafCertHash --cert <cert.pem>
|
||||
Calculate TLS leaf certificate hash.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdLeafCertHash.Run = executeLeafCertHash // break init loop
|
||||
}
|
||||
|
||||
var input = cmdLeafCertHash.Flag.String("cert", "fullchain.pem", "The file path of the leaf certificate")
|
||||
|
||||
func executeLeafCertHash(cmd *base.Command, args []string) {
|
||||
fs := flag.NewFlagSet("leafCertHash", flag.ContinueOnError)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
certContent, err := os.ReadFile(*input)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
certChainHashB64, err := tls.CalculatePEMLeafCertSHA256Hash(certContent)
|
||||
if err != nil {
|
||||
fmt.Println("failed to decode cert", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(certChainHashB64)
|
||||
}
|
||||
@@ -6,8 +6,13 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
. "github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
@@ -46,6 +51,7 @@ func executePing(cmd *base.Command, args []string) {
|
||||
} else {
|
||||
TargetPort, _ = strconv.Atoi(port)
|
||||
}
|
||||
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
|
||||
var ip net.IP
|
||||
if len(*pingIPStr) > 0 {
|
||||
@@ -70,7 +76,7 @@ func executePing(cmd *base.Command, args []string) {
|
||||
if err != nil {
|
||||
base.Fatalf("Failed to dial tcp: %s", err)
|
||||
}
|
||||
tlsConn := gotls.Client(tcpConn, &gotls.Config{
|
||||
tlsConn := GeneraticUClient(tcpConn, &gotls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
MaxVersion: gotls.VersionTLS13,
|
||||
@@ -81,8 +87,9 @@ func executePing(cmd *base.Command, args []string) {
|
||||
fmt.Println("Handshake failure: ", err)
|
||||
} else {
|
||||
fmt.Println("Handshake succeeded")
|
||||
printTLSConnDetail(tlsConn)
|
||||
printCertificates(tlsConn.ConnectionState().PeerCertificates)
|
||||
printTLSConnDetail(tabWriter, tlsConn)
|
||||
printCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)
|
||||
tabWriter.Flush()
|
||||
}
|
||||
tlsConn.Close()
|
||||
}
|
||||
@@ -94,7 +101,7 @@ func executePing(cmd *base.Command, args []string) {
|
||||
if err != nil {
|
||||
base.Fatalf("Failed to dial tcp: %s", err)
|
||||
}
|
||||
tlsConn := gotls.Client(tcpConn, &gotls.Config{
|
||||
tlsConn := GeneraticUClient(tcpConn, &gotls.Config{
|
||||
ServerName: domain,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
MaxVersion: gotls.VersionTLS13,
|
||||
@@ -105,8 +112,9 @@ func executePing(cmd *base.Command, args []string) {
|
||||
fmt.Println("Handshake failure: ", err)
|
||||
} else {
|
||||
fmt.Println("Handshake succeeded")
|
||||
printTLSConnDetail(tlsConn)
|
||||
printCertificates(tlsConn.ConnectionState().PeerCertificates)
|
||||
printTLSConnDetail(tabWriter, tlsConn)
|
||||
printCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)
|
||||
tabWriter.Flush()
|
||||
}
|
||||
tlsConn.Close()
|
||||
}
|
||||
@@ -115,38 +123,45 @@ func executePing(cmd *base.Command, args []string) {
|
||||
fmt.Println("TLS ping finished")
|
||||
}
|
||||
|
||||
func printCertificates(certs []*x509.Certificate) {
|
||||
func printCertificates(tabWriter *tabwriter.Writer, certs []*x509.Certificate) {
|
||||
var leaf *x509.Certificate
|
||||
var CAs []*x509.Certificate
|
||||
var length int
|
||||
for _, cert := range certs {
|
||||
length += len(cert.Raw)
|
||||
if len(cert.DNSNames) != 0 {
|
||||
leaf = cert
|
||||
} else {
|
||||
CAs = append(CAs, cert)
|
||||
}
|
||||
}
|
||||
fmt.Println("Certificate chain's total length: ", length, "(certs count: "+strconv.Itoa(len(certs))+")")
|
||||
fmt.Fprintf(tabWriter, "Certificate chain's total length:\t%d (certs count: %s)\n", length, strconv.Itoa(len(certs)))
|
||||
if leaf != nil {
|
||||
fmt.Println("Cert's signature algorithm: ", leaf.SignatureAlgorithm.String())
|
||||
fmt.Println("Cert's publicKey algorithm: ", leaf.PublicKeyAlgorithm.String())
|
||||
fmt.Println("Cert's allowed domains: ", leaf.DNSNames)
|
||||
fmt.Println("Cert's leaf SHA256: ", hex.EncodeToString(GenerateCertHash(leaf)))
|
||||
fmt.Fprintf(tabWriter, "Cert's signature algorithm:\t%s\n", leaf.SignatureAlgorithm.String())
|
||||
fmt.Fprintf(tabWriter, "Cert's publicKey algorithm:\t%s\n", leaf.PublicKeyAlgorithm.String())
|
||||
fmt.Fprintf(tabWriter, "Cert's leaf SHA256:\t%s\n", hex.EncodeToString(GenerateCertHash(leaf)))
|
||||
for _, ca := range CAs {
|
||||
fmt.Fprintf(tabWriter, "Cert's CA <%s> SHA256:\t%s\n", ca.Subject.CommonName, hex.EncodeToString(GenerateCertHash(ca)))
|
||||
}
|
||||
fmt.Fprintf(tabWriter, "Cert's allowed domains:\t%v\n", leaf.DNSNames)
|
||||
}
|
||||
}
|
||||
|
||||
func printTLSConnDetail(tlsConn *gotls.Conn) {
|
||||
func printTLSConnDetail(tabWriter *tabwriter.Writer, tlsConn *utls.UConn) {
|
||||
connectionState := tlsConn.ConnectionState()
|
||||
var tlsVersion string
|
||||
if connectionState.Version == gotls.VersionTLS13 {
|
||||
switch connectionState.Version {
|
||||
case gotls.VersionTLS13:
|
||||
tlsVersion = "TLS 1.3"
|
||||
} else if connectionState.Version == gotls.VersionTLS12 {
|
||||
case gotls.VersionTLS12:
|
||||
tlsVersion = "TLS 1.2"
|
||||
}
|
||||
fmt.Println("TLS Version: ", tlsVersion)
|
||||
curveID := connectionState.CurveID
|
||||
if curveID != 0 {
|
||||
PostQuantum := (curveID == gotls.X25519MLKEM768)
|
||||
fmt.Println("TLS Post-Quantum key exchange: ", PostQuantum, "("+curveID.String()+")")
|
||||
fmt.Fprintf(tabWriter, "TLS Version:\t%s\n", tlsVersion)
|
||||
curveID := utils.AccessField[utls.CurveID](tlsConn.Conn, "curveID")
|
||||
if curveID != nil {
|
||||
PostQuantum := (*curveID == utls.X25519MLKEM768)
|
||||
fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange:\t%t (%s)\n", PostQuantum, curveID.String())
|
||||
} else {
|
||||
fmt.Println("TLS Post-Quantum key exchange: false (RSA Exchange)")
|
||||
fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: false (RSA Exchange)\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var CmdTLS = &base.Command{
|
||||
Commands: []*base.Command{
|
||||
cmdCert,
|
||||
cmdPing,
|
||||
cmdLeafCertHash,
|
||||
cmdHash,
|
||||
cmdECH,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
@@ -219,6 +220,9 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
|
||||
for _, h := range header {
|
||||
req.Header.Set(h.Key, h.Value)
|
||||
}
|
||||
if req.Header.Get("User-Agent") == "" {
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
}
|
||||
|
||||
connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
|
||||
req.Header.Set("Proxy-Connection", "Keep-Alive")
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func (a *Account) AsAccount() (protocol.Account, error) {
|
||||
return &MemoryAccount{
|
||||
Auth: a.Auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MemoryAccount struct {
|
||||
Auth string
|
||||
}
|
||||
|
||||
func (a *MemoryAccount) Equals(another protocol.Account) bool {
|
||||
if account, ok := another.(*MemoryAccount); ok {
|
||||
return a.Auth == account.Auth
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *MemoryAccount) ToProto() proto.Message {
|
||||
return &Account{
|
||||
Auth: a.Auth,
|
||||
}
|
||||
}
|
||||
|
||||
type Validator struct {
|
||||
emails map[string]struct{}
|
||||
users map[string]*protocol.MemoryUser
|
||||
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewValidator() *Validator {
|
||||
return &Validator{
|
||||
emails: make(map[string]struct{}),
|
||||
users: make(map[string]*protocol.MemoryUser),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) Add(u *protocol.MemoryUser) error {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
if u.Email != "" {
|
||||
if _, ok := v.emails[u.Email]; ok {
|
||||
return errors.New("User ", u.Email, " already exists.")
|
||||
}
|
||||
v.emails[u.Email] = struct{}{}
|
||||
}
|
||||
v.users[u.Account.(*MemoryAccount).Auth] = u
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) Del(email string) error {
|
||||
if email == "" {
|
||||
return errors.New("Email must not be empty.")
|
||||
}
|
||||
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
if _, ok := v.emails[email]; !ok {
|
||||
return errors.New("User ", email, " not found.")
|
||||
}
|
||||
delete(v.emails, email)
|
||||
for key, user := range v.users {
|
||||
if user.Email == email {
|
||||
delete(v.users, key)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) Get(auth string) *protocol.MemoryUser {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
return v.users[auth]
|
||||
}
|
||||
|
||||
func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
|
||||
if email == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
if _, ok := v.emails[email]; ok {
|
||||
for _, user := range v.users {
|
||||
if user.Email == email {
|
||||
return user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) GetAll() []*protocol.MemoryUser {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
var users = make([]*protocol.MemoryUser, 0, len(v.users))
|
||||
for _, user := range v.users {
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func (v *Validator) GetCount() int64 {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
return int64(len(v.users))
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// source: proxy/hysteria/account/config.proto
|
||||
|
||||
package account
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Auth string `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Account) Reset() {
|
||||
*x = Account{}
|
||||
mi := &file_proxy_hysteria_account_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Account) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Account) ProtoMessage() {}
|
||||
|
||||
func (x *Account) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_hysteria_account_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
|
||||
func (*Account) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_hysteria_account_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Account) GetAuth() string {
|
||||
if x != nil {
|
||||
return x.Auth
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proxy_hysteria_account_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_hysteria_account_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"#proxy/hysteria/account/config.proto\x12\x1bxray.proxy.hysteria.account\"\x1d\n" +
|
||||
"\aAccount\x12\x12\n" +
|
||||
"\x04auth\x18\x01 \x01(\tR\x04authBs\n" +
|
||||
"\x1fcom.xray.proxy.hysteria.accountP\x01Z0github.com/xtls/xray-core/proxy/hysteria/account\xaa\x02\x1bXray.Proxy.Hysteria.Accountb\x06proto3"
|
||||
|
||||
var (
|
||||
file_proxy_hysteria_account_config_proto_rawDescOnce sync.Once
|
||||
file_proxy_hysteria_account_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proxy_hysteria_account_config_proto_rawDescGZIP() []byte {
|
||||
file_proxy_hysteria_account_config_proto_rawDescOnce.Do(func() {
|
||||
file_proxy_hysteria_account_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_hysteria_account_config_proto_rawDesc), len(file_proxy_hysteria_account_config_proto_rawDesc)))
|
||||
})
|
||||
return file_proxy_hysteria_account_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proxy_hysteria_account_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_proxy_hysteria_account_config_proto_goTypes = []any{
|
||||
(*Account)(nil), // 0: xray.proxy.hysteria.account.Account
|
||||
}
|
||||
var file_proxy_hysteria_account_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_hysteria_account_config_proto_init() }
|
||||
func file_proxy_hysteria_account_config_proto_init() {
|
||||
if File_proxy_hysteria_account_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_account_config_proto_rawDesc), len(file_proxy_hysteria_account_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proxy_hysteria_account_config_proto_goTypes,
|
||||
DependencyIndexes: file_proxy_hysteria_account_config_proto_depIdxs,
|
||||
MessageInfos: file_proxy_hysteria_account_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proxy_hysteria_account_config_proto = out.File
|
||||
file_proxy_hysteria_account_config_proto_goTypes = nil
|
||||
file_proxy_hysteria_account_config_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.proxy.hysteria.account;
|
||||
option csharp_namespace = "Xray.Proxy.Hysteria.Account";
|
||||
option go_package = "github.com/xtls/xray-core/proxy/hysteria/account";
|
||||
option java_package = "com.xray.proxy.hysteria.account";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Account {
|
||||
string auth = 1;
|
||||
}
|
||||
+44
-25
@@ -135,6 +135,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
||||
if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to transport all UDP request").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,12 +144,14 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
|
||||
|
||||
reader := &UDPReader{
|
||||
Reader: conn,
|
||||
buf: make([]byte, MaxUDPSize),
|
||||
df: &Defragger{},
|
||||
}
|
||||
|
||||
if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to transport all UDP response").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -178,7 +181,6 @@ type UDPWriter struct {
|
||||
func (w *UDPWriter) sendMsg(msg *UDPMessage) error {
|
||||
msgN := msg.Serialize(w.buf)
|
||||
if msgN < 0 {
|
||||
// Message larger than buffer, silent drop
|
||||
return nil
|
||||
}
|
||||
_, err := w.Writer.Write(w.buf[:msgN])
|
||||
@@ -192,10 +194,12 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
|
||||
addr := w.addr
|
||||
if b.UDP != nil {
|
||||
addr = b.UDP.NetAddr()
|
||||
}
|
||||
|
||||
msg := &UDPMessage{
|
||||
SessionID: 0,
|
||||
PacketID: 0,
|
||||
@@ -204,47 +208,58 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
Addr: addr,
|
||||
Data: b.Bytes(),
|
||||
}
|
||||
if err := w.sendMsg(msg); err != nil {
|
||||
var errTooLarge *quic.DatagramTooLargeError
|
||||
if go_errors.As(err, &errTooLarge) {
|
||||
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
|
||||
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
|
||||
for _, fMsg := range fMsgs {
|
||||
err := w.sendMsg(&fMsg)
|
||||
if err != nil {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
|
||||
err := w.sendMsg(msg)
|
||||
var errTooLarge *quic.DatagramTooLargeError
|
||||
if go_errors.As(err, &errTooLarge) {
|
||||
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
|
||||
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
|
||||
for _, fMsg := range fMsgs {
|
||||
err := w.sendMsg(&fMsg)
|
||||
if err != nil {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
|
||||
b.Release()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UDPReader struct {
|
||||
Reader io.Reader
|
||||
df *Defragger
|
||||
Reader io.Reader
|
||||
buf []byte
|
||||
df *Defragger
|
||||
firstMsg *UDPMessage
|
||||
firstDest *net.Destination
|
||||
}
|
||||
|
||||
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
if r.firstMsg != nil {
|
||||
buffer := buf.New()
|
||||
buffer.Write(r.firstMsg.Data)
|
||||
buffer.UDP = r.firstDest
|
||||
|
||||
r.firstMsg = nil
|
||||
|
||||
return buf.MultiBuffer{buffer}, nil
|
||||
}
|
||||
for {
|
||||
b := buf.New()
|
||||
_, err := b.ReadFrom(r.Reader)
|
||||
n, err := r.Reader.Read(r.buf)
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := ParseUDPMessage(b.Bytes())
|
||||
msg, err := ParseUDPMessage(r.buf[:n])
|
||||
if err != nil {
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -253,7 +268,11 @@ func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
dest, _ := net.ParseDestination("udp:" + dfMsg.Addr)
|
||||
dest, err := net.ParseDestination("udp:" + dfMsg.Addr)
|
||||
if err != nil {
|
||||
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
buffer := buf.New()
|
||||
buffer.Write(dfMsg.Data)
|
||||
|
||||
@@ -5,6 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
tcpRequestPadding = padding.Padding{Min: 64, Max: 512}
|
||||
// tcpResponsePadding = padding.Padding{Min: 128, Max: 1024}
|
||||
tcpRequestPadding = padding.Padding{Min: 64, Max: 512}
|
||||
tcpResponsePadding = padding.Padding{Min: 128, Max: 1024}
|
||||
)
|
||||
|
||||
+60
-11
@@ -74,14 +74,60 @@ func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ServerConfig) Reset() {
|
||||
*x = ServerConfig{}
|
||||
mi := &file_proxy_hysteria_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ServerConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ServerConfig) ProtoMessage() {}
|
||||
|
||||
func (x *ServerConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_hysteria_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
|
||||
func (*ServerConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ServerConfig) GetUsers() []*protocol.User {
|
||||
if x != nil {
|
||||
return x.Users
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proxy_hysteria_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_hysteria_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\"f\n" +
|
||||
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\x1a\x1acommon/protocol/user.proto\"f\n" +
|
||||
"\fClientConfig\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" +
|
||||
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06serverB[\n" +
|
||||
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"@\n" +
|
||||
"\fServerConfig\x120\n" +
|
||||
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05usersB[\n" +
|
||||
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -96,18 +142,21 @@ func file_proxy_hysteria_config_proto_rawDescGZIP() []byte {
|
||||
return file_proxy_hysteria_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proxy_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_proxy_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_proxy_hysteria_config_proto_goTypes = []any{
|
||||
(*ClientConfig)(nil), // 0: xray.proxy.hysteria.ClientConfig
|
||||
(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint
|
||||
(*ServerConfig)(nil), // 1: xray.proxy.hysteria.ServerConfig
|
||||
(*protocol.ServerEndpoint)(nil), // 2: xray.common.protocol.ServerEndpoint
|
||||
(*protocol.User)(nil), // 3: xray.common.protocol.User
|
||||
}
|
||||
var file_proxy_hysteria_config_proto_depIdxs = []int32{
|
||||
1, // 0: xray.proxy.hysteria.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
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.proxy.hysteria.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
3, // 1: xray.proxy.hysteria.ServerConfig.users:type_name -> xray.common.protocol.User
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_hysteria_config_proto_init() }
|
||||
@@ -121,7 +170,7 @@ func file_proxy_hysteria_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -7,8 +7,13 @@ option java_package = "com.xray.proxy.hysteria";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/protocol/server_spec.proto";
|
||||
import "common/protocol/user.proto";
|
||||
|
||||
message ClientConfig {
|
||||
int32 version = 1;
|
||||
xray.common.protocol.ServerEndpoint server = 2;
|
||||
}
|
||||
|
||||
message ServerConfig {
|
||||
repeated xray.common.protocol.User users = 1;
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package ctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const (
|
||||
requireDatagram key = iota
|
||||
validator
|
||||
)
|
||||
|
||||
func ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context {
|
||||
@@ -21,3 +24,12 @@ func RequireDatagramFromContext(ctx context.Context) bool {
|
||||
_, ok := ctx.Value(requireDatagram).(struct{})
|
||||
return ok
|
||||
}
|
||||
|
||||
func ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {
|
||||
return context.WithValue(ctx, validator, v)
|
||||
}
|
||||
|
||||
func ValidatorFromContext(ctx context.Context) *account.Validator {
|
||||
v, _ := ctx.Value(validator).(*account.Validator)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
FrameTypeTCPRequest = 0x401
|
||||
|
||||
// Max length values are for preventing DoS attacks
|
||||
|
||||
MaxAddressLength = 2048
|
||||
@@ -28,22 +26,49 @@ const (
|
||||
)
|
||||
|
||||
// TCPRequest format:
|
||||
// 0x401 (QUIC varint)
|
||||
// Address length (QUIC varint)
|
||||
// Address (bytes)
|
||||
// Padding length (QUIC varint)
|
||||
// Padding (bytes)
|
||||
|
||||
func ReadTCPRequest(r io.Reader) (string, error) {
|
||||
bReader := quicvarint.NewReader(r)
|
||||
addrLen, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if addrLen == 0 || addrLen > MaxAddressLength {
|
||||
return "", errors.New("invalid address length")
|
||||
}
|
||||
addrBuf := make([]byte, addrLen)
|
||||
_, err = io.ReadFull(r, addrBuf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
paddingLen, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if paddingLen > MaxPaddingLength {
|
||||
return "", errors.New("invalid padding length")
|
||||
}
|
||||
if paddingLen > 0 {
|
||||
_, err = io.CopyN(io.Discard, r, int64(paddingLen))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return string(addrBuf), nil
|
||||
}
|
||||
|
||||
func WriteTCPRequest(w io.Writer, addr string) error {
|
||||
padding := tcpRequestPadding.String()
|
||||
paddingLen := len(padding)
|
||||
addrLen := len(addr)
|
||||
sz := int(quicvarint.Len(FrameTypeTCPRequest)) +
|
||||
int(quicvarint.Len(uint64(addrLen))) + addrLen +
|
||||
sz := int(quicvarint.Len(uint64(addrLen))) + addrLen +
|
||||
int(quicvarint.Len(uint64(paddingLen))) + paddingLen
|
||||
buf := make([]byte, sz)
|
||||
i := varintPut(buf, FrameTypeTCPRequest)
|
||||
i += varintPut(buf[i:], uint64(addrLen))
|
||||
i := varintPut(buf, uint64(addrLen))
|
||||
i += copy(buf[i:], addr)
|
||||
i += varintPut(buf[i:], uint64(paddingLen))
|
||||
copy(buf[i:], padding)
|
||||
@@ -96,6 +121,26 @@ func ReadTCPResponse(r io.Reader) (bool, string, error) {
|
||||
return status[0] == 0, string(msgBuf), nil
|
||||
}
|
||||
|
||||
func WriteTCPResponse(w io.Writer, ok bool, msg string) error {
|
||||
padding := tcpResponsePadding.String()
|
||||
paddingLen := len(padding)
|
||||
msgLen := len(msg)
|
||||
sz := 1 + int(quicvarint.Len(uint64(msgLen))) + msgLen +
|
||||
int(quicvarint.Len(uint64(paddingLen))) + paddingLen
|
||||
buf := make([]byte, sz)
|
||||
if ok {
|
||||
buf[0] = 0
|
||||
} else {
|
||||
buf[0] = 1
|
||||
}
|
||||
i := varintPut(buf[1:], uint64(msgLen))
|
||||
i += copy(buf[1+i:], msg)
|
||||
i += varintPut(buf[1+i:], uint64(paddingLen))
|
||||
copy(buf[1+i:], padding)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// UDPMessage format:
|
||||
// Session ID (uint32 BE)
|
||||
// Packet ID (uint16 BE)
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
config *ServerConfig
|
||||
validator *account.Validator
|
||||
policyManager policy.Manager
|
||||
}
|
||||
|
||||
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
|
||||
validator := account.NewValidator()
|
||||
for _, user := range config.Users {
|
||||
u, err := user.ToMemoryUser()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to get hysteria user").Base(err).AtError()
|
||||
}
|
||||
|
||||
if err := validator.Add(u); err != nil {
|
||||
return nil, errors.New("failed to add user").Base(err).AtError()
|
||||
}
|
||||
}
|
||||
|
||||
v := core.MustFromContext(ctx)
|
||||
s := &Server{
|
||||
config: config,
|
||||
validator: validator,
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) HysteriaInboundValidator() *account.Validator {
|
||||
return s.validator
|
||||
}
|
||||
|
||||
func (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {
|
||||
return s.validator.Add(u)
|
||||
}
|
||||
|
||||
func (s *Server) RemoveUser(ctx context.Context, e string) error {
|
||||
return s.validator.Del(e)
|
||||
}
|
||||
|
||||
func (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {
|
||||
return s.validator.GetByEmail(email)
|
||||
}
|
||||
|
||||
func (s *Server) GetUsers(ctx context.Context) []*protocol.MemoryUser {
|
||||
return s.validator.GetAll()
|
||||
}
|
||||
|
||||
func (s *Server) GetUsersCount(context.Context) int64 {
|
||||
return s.validator.GetCount()
|
||||
}
|
||||
|
||||
func (s *Server) Network() []net.Network {
|
||||
return []net.Network{net.Network_TCP}
|
||||
}
|
||||
|
||||
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
inbound.Name = "hysteria"
|
||||
inbound.CanSpliceCopy = 3
|
||||
|
||||
var useremail string
|
||||
var userlevel uint32
|
||||
type User interface{ User() *protocol.MemoryUser }
|
||||
if v, ok := conn.(User); ok {
|
||||
inbound.User = v.User()
|
||||
if inbound.User != nil {
|
||||
useremail = inbound.User.Email
|
||||
userlevel = inbound.User.Level
|
||||
}
|
||||
}
|
||||
|
||||
iConn := stat.TryUnwrapStatsConn(conn)
|
||||
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
|
||||
r := io.Reader(conn)
|
||||
b := make([]byte, MaxUDPSize)
|
||||
df := &Defragger{}
|
||||
var firstMsg *UDPMessage
|
||||
var firstDest net.Destination
|
||||
|
||||
for {
|
||||
n, err := r.Read(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := ParseUDPMessage(b[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dfMsg := df.Feed(msg)
|
||||
if dfMsg == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
firstMsg = dfMsg
|
||||
firstDest, err = net.ParseDestination("udp:" + firstMsg.Addr)
|
||||
if err != nil {
|
||||
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
reader := &UDPReader{
|
||||
Reader: r,
|
||||
buf: b,
|
||||
df: df,
|
||||
firstMsg: firstMsg,
|
||||
firstDest: &firstDest,
|
||||
}
|
||||
|
||||
writer := &UDPWriter{
|
||||
Writer: conn,
|
||||
buf: make([]byte, MaxUDPSize),
|
||||
addr: firstMsg.Addr,
|
||||
}
|
||||
|
||||
return dispatcher.DispatchLink(ctx, firstDest, &transport.Link{
|
||||
Reader: reader,
|
||||
Writer: writer,
|
||||
})
|
||||
} else {
|
||||
sessionPolicy := s.policyManager.ForLevel(userlevel)
|
||||
|
||||
common.Must(conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)))
|
||||
addr, err := ReadTCPRequest(conn)
|
||||
if err != nil {
|
||||
log.Record(&log.AccessMessage{
|
||||
From: conn.RemoteAddr(),
|
||||
To: "",
|
||||
Status: log.AccessRejected,
|
||||
Reason: err,
|
||||
})
|
||||
return errors.New("failed to create request from: ", conn.RemoteAddr()).Base(err)
|
||||
}
|
||||
common.Must(conn.SetReadDeadline(time.Time{}))
|
||||
|
||||
dest, err := net.ParseDestination("tcp:" + addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: conn.RemoteAddr(),
|
||||
To: dest,
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
Email: useremail,
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to ", dest)
|
||||
|
||||
bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
|
||||
err = WriteTCPResponse(bufferedWriter, true, "")
|
||||
if err != nil {
|
||||
return errors.New("failed to write response").Base(err)
|
||||
}
|
||||
if err := bufferedWriter.SetBuffered(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dispatcher.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: buf.NewReader(conn),
|
||||
Writer: bufferedWriter,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
return NewServer(ctx, config.(*ServerConfig))
|
||||
}))
|
||||
}
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
maxPollDelay = 10 * time.Second
|
||||
pollDelayMultiplier = 2.0
|
||||
pollLimit = 16
|
||||
windowSize = 1000
|
||||
)
|
||||
|
||||
type packet struct {
|
||||
@@ -30,7 +31,6 @@ type packet struct {
|
||||
type seqStatus struct {
|
||||
needSeqByte bool
|
||||
seqByte byte
|
||||
received bool
|
||||
}
|
||||
|
||||
type xicmpConnClient struct {
|
||||
@@ -129,12 +129,14 @@ func (c *xicmpConnClient) encode(p []byte) ([]byte, error) {
|
||||
c.seqStatus[c.seq] = &seqStatus{
|
||||
needSeqByte: needSeqByte,
|
||||
seqByte: seqByte,
|
||||
received: false,
|
||||
}
|
||||
|
||||
delete(c.seqStatus, int(uint16(c.seq-windowSize)))
|
||||
|
||||
c.seq++
|
||||
|
||||
if c.seq == 65536 {
|
||||
delete(c.seqStatus, int(uint16(c.seq-windowSize)))
|
||||
c.seq = 1
|
||||
}
|
||||
|
||||
@@ -168,16 +170,14 @@ func (c *xicmpConnClient) recvLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
seqStatus, ok := c.seqStatus[echo.Seq]
|
||||
c.mutex.Unlock()
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if seqStatus.received {
|
||||
continue
|
||||
}
|
||||
|
||||
if seqStatus.needSeqByte {
|
||||
if len(echo.Data) <= 1 {
|
||||
continue
|
||||
@@ -189,7 +189,9 @@ func (c *xicmpConnClient) recvLoop() {
|
||||
}
|
||||
|
||||
if len(echo.Data) > 0 {
|
||||
seqStatus.received = true
|
||||
c.mutex.Lock()
|
||||
delete(c.seqStatus, echo.Seq)
|
||||
c.mutex.Unlock()
|
||||
|
||||
buf := make([]byte, len(echo.Data))
|
||||
copy(buf, echo.Data)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
func TestConfig_GetServiceName(t *testing.T) {
|
||||
@@ -115,3 +118,12 @@ func TestConfig_GetTunMultiStreamName(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetUserAgent(t *testing.T) {
|
||||
ua := "Test/1.0"
|
||||
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUserAgent(ua))
|
||||
assert.NoError(t, err)
|
||||
defer conn.Close()
|
||||
setUserAgent(conn, ua)
|
||||
assert.Equal(t, ua, reflect.ValueOf(conn).Elem().FieldByName("dopts").FieldByName("copts").FieldByName("UserAgent").String())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/grpc/encoding"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
@@ -167,10 +169,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
dialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize))
|
||||
}
|
||||
|
||||
if grpcSettings.UserAgent != "" {
|
||||
dialOptions = append(dialOptions, grpc.WithUserAgent(grpcSettings.UserAgent))
|
||||
}
|
||||
|
||||
var grpcDestHost string
|
||||
if dest.Address.Family().IsDomain() {
|
||||
grpcDestHost = dest.Address.Domain()
|
||||
@@ -178,10 +176,26 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
|
||||
grpcDestHost = dest.Address.IP().String()
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(
|
||||
net.JoinHostPort(grpcDestHost, dest.Port.String()),
|
||||
conn, err := grpc.NewClient(
|
||||
"passthrough:///"+net.JoinHostPort(grpcDestHost, dest.Port.String()),
|
||||
dialOptions...,
|
||||
)
|
||||
if err == nil {
|
||||
userAgent := grpcSettings.UserAgent
|
||||
if userAgent == "" {
|
||||
userAgent = utils.ChromeUA
|
||||
}
|
||||
setUserAgent(conn, userAgent)
|
||||
conn.Connect()
|
||||
}
|
||||
globalDialerMap[dialerConf{dest, streamSettings}] = conn
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// setUserAgent overrides the user-agent on a ClientConn to remove the
|
||||
// "grpc-go/version" suffix that grpc.WithUserAgent unconditionally appends.
|
||||
func setUserAgent(conn *grpc.ClientConn, ua string) {
|
||||
if f := reflect.ValueOf(conn).Elem().FieldByName("dopts").FieldByName("copts").FieldByName("UserAgent"); f.IsValid() {
|
||||
*(*string)(f.Addr().UnsafePointer()) = ua
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"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/utils"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
@@ -86,6 +87,9 @@ func dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *
|
||||
for key, value := range transportConfiguration.Header {
|
||||
AddHeader(req.Header, key, value)
|
||||
}
|
||||
if req.Header.Get("User-Agent") == "" {
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
}
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
|
||||
|
||||
@@ -31,7 +31,18 @@ func (s *server) Addr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Handle(conn net.Conn) (stat.Connection, error) {
|
||||
func (s *server) Handle(conn net.Conn) {
|
||||
upgradedConn, err := s.upgrade(conn)
|
||||
if err != nil {
|
||||
common.CloseIfExists(conn)
|
||||
errors.LogInfoInner(context.Background(), err, "failed to handle request")
|
||||
return
|
||||
}
|
||||
s.addConn(upgradedConn)
|
||||
}
|
||||
|
||||
// upgrade execute a fake websocket upgrade process and return the available connection
|
||||
func (s *server) upgrade(conn net.Conn) (stat.Connection, error) {
|
||||
connReader := bufio.NewReader(conn)
|
||||
req, err := http.ReadRequest(connReader)
|
||||
if err != nil {
|
||||
@@ -52,7 +63,6 @@ func (s *server) Handle(conn net.Conn) (stat.Connection, error) {
|
||||
connection := strings.ToLower(req.Header.Get("Connection"))
|
||||
upgrade := strings.ToLower(req.Header.Get("Upgrade"))
|
||||
if connection != "upgrade" || upgrade != "websocket" {
|
||||
_ = conn.Close()
|
||||
return nil, errors.New("unrecognized request")
|
||||
}
|
||||
resp := &http.Response{
|
||||
@@ -67,7 +77,6 @@ func (s *server) Handle(conn net.Conn) (stat.Connection, error) {
|
||||
resp.Header.Set("Upgrade", "websocket")
|
||||
err = resp.Write(conn)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -99,12 +108,7 @@ func (s *server) keepAccepting() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
handledConn, err := s.Handle(conn)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to handle request")
|
||||
continue
|
||||
}
|
||||
s.addConn(handledConn)
|
||||
go s.Handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
|
||||
@@ -23,11 +25,15 @@ const (
|
||||
StatusAuthOK = 233
|
||||
|
||||
udpMessageChanSize = 1024
|
||||
|
||||
FrameTypeTCPRequest = 0x401
|
||||
|
||||
idleCleanupInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
|
||||
// authResponsePadding = padding.Padding{Min: 256, Max: 2048}
|
||||
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
|
||||
authResponsePadding = padding.Padding{Min: 256, Max: 2048}
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
@@ -38,6 +38,16 @@ type Config struct {
|
||||
MaxIdleTimeout int64 `protobuf:"varint,13,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
|
||||
KeepAlivePeriod int64 `protobuf:"varint,14,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
|
||||
DisablePathMtuDiscovery bool `protobuf:"varint,15,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
|
||||
MaxIncomingStreams int64 `protobuf:"varint,16,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
|
||||
UdpIdleTimeout int64 `protobuf:"varint,17,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
|
||||
MasqType string `protobuf:"bytes,18,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
|
||||
MasqFile string `protobuf:"bytes,19,opt,name=masq_file,json=masqFile,proto3" json:"masq_file,omitempty"`
|
||||
MasqUrl string `protobuf:"bytes,20,opt,name=masq_url,json=masqUrl,proto3" json:"masq_url,omitempty"`
|
||||
MasqUrlRewriteHost bool `protobuf:"varint,21,opt,name=masq_url_rewrite_host,json=masqUrlRewriteHost,proto3" json:"masq_url_rewrite_host,omitempty"`
|
||||
MasqUrlInsecure bool `protobuf:"varint,22,opt,name=masq_url_insecure,json=masqUrlInsecure,proto3" json:"masq_url_insecure,omitempty"`
|
||||
MasqString string `protobuf:"bytes,23,opt,name=masq_string,json=masqString,proto3" json:"masq_string,omitempty"`
|
||||
MasqStringHeaders map[string]string `protobuf:"bytes,24,rep,name=masq_string_headers,json=masqStringHeaders,proto3" json:"masq_string_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
MasqStringStatusCode int32 `protobuf:"varint,25,opt,name=masq_string_status_code,json=masqStringStatusCode,proto3" json:"masq_string_status_code,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -177,11 +187,81 @@ func (x *Config) GetDisablePathMtuDiscovery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxIncomingStreams() int64 {
|
||||
if x != nil {
|
||||
return x.MaxIncomingStreams
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetUdpIdleTimeout() int64 {
|
||||
if x != nil {
|
||||
return x.UdpIdleTimeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqType() string {
|
||||
if x != nil {
|
||||
return x.MasqType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqFile() string {
|
||||
if x != nil {
|
||||
return x.MasqFile
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqUrl() string {
|
||||
if x != nil {
|
||||
return x.MasqUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqUrlRewriteHost() bool {
|
||||
if x != nil {
|
||||
return x.MasqUrlRewriteHost
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqUrlInsecure() bool {
|
||||
if x != nil {
|
||||
return x.MasqUrlInsecure
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqString() string {
|
||||
if x != nil {
|
||||
return x.MasqString
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqStringHeaders() map[string]string {
|
||||
if x != nil {
|
||||
return x.MasqStringHeaders
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqStringStatusCode() int32 {
|
||||
if x != nil {
|
||||
return x.MasqStringStatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xd1\x04\n" +
|
||||
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xf0\b\n" +
|
||||
"\x06Config\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" +
|
||||
"\x04auth\x18\x02 \x01(\tR\x04auth\x12\x1e\n" +
|
||||
@@ -200,7 +280,21 @@ const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
||||
"\x17max_conn_receive_window\x18\f \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
|
||||
"\x10max_idle_timeout\x18\r \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
|
||||
"\x11keep_alive_period\x18\x0e \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
|
||||
"\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscoveryB\x82\x01\n" +
|
||||
"\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
|
||||
"\x14max_incoming_streams\x18\x10 \x01(\x03R\x12maxIncomingStreams\x12(\n" +
|
||||
"\x10udp_idle_timeout\x18\x11 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
|
||||
"\tmasq_type\x18\x12 \x01(\tR\bmasqType\x12\x1b\n" +
|
||||
"\tmasq_file\x18\x13 \x01(\tR\bmasqFile\x12\x19\n" +
|
||||
"\bmasq_url\x18\x14 \x01(\tR\amasqUrl\x121\n" +
|
||||
"\x15masq_url_rewrite_host\x18\x15 \x01(\bR\x12masqUrlRewriteHost\x12*\n" +
|
||||
"\x11masq_url_insecure\x18\x16 \x01(\bR\x0fmasqUrlInsecure\x12\x1f\n" +
|
||||
"\vmasq_string\x18\x17 \x01(\tR\n" +
|
||||
"masqString\x12o\n" +
|
||||
"\x13masq_string_headers\x18\x18 \x03(\v2?.xray.transport.internet.hysteria.Config.MasqStringHeadersEntryR\x11masqStringHeaders\x125\n" +
|
||||
"\x17masq_string_status_code\x18\x19 \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
|
||||
"\x16MasqStringHeadersEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x82\x01\n" +
|
||||
"$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -215,16 +309,18 @@ func file_transport_internet_hysteria_config_proto_rawDescGZIP() []byte {
|
||||
return file_transport_internet_hysteria_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_transport_internet_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_transport_internet_hysteria_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.transport.internet.hysteria.Config
|
||||
nil, // 1: xray.transport.internet.hysteria.Config.MasqStringHeadersEntry
|
||||
}
|
||||
var file_transport_internet_hysteria_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
1, // 0: xray.transport.internet.hysteria.Config.masq_string_headers:type_name -> xray.transport.internet.hysteria.Config.MasqStringHeadersEntry
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_hysteria_config_proto_init() }
|
||||
@@ -238,7 +334,7 @@ func file_transport_internet_hysteria_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_hysteria_config_proto_rawDesc), len(file_transport_internet_hysteria_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -23,5 +23,15 @@ message Config {
|
||||
int64 max_idle_timeout = 13;
|
||||
int64 keep_alive_period = 14;
|
||||
bool disable_path_mtu_discovery = 15;
|
||||
}
|
||||
int64 max_incoming_streams = 16;
|
||||
|
||||
int64 udp_idle_timeout = 17;
|
||||
string masq_type = 18;
|
||||
string masq_file = 19;
|
||||
string masq_url = 20;
|
||||
bool masq_url_rewrite_host = 21;
|
||||
bool masq_url_insecure = 22;
|
||||
string masq_string = 23;
|
||||
map<string, string> masq_string_headers = 24;
|
||||
int32 masq_string_status_code = 25;
|
||||
}
|
||||
@@ -3,16 +3,28 @@ package hysteria
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/apernet/quic-go/quicvarint"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
)
|
||||
|
||||
type interConn struct {
|
||||
stream *quic.Stream
|
||||
local net.Addr
|
||||
remote net.Addr
|
||||
|
||||
client bool
|
||||
mutex sync.Mutex
|
||||
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (i *interConn) User() *protocol.MemoryUser {
|
||||
return i.user
|
||||
}
|
||||
|
||||
func (i *interConn) Read(b []byte) (int, error) {
|
||||
@@ -20,6 +32,22 @@ func (i *interConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (i *interConn) Write(b []byte) (int, error) {
|
||||
if i.client {
|
||||
i.mutex.Lock()
|
||||
if i.client {
|
||||
buf := make([]byte, 0, quicvarint.Len(FrameTypeTCPRequest)+len(b))
|
||||
buf = quicvarint.Append(buf, FrameTypeTCPRequest)
|
||||
buf = append(buf, b...)
|
||||
_, err := i.stream.Write(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i.client = false
|
||||
return len(b), nil
|
||||
}
|
||||
i.mutex.Unlock()
|
||||
}
|
||||
|
||||
return i.stream.Write(b)
|
||||
}
|
||||
|
||||
@@ -53,10 +81,34 @@ type InterUdpConn struct {
|
||||
local net.Addr
|
||||
remote net.Addr
|
||||
|
||||
id uint32
|
||||
ch chan []byte
|
||||
id uint32
|
||||
ch chan []byte
|
||||
|
||||
closed bool
|
||||
closeFunc func()
|
||||
|
||||
last time.Time
|
||||
mutex sync.Mutex
|
||||
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) User() *protocol.MemoryUser {
|
||||
return i.user
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) SetLast() {
|
||||
i.mutex.Lock()
|
||||
defer i.mutex.Unlock()
|
||||
|
||||
i.last = time.Now()
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) GetLast() time.Time {
|
||||
i.mutex.Lock()
|
||||
defer i.mutex.Unlock()
|
||||
|
||||
return i.last
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) Read(p []byte) (int, error) {
|
||||
@@ -68,10 +120,14 @@ func (i *InterUdpConn) Read(p []byte) (int, error) {
|
||||
if n != len(b) {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
i.SetLast()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) Write(p []byte) (int, error) {
|
||||
i.SetLast()
|
||||
|
||||
binary.BigEndian.PutUint32(p, i.id)
|
||||
if err := i.conn.SendDatagram(p); err != nil {
|
||||
return 0, err
|
||||
|
||||
@@ -26,15 +26,23 @@ import (
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
type udpSessionManager struct {
|
||||
type udpSessionManagerClient struct {
|
||||
conn *quic.Conn
|
||||
m map[uint32]*InterUdpConn
|
||||
nextId uint32
|
||||
next uint32
|
||||
closed bool
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) run() {
|
||||
func (m *udpSessionManagerClient) close(udpConn *InterUdpConn) {
|
||||
if !udpConn.closed {
|
||||
udpConn.closed = true
|
||||
close(udpConn.ch)
|
||||
delete(m.m, udpConn.id)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManagerClient) run() {
|
||||
for {
|
||||
d, err := m.conn.ReceiveDatagram(context.Background())
|
||||
if err != nil {
|
||||
@@ -44,29 +52,22 @@ func (m *udpSessionManager) run() {
|
||||
if len(d) < 4 {
|
||||
continue
|
||||
}
|
||||
sessionId := binary.BigEndian.Uint32(d[:4])
|
||||
id := binary.BigEndian.Uint32(d[:4])
|
||||
|
||||
m.feed(sessionId, d)
|
||||
m.feed(id, d)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.closed = true
|
||||
|
||||
for _, udpConn := range m.m {
|
||||
m.close(udpConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) close(udpConn *InterUdpConn) {
|
||||
if !udpConn.closed {
|
||||
udpConn.closed = true
|
||||
close(udpConn.ch)
|
||||
delete(m.m, udpConn.id)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||
func (m *udpSessionManagerClient) udp() (*InterUdpConn, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
@@ -79,7 +80,7 @@ func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||
local: m.conn.LocalAddr(),
|
||||
remote: m.conn.RemoteAddr(),
|
||||
|
||||
id: m.nextId,
|
||||
id: m.next,
|
||||
ch: make(chan []byte, udpMessageChanSize),
|
||||
}
|
||||
udpConn.closeFunc = func() {
|
||||
@@ -87,17 +88,17 @@ func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||
defer m.mutex.Unlock()
|
||||
m.close(udpConn)
|
||||
}
|
||||
m.m[m.nextId] = udpConn
|
||||
m.nextId++
|
||||
m.m[m.next] = udpConn
|
||||
m.next++
|
||||
|
||||
return udpConn, nil
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) feed(sessionId uint32, d []byte) {
|
||||
func (m *udpSessionManagerClient) feed(id uint32, d []byte) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
udpConn, ok := m.m[sessionId]
|
||||
udpConn, ok := m.m[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -117,7 +118,7 @@ type client struct {
|
||||
tlsConfig *go_tls.Config
|
||||
socketConfig *internet.SocketConfig
|
||||
udpmaskManager *finalmask.UdpmaskManager
|
||||
udpSM *udpSessionManager
|
||||
udpSM *udpSessionManagerClient
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
@@ -269,10 +270,10 @@ func (c *client) dial() error {
|
||||
c.pktConn = pktConn
|
||||
c.conn = quicConn
|
||||
if serverUdp {
|
||||
c.udpSM = &udpSessionManager{
|
||||
conn: quicConn,
|
||||
m: make(map[uint32]*InterUdpConn),
|
||||
nextId: 1,
|
||||
c.udpSM = &udpSessionManagerClient{
|
||||
conn: quicConn,
|
||||
m: make(map[uint32]*InterUdpConn),
|
||||
next: 1,
|
||||
}
|
||||
go c.udpSM.run()
|
||||
}
|
||||
@@ -307,6 +308,8 @@ func (c *client) tcp() (stat.Connection, error) {
|
||||
stream: stream,
|
||||
local: c.conn.LocalAddr(),
|
||||
remote: c.conn.RemoteAddr(),
|
||||
|
||||
client: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/binary"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/apernet/quic-go/http3"
|
||||
"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/protocol"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
type udpSessionManagerServer struct {
|
||||
conn *quic.Conn
|
||||
m map[uint32]*InterUdpConn
|
||||
addConn internet.ConnHandler
|
||||
stopCh chan struct{}
|
||||
udpIdleTimeout time.Duration
|
||||
mutex sync.RWMutex
|
||||
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (m *udpSessionManagerServer) close(udpConn *InterUdpConn) {
|
||||
if !udpConn.closed {
|
||||
udpConn.closed = true
|
||||
close(udpConn.ch)
|
||||
delete(m.m, udpConn.id)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManagerServer) clean() {
|
||||
ticker := time.NewTicker(idleCleanupInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
m.mutex.RLock()
|
||||
now := time.Now()
|
||||
timeoutConn := make([]*InterUdpConn, 0, len(m.m))
|
||||
for _, udpConn := range m.m {
|
||||
if now.Sub(udpConn.GetLast()) > m.udpIdleTimeout {
|
||||
timeoutConn = append(timeoutConn, udpConn)
|
||||
}
|
||||
}
|
||||
m.mutex.RUnlock()
|
||||
|
||||
for _, udpConn := range timeoutConn {
|
||||
m.mutex.Lock()
|
||||
m.close(udpConn)
|
||||
m.mutex.Unlock()
|
||||
}
|
||||
case <-m.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManagerServer) run() {
|
||||
for {
|
||||
d, err := m.conn.ReceiveDatagram(context.Background())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if len(d) < 4 {
|
||||
continue
|
||||
}
|
||||
id := binary.BigEndian.Uint32(d[:4])
|
||||
|
||||
m.feed(id, d)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
close(m.stopCh)
|
||||
|
||||
for _, udpConn := range m.m {
|
||||
m.close(udpConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManagerServer) feed(id uint32, d []byte) {
|
||||
m.mutex.RLock()
|
||||
udpConn, ok := m.m[id]
|
||||
m.mutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
m.mutex.Lock()
|
||||
udpConn, ok = m.m[id]
|
||||
if !ok {
|
||||
udpConn = &InterUdpConn{
|
||||
conn: m.conn,
|
||||
local: m.conn.LocalAddr(),
|
||||
remote: m.conn.RemoteAddr(),
|
||||
|
||||
id: id,
|
||||
ch: make(chan []byte, udpMessageChanSize),
|
||||
last: time.Now(),
|
||||
|
||||
user: m.user,
|
||||
}
|
||||
udpConn.closeFunc = func() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.close(udpConn)
|
||||
}
|
||||
m.m[id] = udpConn
|
||||
m.addConn(udpConn)
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
}
|
||||
|
||||
select {
|
||||
case udpConn.ch <- d:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
type httpHandler struct {
|
||||
ctx context.Context
|
||||
conn *quic.Conn
|
||||
addConn internet.ConnHandler
|
||||
|
||||
config *Config
|
||||
validator *account.Validator
|
||||
masqHandler http.Handler
|
||||
|
||||
auth bool
|
||||
mutex sync.Mutex
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost && r.Host == URLHost && r.URL.Path == URLPath {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.auth {
|
||||
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
|
||||
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.config.Down, 10))
|
||||
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
|
||||
w.WriteHeader(StatusAuthOK)
|
||||
return
|
||||
}
|
||||
|
||||
auth := r.Header.Get(RequestHeaderAuth)
|
||||
clientDown, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)
|
||||
|
||||
var user *protocol.MemoryUser
|
||||
var ok bool
|
||||
if h.validator != nil {
|
||||
user = h.validator.Get(auth)
|
||||
} else if auth == h.config.Auth {
|
||||
ok = true
|
||||
}
|
||||
|
||||
if user != nil || ok {
|
||||
h.auth = true
|
||||
h.user = user
|
||||
|
||||
switch h.config.Congestion {
|
||||
case "reno":
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
|
||||
case "bbr":
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
|
||||
congestion.UseBBR(h.conn)
|
||||
case "brutal", "":
|
||||
if h.config.Up == 0 || clientDown == 0 {
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
|
||||
congestion.UseBBR(h.conn)
|
||||
} else {
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.config.Up, clientDown))
|
||||
congestion.UseBrutal(h.conn, min(h.config.Up, clientDown))
|
||||
}
|
||||
case "force-brutal":
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", h.config.Up)
|
||||
congestion.UseBrutal(h.conn, h.config.Up)
|
||||
default:
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
|
||||
}
|
||||
|
||||
if hyCtx.RequireDatagramFromContext(h.ctx) {
|
||||
udpSM := &udpSessionManagerServer{
|
||||
conn: h.conn,
|
||||
m: make(map[uint32]*InterUdpConn),
|
||||
addConn: h.addConn,
|
||||
stopCh: make(chan struct{}),
|
||||
udpIdleTimeout: time.Duration(h.config.UdpIdleTimeout) * time.Second,
|
||||
|
||||
user: h.user,
|
||||
}
|
||||
go udpSM.clean()
|
||||
go udpSM.run()
|
||||
}
|
||||
|
||||
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
|
||||
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.config.Down, 10))
|
||||
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
|
||||
w.WriteHeader(StatusAuthOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.masqHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *httpHandler) ProxyStreamHijacker(ft http3.FrameType, id quic.ConnectionTracingID, stream *quic.Stream, err error) (bool, error) {
|
||||
if err != nil || !h.auth {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch ft {
|
||||
case FrameTypeTCPRequest:
|
||||
h.addConn(&interConn{
|
||||
stream: stream,
|
||||
local: h.conn.LocalAddr(),
|
||||
remote: h.conn.RemoteAddr(),
|
||||
|
||||
user: h.user,
|
||||
})
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
ctx context.Context
|
||||
pktConn net.PacketConn
|
||||
listener *quic.Listener
|
||||
addConn internet.ConnHandler
|
||||
|
||||
config *Config
|
||||
validator *account.Validator
|
||||
masqHandler http.Handler
|
||||
}
|
||||
|
||||
func (l *Listener) handleClient(conn *quic.Conn) {
|
||||
handler := &httpHandler{
|
||||
ctx: l.ctx,
|
||||
conn: conn,
|
||||
addConn: l.addConn,
|
||||
|
||||
config: l.config,
|
||||
validator: l.validator,
|
||||
masqHandler: l.masqHandler,
|
||||
}
|
||||
h3 := http3.Server{
|
||||
Handler: handler,
|
||||
StreamHijacker: handler.ProxyStreamHijacker,
|
||||
}
|
||||
err := h3.ServeQUICConn(conn)
|
||||
errors.LogDebug(context.Background(), conn.RemoteAddr(), " disconnected with err ", err)
|
||||
_ = conn.CloseWithError(closeErrCodeOK, "")
|
||||
}
|
||||
|
||||
func (l *Listener) keepAccepting() {
|
||||
for {
|
||||
conn, err := l.listener.Accept(context.Background())
|
||||
if err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to accept QUIC connection")
|
||||
break
|
||||
}
|
||||
go l.handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
err := l.listener.Close()
|
||||
_ = l.pktConn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
|
||||
if address.Family().IsDomain() {
|
||||
return nil, errors.New("address is domain")
|
||||
}
|
||||
|
||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||
if tlsConfig == nil {
|
||||
return nil, errors.New("tls config is nil")
|
||||
}
|
||||
|
||||
config := streamSettings.ProtocolSettings.(*Config)
|
||||
|
||||
validator := hyCtx.ValidatorFromContext(ctx)
|
||||
|
||||
if config.Auth == "" && validator == nil {
|
||||
return nil, errors.New("validator is nil")
|
||||
}
|
||||
|
||||
var masqHandler http.Handler
|
||||
switch strings.ToLower(config.MasqType) {
|
||||
case "", "404":
|
||||
masqHandler = http.NotFoundHandler()
|
||||
case "file":
|
||||
masqHandler = http.FileServer(http.Dir(config.MasqFile))
|
||||
case "proxy":
|
||||
u, err := url.Parse(config.MasqUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport := http.DefaultTransport.(*http.Transport)
|
||||
if config.MasqUrlInsecure {
|
||||
transport = transport.Clone()
|
||||
transport.TLSClientConfig = &gotls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
masqHandler = &httputil.ReverseProxy{
|
||||
Rewrite: func(pr *httputil.ProxyRequest) {
|
||||
pr.SetURL(u)
|
||||
if !config.MasqUrlRewriteHost {
|
||||
pr.Out.Host = pr.In.Host
|
||||
}
|
||||
},
|
||||
Transport: transport,
|
||||
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
},
|
||||
}
|
||||
case "string":
|
||||
masqHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for k, v := range config.MasqStringHeaders {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
if config.MasqStringStatusCode != 0 {
|
||||
w.WriteHeader(int(config.MasqStringStatusCode))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
_, _ = w.Write([]byte(config.MasqString))
|
||||
})
|
||||
default:
|
||||
return nil, errors.New("unknown masq type")
|
||||
}
|
||||
|
||||
raw, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pktConn net.PacketConn
|
||||
pktConn = raw
|
||||
|
||||
if streamSettings.UdpmaskManager != nil {
|
||||
pktConn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)
|
||||
if err != nil {
|
||||
raw.Close()
|
||||
return nil, errors.New("mask err").Base(err)
|
||||
}
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: config.InitStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: config.MaxStreamReceiveWindow,
|
||||
InitialConnectionReceiveWindow: config.InitConnReceiveWindow,
|
||||
MaxConnectionReceiveWindow: config.MaxConnReceiveWindow,
|
||||
MaxIdleTimeout: time.Duration(config.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: config.MaxIncomingStreams,
|
||||
DisablePathMTUDiscovery: config.DisablePathMtuDiscovery,
|
||||
EnableDatagrams: true,
|
||||
MaxDatagramFrameSize: MaxDatagramFrameSize,
|
||||
DisablePathManager: true,
|
||||
}
|
||||
|
||||
qListener, err := quic.Listen(pktConn, tlsConfig.GetTLSConfig(), quicConfig)
|
||||
if err != nil {
|
||||
_ = pktConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listener := &Listener{
|
||||
ctx: ctx,
|
||||
pktConn: pktConn,
|
||||
listener: qListener,
|
||||
addConn: handler,
|
||||
|
||||
config: config,
|
||||
validator: validator,
|
||||
masqHandler: masqHandler,
|
||||
}
|
||||
|
||||
go listener.keepAccepting()
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterTransportListener(protocolName, Listen))
|
||||
}
|
||||
@@ -54,15 +54,15 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet
|
||||
return nil, errors.New("failed to dial to dest: ", err).AtWarning().Base(err)
|
||||
}
|
||||
|
||||
wrapper, ok := rawConn.(*internet.PacketConnWrapper)
|
||||
if !ok {
|
||||
rawConn.Close()
|
||||
return nil, errors.New("raw is not PacketConnWrapper")
|
||||
}
|
||||
|
||||
raw := wrapper.Conn
|
||||
|
||||
if streamSettings.UdpmaskManager != nil {
|
||||
wrapper, ok := rawConn.(*internet.PacketConnWrapper)
|
||||
if !ok {
|
||||
rawConn.Close()
|
||||
return nil, errors.New("raw is not PacketConnWrapper")
|
||||
}
|
||||
|
||||
raw := wrapper.Conn
|
||||
|
||||
wrapper.Conn, err = streamSettings.UdpmaskManager.WrapPacketConnClient(raw)
|
||||
if err != nil {
|
||||
raw.Close()
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
@@ -222,7 +223,7 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("User-Agent", fingerprint.Client) // TODO: User-Agent map
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
if first && config.Show {
|
||||
fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
@@ -47,6 +48,9 @@ func (c *Config) GetRequestHeader() http.Header {
|
||||
for k, v := range c.Headers {
|
||||
header.Add(k, v)
|
||||
}
|
||||
if header.Get("User-Agent") == "" {
|
||||
header.Set("User-Agent", utils.ChromeUA)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
|
||||
@@ -289,9 +289,6 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509
|
||||
if len(certs) == 0 {
|
||||
return errors.New("unexpected certs")
|
||||
}
|
||||
if certs[0].IsCA {
|
||||
slices.Reverse(certs)
|
||||
}
|
||||
|
||||
// directly return success if pinned cert is leaf
|
||||
// or replace RootCAs if pinned cert is CA (and can be used in VerifyPeerCertByName)
|
||||
@@ -384,6 +381,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
||||
PinnedPeerCertSha256: c.PinnedPeerCertSha256,
|
||||
}
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: c.AllowInsecure,
|
||||
Rand: randCarrier,
|
||||
ClientSessionCache: globalSessionCache,
|
||||
RootCAs: root,
|
||||
@@ -526,11 +524,13 @@ func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
|
||||
|
||||
func ParseCurveName(curveNames []string) []tls.CurveID {
|
||||
curveMap := map[string]tls.CurveID{
|
||||
"curvep256": tls.CurveP256,
|
||||
"curvep384": tls.CurveP384,
|
||||
"curvep521": tls.CurveP521,
|
||||
"x25519": tls.X25519,
|
||||
"x25519mlkem768": tls.X25519MLKEM768,
|
||||
"curvep256": tls.CurveP256,
|
||||
"curvep384": tls.CurveP384,
|
||||
"curvep521": tls.CurveP521,
|
||||
"x25519": tls.X25519,
|
||||
"x25519mlkem768": tls.X25519MLKEM768,
|
||||
"secp256r1mlkem768": tls.SecP256r1MLKEM768,
|
||||
"secp384r1mlkem1024": tls.SecP384r1MLKEM1024,
|
||||
}
|
||||
|
||||
var curveIDs []tls.CurveID
|
||||
@@ -557,14 +557,19 @@ const (
|
||||
)
|
||||
|
||||
func verifyChain(certs []*x509.Certificate, pinnedPeerCertSha256 [][]byte) (verifyResult, *x509.Certificate) {
|
||||
leafHash := GenerateCertHash(certs[0])
|
||||
for _, c := range pinnedPeerCertSha256 {
|
||||
if hmac.Equal(leafHash, c) {
|
||||
return foundLeaf, nil
|
||||
}
|
||||
}
|
||||
certs = certs[1:] // skip leaf
|
||||
for _, cert := range certs {
|
||||
certHash := GenerateCertHash(cert)
|
||||
for _, c := range pinnedPeerCertSha256 {
|
||||
if hmac.Equal(certHash, c) {
|
||||
if cert.IsCA {
|
||||
return foundCA, cert
|
||||
} else {
|
||||
return foundLeaf, cert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,8 @@ func (x *Certificate) GetBuildChain() bool {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"`
|
||||
// List of certificates to be served on server.
|
||||
Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"`
|
||||
// Override server name.
|
||||
@@ -241,6 +242,13 @@ func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_tls_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Config) GetAllowInsecure() bool {
|
||||
if x != nil {
|
||||
return x.AllowInsecure
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetCertificate() []*Certificate {
|
||||
if x != nil {
|
||||
return x.Certificate
|
||||
@@ -385,8 +393,9 @@ const file_transport_internet_tls_config_proto_rawDesc = "" +
|
||||
"\x05Usage\x12\x10\n" +
|
||||
"\fENCIPHERMENT\x10\x00\x12\x14\n" +
|
||||
"\x10AUTHORITY_VERIFY\x10\x01\x12\x13\n" +
|
||||
"\x0fAUTHORITY_ISSUE\x10\x02\"\xce\x06\n" +
|
||||
"\x06Config\x12J\n" +
|
||||
"\x0fAUTHORITY_ISSUE\x10\x02\"\xf5\x06\n" +
|
||||
"\x06Config\x12%\n" +
|
||||
"\x0eallow_insecure\x18\x01 \x01(\bR\rallowInsecure\x12J\n" +
|
||||
"\vcertificate\x18\x02 \x03(\v2(.xray.transport.internet.tls.CertificateR\vcertificate\x12\x1f\n" +
|
||||
"\vserver_name\x18\x03 \x01(\tR\n" +
|
||||
"serverName\x12#\n" +
|
||||
|
||||
@@ -38,6 +38,8 @@ message Certificate {
|
||||
}
|
||||
|
||||
message Config {
|
||||
bool allow_insecure = 1;
|
||||
|
||||
// List of certificates to be served on server.
|
||||
repeated Certificate certificate = 2;
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ package tls
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
@@ -23,8 +21,6 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/xtls/reality"
|
||||
"github.com/xtls/reality/hpke"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
@@ -257,6 +253,7 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
|
||||
}
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("User-Agent", utils.ChromeUA)
|
||||
req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))
|
||||
|
||||
resp, err := client.Do(req)
|
||||
@@ -329,34 +326,6 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
|
||||
return nil, dns2.DefaultTTL, nil
|
||||
}
|
||||
|
||||
// reference github.com/OmarTariq612/goech
|
||||
func MarshalBinary(ech reality.EchConfig) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(ech.Version)
|
||||
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
child.AddUint8(ech.ConfigID)
|
||||
child.AddUint16(ech.KemID)
|
||||
child.AddUint16(uint16(len(ech.PublicKey)))
|
||||
child.AddBytes(ech.PublicKey)
|
||||
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
for _, cipherSuite := range ech.SymmetricCipherSuite {
|
||||
child.AddUint16(cipherSuite.KDFID)
|
||||
child.AddUint16(cipherSuite.AEADID)
|
||||
}
|
||||
})
|
||||
child.AddUint8(ech.MaxNameLength)
|
||||
child.AddUint8(uint8(len(ech.PublicName)))
|
||||
child.AddBytes(ech.PublicName)
|
||||
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||
for _, extention := range ech.Extensions {
|
||||
child.AddUint16(extention.Type)
|
||||
child.AddBytes(extention.Data)
|
||||
}
|
||||
})
|
||||
})
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
var ErrInvalidLen = errors.New("goech: invalid length")
|
||||
|
||||
func ConvertToGoECHKeys(data []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
@@ -391,41 +360,3 @@ func ConvertToGoECHKeys(data []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
const ExtensionEncryptedClientHello = 0xfe0d
|
||||
const KDF_HKDF_SHA384 = 0x0002
|
||||
const KDF_HKDF_SHA512 = 0x0003
|
||||
|
||||
func GenerateECHKeySet(configID uint8, domain string, kem uint16) (reality.EchConfig, []byte, error) {
|
||||
config := reality.EchConfig{
|
||||
Version: ExtensionEncryptedClientHello,
|
||||
ConfigID: configID,
|
||||
PublicName: []byte(domain),
|
||||
KemID: kem,
|
||||
SymmetricCipherSuite: []reality.EchCipher{
|
||||
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_AES_128_GCM},
|
||||
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_AES_256_GCM},
|
||||
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_ChaCha20Poly1305},
|
||||
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_AES_128_GCM},
|
||||
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_AES_256_GCM},
|
||||
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_ChaCha20Poly1305},
|
||||
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_AES_128_GCM},
|
||||
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_AES_256_GCM},
|
||||
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_ChaCha20Poly1305},
|
||||
},
|
||||
MaxNameLength: 0,
|
||||
Extensions: nil,
|
||||
}
|
||||
// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {
|
||||
curve := ecdh.X25519()
|
||||
priv := make([]byte, 32) //x25519
|
||||
_, err := io.ReadFull(rand.Reader, priv)
|
||||
if err != nil {
|
||||
return config, nil, err
|
||||
}
|
||||
privKey, _ := curve.NewPrivateKey(priv)
|
||||
config.PublicKey = privKey.PublicKey().Bytes()
|
||||
return config, priv, nil
|
||||
// }
|
||||
// TODO: add mlkem768 (former kyber768 draft00). The golang mlkem private key is 64 bytes seed?
|
||||
}
|
||||
|
||||
@@ -4,28 +4,8 @@ import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
)
|
||||
|
||||
func CalculatePEMLeafCertSHA256Hash(certContent []byte) (string, error) {
|
||||
var leafCert *x509.Certificate
|
||||
for {
|
||||
var err error
|
||||
block, remain := pem.Decode(certContent)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
leafCert, err = x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
certContent = remain
|
||||
}
|
||||
certHash := GenerateCertHash(leafCert)
|
||||
certHashHex := hex.EncodeToString(certHash)
|
||||
return certHashHex, nil
|
||||
}
|
||||
|
||||
// []byte must be ASN.1 DER content
|
||||
func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {
|
||||
var out [32]byte
|
||||
@@ -37,3 +17,14 @@ func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {
|
||||
}
|
||||
return out[:]
|
||||
}
|
||||
|
||||
func GenerateCertHashHex[T *x509.Certificate | []byte](cert T) string {
|
||||
var out [32]byte
|
||||
switch v := any(cert).(type) {
|
||||
case *x509.Certificate:
|
||||
out = sha256.Sum256(v.Raw)
|
||||
case []byte:
|
||||
out = sha256.Sum256(v)
|
||||
}
|
||||
return hex.EncodeToString(out[:])
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
@@ -97,6 +98,12 @@ func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
|
||||
if err := c.BuildHandshakeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
config := *utils.AccessField[*utls.Config](c, "config")
|
||||
// Do not modify outer ALPN to http/1.1 if ECH is used
|
||||
// Outer ALPN will be h2,http/1.1, and real ALPN in config will be hidden in ECH
|
||||
if config.EncryptedClientHelloConfigList != nil {
|
||||
return c.HandshakeContext(ctx)
|
||||
}
|
||||
// Iterate over extensions and check for utls.ALPNExtension
|
||||
hasALPNExtension := false
|
||||
for _, extension := range c.Extensions {
|
||||
@@ -126,8 +133,12 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
|
||||
return &UConn{UConn: utlsConn}
|
||||
}
|
||||
|
||||
func GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {
|
||||
return utls.UClient(c, copyConfig(config), utls.HelloChrome_Auto)
|
||||
}
|
||||
|
||||
func copyConfig(c *tls.Config) *utls.Config {
|
||||
return &utls.Config{
|
||||
config := &utls.Config{
|
||||
Rand: c.Rand,
|
||||
RootCAs: c.RootCAs,
|
||||
ServerName: c.ServerName,
|
||||
@@ -136,6 +147,10 @@ func copyConfig(c *tls.Config) *utls.Config {
|
||||
KeyLogWriter: c.KeyLogWriter,
|
||||
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||
}
|
||||
if config.EncryptedClientHelloConfigList != nil {
|
||||
config.NextProtos = c.NextProtos
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"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/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
@@ -20,7 +21,24 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if streamSettings != nil && streamSettings.UdpmaskManager != nil {
|
||||
wrapper, ok := conn.(*internet.PacketConnWrapper)
|
||||
if !ok {
|
||||
conn.Close()
|
||||
return nil, errors.New("conn is not PacketConnWrapper")
|
||||
}
|
||||
|
||||
raw := wrapper.Conn
|
||||
|
||||
wrapper.Conn, err = streamSettings.UdpmaskManager.WrapPacketConnClient(raw)
|
||||
if err != nil {
|
||||
raw.Close()
|
||||
return nil, errors.New("mask err").Base(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle dialer options
|
||||
return stat.Connection(conn), nil
|
||||
return conn, nil
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
@@ -23,6 +24,9 @@ func (c *Config) GetRequestHeader() http.Header {
|
||||
for k, v := range c.Header {
|
||||
header.Add(k, v)
|
||||
}
|
||||
if header.Get("User-Agent") == "" {
|
||||
header.Set("User-Agent", utils.ChromeUA)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user