5 Комити 3af6497577 ... 06c49b92f8

Аутор SHA1 Порука Датум
  MHSanaei 06c49b92f8 v2.8.9 пре 1 недеља
  MHSanaei e35213bc73 Update Xray-core to v26.1.31 and related dependencies пре 1 недеља
  MHSanaei aa6a886977 Add UDP hop interval min/max support for Hysteria пре 1 недеља
  MHSanaei 9d603c5ad2 Add pinnedPeerCertSha256 support to TLS settings пре 1 недеља
  MHSanaei a973fa6d68 XHTTP transport: New options for bypassing CDN's detection пре 1 недеља

+ 2 - 2
.github/workflows/release.yml

@@ -89,7 +89,7 @@ jobs:
           cd x-ui/bin
           
           # Download dependencies
-          Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.18/"
+          Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
           if [ "${{ matrix.platform }}" == "amd64" ]; then
             wget -q ${Xray_URL}Xray-linux-64.zip
             unzip Xray-linux-64.zip
@@ -187,7 +187,7 @@ jobs:
           cd x-ui\bin
           
           # Download Xray for Windows
-          $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.1.18/"
+          $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
           Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
           Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
           Remove-Item "Xray-windows-64.zip"

+ 1 - 1
DockerInit.sh

@@ -27,7 +27,7 @@ case $1 in
 esac
 mkdir -p build/bin
 cd build/bin
-curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.1.18/Xray-linux-${ARCH}.zip"
+curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/Xray-linux-${ARCH}.zip"
 unzip "Xray-linux-${ARCH}.zip"
 rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
 mv xray "xray-linux-${FNAME}"

+ 1 - 1
config/version

@@ -1 +1 @@
-2.8.8
+2.8.9

+ 10 - 14
go.mod

@@ -16,11 +16,11 @@ require (
 	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
 	github.com/pelletier/go-toml/v2 v2.2.4
 	github.com/robfig/cron/v3 v3.0.1
-	github.com/shirou/gopsutil/v4 v4.25.12
+	github.com/shirou/gopsutil/v4 v4.26.1
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/valyala/fasthttp v1.69.0
 	github.com/xlzd/gotp v0.1.0
-	github.com/xtls/xray-core v1.260118.0
+	github.com/xtls/xray-core v1.260131.0
 	go.uber.org/atomic v1.11.0
 	golang.org/x/crypto v0.47.0
 	golang.org/x/sys v0.40.0
@@ -35,11 +35,10 @@ require (
 	github.com/andybalholm/brotli v1.2.0 // indirect
 	github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect
 	github.com/bytedance/gopkg v0.1.3 // indirect
-	github.com/bytedance/sonic v1.14.2 // indirect
-	github.com/bytedance/sonic/loader v0.4.0 // indirect
-	github.com/cloudflare/circl v1.6.2 // indirect
+	github.com/bytedance/sonic v1.15.0 // indirect
+	github.com/bytedance/sonic/loader v0.5.0 // indirect
+	github.com/cloudflare/circl v1.6.3 // indirect
 	github.com/cloudwego/base64x v0.1.6 // indirect
-	github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
 	github.com/ebitengine/purego v0.9.1 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.12 // indirect
 	github.com/gin-contrib/sse v1.1.0 // indirect
@@ -64,24 +63,21 @@ require (
 	github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-sqlite3 v1.14.33 // indirect
-	github.com/miekg/dns v1.1.70 // indirect
+	github.com/miekg/dns v1.1.72 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/pires/go-proxyproto v0.8.1 // indirect
+	github.com/pires/go-proxyproto v0.9.2 // indirect
 	github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
 	github.com/quic-go/qpack v0.6.0 // indirect
 	github.com/quic-go/quic-go v0.59.0 // indirect
 	github.com/refraction-networking/utls v1.8.2 // indirect
-	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	github.com/rogpeppe/go-internal v1.14.1 // indirect
-	github.com/sagernet/sing v0.7.14 // indirect
+	github.com/sagernet/sing v0.7.18 // indirect
 	github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
-	github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
 	github.com/tklauser/go-sysconf v0.3.16 // indirect
 	github.com/tklauser/numcpus v0.11.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.3.1 // indirect
-	github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/valyala/fastjson v1.6.7 // indirect
 	github.com/vishvananda/netlink v1.3.1 // indirect
@@ -98,8 +94,8 @@ require (
 	golang.org/x/tools v0.41.0 // indirect
 	golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
 	google.golang.org/protobuf v1.36.11 // indirect
-	gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 // indirect
+	gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
 	lukechampine.com/blake3 v1.4.1 // indirect
 )

+ 20 - 31
go.sum

@@ -10,20 +10,17 @@ github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/
 github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
 github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
 github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
-github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
-github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
-github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
-github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
-github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
-github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
+github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
+github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
+github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
+github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
+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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
 github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
-github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
-github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
 github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
 github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
@@ -124,8 +121,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
 github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
-github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
+github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
+github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -141,8 +138,8 @@ 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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
-github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
-github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
+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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
@@ -153,20 +150,16 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
 github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
 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/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
-github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
-github.com/sagernet/sing v0.7.14 h1:5QQRDCUvYNOMyVp3LuK/hYEBAIv0VsbD3x/l9zH467s=
-github.com/sagernet/sing v0.7.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
+github.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E=
+github.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
 github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
 github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
-github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
-github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
-github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
-github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
+github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
+github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -174,7 +167,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -189,8 +181,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
 github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
-github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
-github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
@@ -205,8 +195,8 @@ github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
 github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
 github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
 github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
-github.com/xtls/xray-core v1.260118.0 h1:RJtgIbQ3ykFRcH1CKeoCgQ5WvhsMFu+lnvLF/fFHagE=
-github.com/xtls/xray-core v1.260118.0/go.mod h1:A5k7TXE2KfAjT8dAq6Ir4mMP1q0OTh+8VMmUdqWMQpg=
+github.com/xtls/xray-core v1.260131.0 h1:gPBykLhUvRZ8sfubNerkwWqV3c15UtmSYQG2cgKqrV4=
+github.com/xtls/xray-core v1.260131.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
 github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
 github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -263,8 +253,8 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z
 golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
 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-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
 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/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
@@ -275,14 +265,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
 gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
 gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
 gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
-gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 h1:fr6L00yGG2RP5NMea6njWpdC+bm+cMdFClrSpaicp1c=
-gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
+gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
+gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
 lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
 lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=

+ 60 - 4
web/assets/js/model/inbound.js

@@ -487,6 +487,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
         noSSEHeader = false,
         xPaddingBytes = "100-1000",
         mode = MODE_OPTION.AUTO,
+        xPaddingObfsMode = false,
+        xPaddingKey = '',
+        xPaddingHeader = '',
+        xPaddingPlacement = '',
+        xPaddingMethod = '',
+        uplinkHTTPMethod = '',
+        sessionPlacement = '',
+        sessionKey = '',
+        seqPlacement = '',
+        seqKey = '',
+        uplinkDataPlacement = '',
+        uplinkDataKey = '',
+        uplinkChunkSize = 0,
     ) {
         super();
         this.path = path;
@@ -498,6 +511,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
         this.noSSEHeader = noSSEHeader;
         this.xPaddingBytes = xPaddingBytes;
         this.mode = mode;
+        this.xPaddingObfsMode = xPaddingObfsMode;
+        this.xPaddingKey = xPaddingKey;
+        this.xPaddingHeader = xPaddingHeader;
+        this.xPaddingPlacement = xPaddingPlacement;
+        this.xPaddingMethod = xPaddingMethod;
+        this.uplinkHTTPMethod = uplinkHTTPMethod;
+        this.sessionPlacement = sessionPlacement;
+        this.sessionKey = sessionKey;
+        this.seqPlacement = seqPlacement;
+        this.seqKey = seqKey;
+        this.uplinkDataPlacement = uplinkDataPlacement;
+        this.uplinkDataKey = uplinkDataKey;
+        this.uplinkChunkSize = uplinkChunkSize;
     }
 
     addHeader(name, value) {
@@ -519,6 +545,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
             json.noSSEHeader,
             json.xPaddingBytes,
             json.mode,
+            json.xPaddingObfsMode,
+            json.xPaddingKey,
+            json.xPaddingHeader,
+            json.xPaddingPlacement,
+            json.xPaddingMethod,
+            json.uplinkHTTPMethod,
+            json.sessionPlacement,
+            json.sessionKey,
+            json.seqPlacement,
+            json.seqKey,
+            json.uplinkDataPlacement,
+            json.uplinkDataKey,
+            json.uplinkChunkSize,
         );
     }
 
@@ -533,6 +572,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
             noSSEHeader: this.noSSEHeader,
             xPaddingBytes: this.xPaddingBytes,
             mode: this.mode,
+            xPaddingObfsMode: this.xPaddingObfsMode,
+            xPaddingKey: this.xPaddingKey,
+            xPaddingHeader: this.xPaddingHeader,
+            xPaddingPlacement: this.xPaddingPlacement,
+            xPaddingMethod: this.xPaddingMethod,
+            uplinkHTTPMethod: this.uplinkHTTPMethod,
+            sessionPlacement: this.sessionPlacement,
+            sessionKey: this.sessionKey,
+            seqPlacement: this.seqPlacement,
+            seqKey: this.seqKey,
+            uplinkDataPlacement: this.uplinkDataPlacement,
+            uplinkDataKey: this.uplinkDataKey,
+            uplinkChunkSize: this.uplinkChunkSize,
         };
     }
 }
@@ -544,7 +596,8 @@ class TlsStreamSettings extends XrayCommonClass {
         maxVersion = TLS_VERSION_OPTION.TLS13,
         cipherSuites = '',
         rejectUnknownSni = false,
-        verifyPeerCertByName = ['dns.google', 'cloudflare-dns.com'],
+        verifyPeerCertByNames = ['dns.google', 'cloudflare-dns.com'],
+        pinnedPeerCertSha256 = [],
         disableSystemRoot = false,
         enableSessionResumption = false,
         certificates = [new TlsStreamSettings.Cert()],
@@ -559,7 +612,8 @@ class TlsStreamSettings extends XrayCommonClass {
         this.maxVersion = maxVersion;
         this.cipherSuites = cipherSuites;
         this.rejectUnknownSni = rejectUnknownSni;
-        this.verifyPeerCertByName = Array.isArray(verifyPeerCertByName) ? verifyPeerCertByName.join(",") : verifyPeerCertByName;
+        this.verifyPeerCertByNames = Array.isArray(verifyPeerCertByNames) ? verifyPeerCertByNames.join(",") : verifyPeerCertByNames;
+        this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
         this.disableSystemRoot = disableSystemRoot;
         this.enableSessionResumption = enableSessionResumption;
         this.certs = certificates;
@@ -593,7 +647,8 @@ class TlsStreamSettings extends XrayCommonClass {
             json.maxVersion,
             json.cipherSuites,
             json.rejectUnknownSni,
-            json.verifyPeerCertByName,
+            json.verifyPeerCertByNames,
+            json.pinnedPeerCertSha256 || [],
             json.disableSystemRoot,
             json.enableSessionResumption,
             certs,
@@ -611,7 +666,8 @@ class TlsStreamSettings extends XrayCommonClass {
             maxVersion: this.maxVersion,
             cipherSuites: this.cipherSuites,
             rejectUnknownSni: this.rejectUnknownSni,
-            verifyPeerCertByName: this.verifyPeerCertByName.split(","),
+            verifyPeerCertByNames: this.verifyPeerCertByNames.split(","),
+            pinnedPeerCertSha256: this.pinnedPeerCertSha256.length > 0 ? this.pinnedPeerCertSha256 : undefined,
             disableSystemRoot: this.disableSystemRoot,
             enableSessionResumption: this.enableSessionResumption,
             certificates: TlsStreamSettings.toJsonArray(this.certs),

+ 27 - 7
web/assets/js/model/outbound.js

@@ -424,7 +424,8 @@ class HysteriaStreamSettings extends CommonClass {
         up = '0',
         down = '0',
         udphopPort = '',
-        udphopInterval = 30,
+        udphopIntervalMin = 30,
+        udphopIntervalMax = 30,
         initStreamReceiveWindow = 8388608,
         maxStreamReceiveWindow = 8388608,
         initConnectionReceiveWindow = 20971520,
@@ -440,7 +441,8 @@ class HysteriaStreamSettings extends CommonClass {
         this.up = up;
         this.down = down;
         this.udphopPort = udphopPort;
-        this.udphopInterval = udphopInterval;
+        this.udphopIntervalMin = udphopIntervalMin;
+        this.udphopIntervalMax = udphopIntervalMax;
         this.initStreamReceiveWindow = initStreamReceiveWindow;
         this.maxStreamReceiveWindow = maxStreamReceiveWindow;
         this.initConnectionReceiveWindow = initConnectionReceiveWindow;
@@ -452,10 +454,18 @@ class HysteriaStreamSettings extends CommonClass {
 
     static fromJson(json = {}) {
         let udphopPort = '';
-        let udphopInterval = 30;
+        let udphopIntervalMin = 30;
+        let udphopIntervalMax = 30;
         if (json.udphop) {
             udphopPort = json.udphop.port || '';
-            udphopInterval = json.udphop.interval || 30;
+            // Backward compatibility: if old 'interval' exists, use it for both min/max
+            if (json.udphop.interval !== undefined) {
+                udphopIntervalMin = json.udphop.interval;
+                udphopIntervalMax = json.udphop.interval;
+            } else {
+                udphopIntervalMin = json.udphop.intervalMin || 30;
+                udphopIntervalMax = json.udphop.intervalMax || 30;
+            }
         }
         return new HysteriaStreamSettings(
             json.version,
@@ -464,7 +474,8 @@ class HysteriaStreamSettings extends CommonClass {
             json.up,
             json.down,
             udphopPort,
-            udphopInterval,
+            udphopIntervalMin,
+            udphopIntervalMax,
             json.initStreamReceiveWindow,
             json.maxStreamReceiveWindow,
             json.initConnectionReceiveWindow,
@@ -493,7 +504,8 @@ class HysteriaStreamSettings extends CommonClass {
         if (this.udphopPort) {
             result.udphop = {
                 port: this.udphopPort,
-                interval: this.udphopInterval
+                intervalMin: this.udphopIntervalMin,
+                intervalMax: this.udphopIntervalMax
             };
         }
         return result;
@@ -1024,7 +1036,15 @@ class Outbound extends CommonClass {
         stream.hysteria.up = urlParams.get('up') ?? '0';
         stream.hysteria.down = urlParams.get('down') ?? '0';
         stream.hysteria.udphopPort = urlParams.get('udphopPort') ?? '';
-        stream.hysteria.udphopInterval = parseInt(urlParams.get('udphopInterval') ?? '30');
+        // Support both old single interval and new min/max range
+        if (urlParams.has('udphopInterval')) {
+            const interval = parseInt(urlParams.get('udphopInterval'));
+            stream.hysteria.udphopIntervalMin = interval;
+            stream.hysteria.udphopIntervalMax = interval;
+        } else {
+            stream.hysteria.udphopIntervalMin = parseInt(urlParams.get('udphopIntervalMin') ?? '30');
+            stream.hysteria.udphopIntervalMax = parseInt(urlParams.get('udphopIntervalMax') ?? '30');
+        }
 
         // Optional QUIC parameters
         if (urlParams.has('initStreamReceiveWindow')) {

+ 8 - 2
web/html/form/outbound.html

@@ -549,10 +549,16 @@
             <a-input v-model.trim="outbound.stream.hysteria.udphopPort"
               placeholder="e.g., 1145-1919 or 11,13,15-17"></a-input>
           </a-form-item>
-          <a-form-item label='UDP Hop Interval (s)'
+          <a-form-item label='UDP Hop Interval Min (s)'
             v-if="outbound.stream.hysteria.udphopPort">
             <a-input-number
-              v-model.number="outbound.stream.hysteria.udphopInterval"
+              v-model.number="outbound.stream.hysteria.udphopIntervalMin"
+              :min="5"></a-input-number>
+          </a-form-item>
+          <a-form-item label='UDP Hop Interval Max (s)'
+            v-if="outbound.stream.hysteria.udphopPort">
+            <a-input-number
+              v-model.number="outbound.stream.hysteria.udphopIntervalMax"
               :min="5"></a-input-number>
           </a-form-item>
           <a-form-item label='Init Stream Receive'>

+ 113 - 12
web/html/form/stream/stream_xhttp.html

@@ -1,5 +1,6 @@
 {{define "form/streamXHTTP"}}
-<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+<a-form :colon="false" :label-col="{ md: {span:8} }"
+    :wrapper-col="{ md: {span:14} }">
     <a-form-item label='{{ i18n "host" }}'>
         <a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
     </a-form-item>
@@ -7,38 +8,138 @@
         <a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
     </a-form-item>
     <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
-        <a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
+        <a-button icon="plus" size="small"
+            @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
     </a-form-item>
     <a-form-item :wrapper-col="{span:24}">
-        <a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
+        <a-input-group compact
+            v-for="(header, index) in inbound.stream.xhttp.headers">
             <a-input :style="{ width: '50%' }" v-model.trim="header.name"
                 placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
-                <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
+                <template slot="addonBefore" :style="{ margin: '0' }">[[ index+1
+                    ]]</template>
             </a-input>
             <a-input :style="{ width: '50%' }" v-model.trim="header.value"
                 placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
-                <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)"></a-button>
+                <a-button icon="minus" slot="addonAfter" size="small"
+                    @click="inbound.stream.xhttp.removeHeader(index)"></a-button>
             </a-input>
         </a-input-group>
     </a-form-item>
     <a-form-item label='Mode'>
         <a-select v-model="inbound.stream.xhttp.mode" :style="{ width: '50%' }"
             :dropdown-class-name="themeSwitcher.currentTheme">
-            <a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
+            <a-select-option v-for="key in MODE_OPTION" :value="key">[[ key
+                ]]</a-select-option>
         </a-select>
     </a-form-item>
-    <a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
-        <a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
+    <a-form-item label="Max Buffered Upload"
+        v-if="inbound.stream.xhttp.mode === 'packet-up'">
+        <a-input-number
+            v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
     </a-form-item>
-    <a-form-item label="Max Upload Size (Byte)" v-if="inbound.stream.xhttp.mode === 'packet-up'">
-        <a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
+    <a-form-item label="Max Upload Size (Byte)"
+        v-if="inbound.stream.xhttp.mode === 'packet-up'">
+        <a-input
+            v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
     </a-form-item>
-    <a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
-        <a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
+    <a-form-item label="Stream-Up Server"
+        v-if="inbound.stream.xhttp.mode === 'stream-up'">
+        <a-input
+            v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
     </a-form-item>
     <a-form-item label="Padding Bytes">
         <a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
     </a-form-item>
+    <a-form-item label="Padding Obfs Mode">
+        <a-switch v-model="inbound.stream.xhttp.xPaddingObfsMode"></a-switch>
+    </a-form-item>
+    <template v-if="inbound.stream.xhttp.xPaddingObfsMode">
+        <a-form-item label="Padding Key">
+            <a-input v-model.trim="inbound.stream.xhttp.xPaddingKey"
+                placeholder="x_padding"></a-input>
+        </a-form-item>
+        <a-form-item label="Padding Header">
+            <a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader"
+                placeholder="X-Padding"></a-input>
+        </a-form-item>
+        <a-form-item label="Padding Placement">
+            <a-select v-model="inbound.stream.xhttp.xPaddingPlacement"
+                :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value>Default (queryInHeader)</a-select-option>
+                <a-select-option
+                    value="queryInHeader">queryInHeader</a-select-option>
+                <a-select-option value="header">header</a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item label="Padding Method">
+            <a-select v-model="inbound.stream.xhttp.xPaddingMethod"
+                :dropdown-class-name="themeSwitcher.currentTheme">
+                <a-select-option value>Default (repeat-x)</a-select-option>
+                <a-select-option value="repeat-x">repeat-x</a-select-option>
+                <a-select-option value="tokenish">tokenish</a-select-option>
+            </a-select>
+        </a-form-item>
+    </template>
+    <a-form-item label="Uplink HTTP Method">
+        <a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod"
+            :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value>Default (POST)</a-select-option>
+            <a-select-option value="POST">POST</a-select-option>
+            <a-select-option value="PUT">PUT</a-select-option>
+            <a-select-option value="GET">GET (packet-up only)</a-select-option>
+        </a-select>
+    </a-form-item>
+    <a-form-item label="Session Placement">
+        <a-select v-model="inbound.stream.xhttp.sessionPlacement"
+            :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value>Default (path)</a-select-option>
+            <a-select-option value="path">path</a-select-option>
+            <a-select-option value="header">header</a-select-option>
+            <a-select-option value="cookie">cookie</a-select-option>
+            <a-select-option value="query">query</a-select-option>
+        </a-select>
+    </a-form-item>
+    <a-form-item label="Session Key"
+        v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'">
+        <a-input v-model.trim="inbound.stream.xhttp.sessionKey"
+            placeholder="x_session"></a-input>
+    </a-form-item>
+    <a-form-item label="Sequence Placement">
+        <a-select v-model="inbound.stream.xhttp.seqPlacement"
+            :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value>Default (path)</a-select-option>
+            <a-select-option value="path">path</a-select-option>
+            <a-select-option value="header">header</a-select-option>
+            <a-select-option value="cookie">cookie</a-select-option>
+            <a-select-option value="query">query</a-select-option>
+        </a-select>
+    </a-form-item>
+    <a-form-item label="Sequence Key"
+        v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'">
+        <a-input v-model.trim="inbound.stream.xhttp.seqKey"
+            placeholder="x_seq"></a-input>
+    </a-form-item>
+    <a-form-item label="Uplink Data Placement"
+        v-if="inbound.stream.xhttp.mode === 'packet-up'">
+        <a-select v-model="inbound.stream.xhttp.uplinkDataPlacement"
+            :dropdown-class-name="themeSwitcher.currentTheme">
+            <a-select-option value>Default (body)</a-select-option>
+            <a-select-option value="body">body</a-select-option>
+            <a-select-option value="header">header</a-select-option>
+            <a-select-option value="query">query</a-select-option>
+        </a-select>
+    </a-form-item>
+    <a-form-item label="Uplink Data Key"
+        v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
+        <a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey"
+            placeholder="x_data"></a-input>
+    </a-form-item>
+    <a-form-item label="Uplink Chunk Size"
+        v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
+        <a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize"
+            :min="0" placeholder="0 (unlimited)"></a-input-number>
+    </a-form-item>
     <a-form-item label="No SSE Header">
         <a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
     </a-form-item>

+ 62 - 31
web/html/form/tls_settings.html

@@ -1,11 +1,13 @@
 {{define "form/tlsSettings"}}
 <!-- tls enable -->
-<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+<a-form v-if="inbound.canEnableTls()" :colon="false"
+  :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
   <a-divider :style="{ margin: '3px 0' }"></a-divider>
   <a-form-item label='{{ i18n "security" }}'>
     <a-radio-group v-model="inbound.stream.security" button-style="solid">
       <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
-      <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
+      <a-radio-button v-if="inbound.canEnableReality()"
+        value="reality">Reality</a-radio-button>
       <a-radio-button value="tls">TLS</a-radio-button>
     </a-radio-group>
   </a-form-item>
@@ -16,33 +18,44 @@
       <a-input v-model.trim="inbound.stream.tls.sni"></a-input>
     </a-form-item>
     <a-form-item label="Cipher Suites">
-      <a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
-        <a-select-option value="">Auto</a-select-option>
-        <a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
+      <a-select v-model="inbound.stream.tls.cipherSuites"
+        :dropdown-class-name="themeSwitcher.currentTheme">
+        <a-select-option value>Auto</a-select-option>
+        <a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[
+          value ]]</a-select-option>
       </a-select>
     </a-form-item>
     <a-form-item label="Min/Max Version">
       <a-input-group compact>
-        <a-select v-model="inbound.stream.tls.minVersion" :style="{ width: '50%' }"
+        <a-select v-model="inbound.stream.tls.minVersion"
+          :style="{ width: '50%' }"
           :dropdown-class-name="themeSwitcher.currentTheme">
-          <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
+          <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key
+            ]]</a-select-option>
         </a-select>
-        <a-select v-model="inbound.stream.tls.maxVersion" :style="{ width: '50%' }"
+        <a-select v-model="inbound.stream.tls.maxVersion"
+          :style="{ width: '50%' }"
           :dropdown-class-name="themeSwitcher.currentTheme">
-          <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
+          <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key
+            ]]</a-select-option>
         </a-select>
       </a-input-group>
     </a-form-item>
     <a-form-item label="uTLS">
-      <a-select v-model="inbound.stream.tls.settings.fingerprint" :style="{ width: '100%' }"
+      <a-select v-model="inbound.stream.tls.settings.fingerprint"
+        :style="{ width: '100%' }"
         :dropdown-class-name="themeSwitcher.currentTheme">
-        <a-select-option value=''>None</a-select-option>
-        <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
+        <a-select-option value>None</a-select-option>
+        <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key
+          ]]</a-select-option>
       </a-select>
     </a-form-item>
     <a-form-item label="ALPN">
-      <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme" v-model="inbound.stream.tls.alpn">
-        <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
+      <a-select mode="multiple"
+        :dropdown-class-name="themeSwitcher.currentTheme"
+        v-model="inbound.stream.tls.alpn">
+        <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn
+          ]]</a-select-option>
       </a-select>
     </a-form-item>
     <a-form-item label="Allow Insecure">
@@ -57,21 +70,34 @@
     <a-form-item label="Session Resumption">
       <a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
     </a-form-item>
-    <a-form-item label="verifyPeerCertByName">
-      <a-input v-model.trim="inbound.stream.tls.verifyPeerCertByName"></a-input>
+    <a-form-item label="verifyPeerCertByNames">
+      <a-input v-model.trim="inbound.stream.tls.verifyPeerCertByNames"></a-input>
+    </a-form-item>
+    <a-form-item label="pinned Peer Cert Sha256">
+      <a-select mode="tags" v-model="inbound.stream.tls.pinnedPeerCertSha256"
+        :dropdown-class-name="themeSwitcher.currentTheme"
+        placeholder="Enter SHA256 fingerprints (base64)">
+      </a-select>
     </a-form-item>
     <a-divider :style="{ margin: '3px 0' }"></a-divider>
     <template v-for="cert,index in inbound.stream.tls.certs">
       <a-form-item label='{{ i18n "certificate" }}'>
-        <a-radio-group v-model="cert.useFile" button-style="solid" :style="{ display: 'inline-flex', whiteSpace: 'nowrap', maxWidth: '100%' }">
-          <a-radio-button :value="true" :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
-          <a-radio-button :value="false" :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
+        <a-radio-group v-model="cert.useFile" button-style="solid"
+          :style="{ display: 'inline-flex', whiteSpace: 'nowrap', maxWidth: '100%' }">
+          <a-radio-button :value="true"
+            :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{
+            i18n "pages.inbounds.certificatePath" }}</a-radio-button>
+          <a-radio-button :value="false"
+            :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{
+            i18n "pages.inbounds.certificateContent" }}</a-radio-button>
         </a-radio-group>
       </a-form-item>
       <a-form-item label=" ">
         <a-space>
-          <a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"></a-button>
-          <a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
+          <a-button icon="plus" v-if="index === 0" type="primary" size="small"
+            @click="inbound.stream.tls.addCert()"></a-button>
+          <a-button icon="minus" v-if="inbound.stream.tls.certs.length>1"
+            type="primary" size="small"
             @click="inbound.stream.tls.removeCert(index)"></a-button>
         </a-space>
       </a-form-item>
@@ -83,7 +109,8 @@
           <a-input v-model.trim="cert.keyFile"></a-input>
         </a-form-item>
         <a-form-item label=" ">
-          <a-button type="primary" icon="import" @click="setDefaultCertData(index)">
+          <a-button type="primary" icon="import"
+            @click="setDefaultCertData(index)">
             {{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
         </a-form-item>
       </template>
@@ -99,8 +126,10 @@
         <a-switch v-model="cert.oneTimeLoading"></a-switch>
       </a-form-item>
       <a-form-item label='Usage Option'>
-        <a-select v-model="cert.usage" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
-          <a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
+        <a-select v-model="cert.usage" :style="{ width: '50%' }"
+          :dropdown-class-name="themeSwitcher.currentTheme">
+          <a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key
+            ]]</a-select-option>
         </a-select>
       </a-form-item>
       <a-form-item label="Build Chain" v-if="cert.usage === 'issue'">
@@ -108,20 +137,22 @@
       </a-form-item>
     </template>
     <a-form-item label='ECH key'>
-        <a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
+      <a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
     </a-form-item>
     <a-form-item label='ECH config'>
-        <a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
+      <a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
     </a-form-item>
     <a-form-item label='ECH force query'>
-        <a-select v-model="inbound.stream.tls.echForceQuery"
-            :dropdown-class-name="themeSwitcher.currentTheme">
-            <a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
-        </a-select>
+      <a-select v-model="inbound.stream.tls.echForceQuery"
+        :dropdown-class-name="themeSwitcher.currentTheme">
+        <a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[
+          key ]]</a-select-option>
+      </a-select>
     </a-form-item>
     <a-form-item label=" ">
       <a-space>
-        <a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
+        <a-button type="primary" icon="import" @click="getNewEchCert">Get New
+          ECH Cert</a-button>
         <a-button danger @click="clearEchCert">Clear</a-button>
       </a-space>
     </a-form-item>

+ 1 - 1
web/service/server.go

@@ -567,7 +567,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
 			continue
 		}
 
-		if major > 26 || (major == 26 && minor > 1) || (major == 26 && minor == 1 && patch >= 18) {
+		if major > 26 || (major == 26 && minor > 1) || (major == 26 && minor == 1 && patch >= 31) {
 			versions = append(versions, release.TagName)
 		}
 	}