Compare commits

..

20 Commits

Author SHA1 Message Date
RPRX
38cc306c95 Use nfsAEAD & pfsAEAD for paddings; Refine comments
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3210125349

373558ed7a (r164355865)
2025-08-21 11:27:09 +00:00
RPRX
373558ed7a Use X25519 for XOR; Add "divide" (ECH, before and includes type 0); Change config format
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3207449672
2025-08-20 18:17:35 +00:00
RPRX
84835bec7d Support VLESS Encryption (native/random) + XTLS Vision + Any Transport like XHTTP (UDS or not) + TLS/REALITY
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3200720109
2025-08-19 13:20:27 +00:00
RPRX
49580705f6 Use SHA3-256 instead of SHA2-256; Support XTLS Vision for random appearance
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3197163816
2025-08-18 14:58:21 +00:00
RPRX
d1fb485212 Add hash11(nfsEKeyBytes) to client/ticket hello; Support XTLS Vision for native appearance
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3194609798
2025-08-17 22:39:53 +00:00
RPRX
bfe4820f2f Fix 1/67000000 chance's server panic; Refine comments
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3188118918
2025-08-14 15:28:40 +00:00
RPRX
2807ee432a Allow paddings before handshake; CTR 128->256; Fix panic
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3187832651
2025-08-14 10:28:17 +00:00
RPRX
7f778a4e2f SHA256(nfsEKeyBytes) for XOR's key
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3185590465
2025-08-13 21:37:06 +00:00
RPRX
09cc92c61d chore
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3183681018
2025-08-13 12:44:58 +00:00
RPRX
0fd7691d6b Fix reading ticket hello
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3183283514
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3183324745
2025-08-13 11:24:10 +00:00
RPRX
1720be168f aes128xor (all) -> xored (optimized)
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3182301703
2025-08-13 10:41:26 +00:00
RPRX
3c20bddfcf Add 5-bytes header for client/server/ticket hello and server random
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3182301703
2025-08-13 09:17:37 +00:00
RPRX
23d7aad461 Add Close() for ServerInstance; Fix server's nonce overflow
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3180075690
5c61142048 (r163855798)
2025-08-12 17:07:57 +00:00
RPRX
5c61142048 Generate new key when nonce reaches max value
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3179685937
2025-08-12 14:50:44 +00:00
RPRX
ec1cc35188 Mainly reverse VLESS Client Hello
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3176751311
2025-08-12 11:51:28 +00:00
RPRX
7ffb555fc8 Add optional aes128xor layer
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3172703168
2025-08-11 12:07:00 +00:00
RPRX
2e6a88307c Revert "Fix test" 2025-08-11 00:28:09 +00:00
RPRX
3e19bf9233 Rename reuse/index -> ticket 2025-08-11 00:24:08 +00:00
风扇滑翔翼
fc137d2612 Fix test 2025-08-10 12:12:58 +00:00
RPRX
f61c14e9c6 VLESS protocol: Add lightweight Post-Quantum ML-KEM-768-based PFS 1-RTT / anti-replay 0-RTT AEAD encryption
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3163335040
2025-08-10 11:50:18 +00:00
84 changed files with 1788 additions and 689 deletions

View File

@@ -7,8 +7,6 @@ body:
description: |-
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
options:
- label: I have read all the comments in the issue template and ensured that this issue meet the requirements.
required: true
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
required: true
- label: I provided the complete config and logs, rather than just providing the truncated parts based on my own judgment.
@@ -40,8 +38,6 @@ body:
### For config
Please provide the configuration files that can reproduce the problem, including the server and client.
Don't just paste a big exported config file here. Eliminate useless inbound/outbound, rules, options, this can help determine the problem, if you really want to get help.
After removing parts that do not affect reproduction, provide the actual running **complete** file.
meaning of complete: This config can be directly used to start the core, **not a truncated part of the config**. For fields like keys, use newly generated valid parameters that have not been actually used to fill in.
### For logs
Please set the log level to debug and dnsLog to true first.
@@ -50,29 +46,42 @@ body:
Provide the log of Xray-core, not the log output by the panel or other things.
### Finally
The specific content to be filled in each of the following text boxes needs to be placed between ```<details><pre><code>``` and ```</code></pre></details>```, like this
```
<details><pre><code>
(config)
</code></pre></details>
```
After removing parts that do not affect reproduction, provide the actual running **complete** file, do not only provide inbound or outbound or a few lines of logs based on your own judgment.
Put the content between the preset ```<details><pre><code>``` ```</code></pre></details>``` in the text box.
If the problem is very clear that only related to one end (such as core startup failure/crash after correctly writing the config according to the documents), N/A can be filled in for unnecessary areas below.
- type: textarea
attributes:
label: Client config
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true
- type: textarea
attributes:
label: Server config
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true
- type: textarea
attributes:
label: Client log
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true
- type: textarea
attributes:
label: Server log
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true

View File

@@ -7,8 +7,6 @@ body:
description: |-
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
options:
- label: 我读完了 issue 模板中的所有注释,确保填写符合要求。
required: true
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
required: true
- label: 我提供了完整的配置文件和日志,而不是出于自己的判断只给出截取的部分。
@@ -40,8 +38,6 @@ body:
### 对于配置文件
请提供可以重现问题的配置文件,包括服务端和客户端。
不要直接在这里黏贴一大段导出的 config 文件。去掉无用的出入站、规则、选项,这可以帮助确定问题,如果你真的想得到帮助。
在去掉不影响复现的部分后,提供实际运行的**完整**文件。
完整的含义:可以直接使用这个配置启动核心,**不是截取的部分配置**。对于密钥等参数使用重新生成未实际使用的有效参数填充。
### 对于日志
请先将日志等级设置为 debug, dnsLog 设置为true.
@@ -50,29 +46,42 @@ body:
提供 Xray-core 的日志,而不是面板或者别的东西输出的日志。
### 最后
把下面的每格具体内容需要放在 ```<details><pre><code>``` 和 ```</code></pre></details>``` 中间,如
```
<details><pre><code>
(config)
</code></pre></details>
```
在去掉不影响复现的部分后,提供实际运行的**完整**文件,不要出于自己的判断只提供入站出站或者几行日志。
把内容放在文本框预置的 ```<details><pre><code>``` 和 ```</code></pre></details>``` 中间。
如果问题十分明确只出现在某一端(如按文档正确编写配置后核心启动失败/崩溃)可以在下面不需要的项目填入N/A.
- type: textarea
attributes:
label: 客户端配置
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true
- type: textarea
attributes:
label: 服务端配置
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true
- type: textarea
attributes:
label: 客户端日志
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true
- type: textarea
attributes:
label: 服务端日志
value: |-
<details><pre><code>
</code></pre></details>
validations:
required: true

View File

@@ -65,7 +65,7 @@ jobs:
echo "LATEST=$LATEST" >>${GITHUB_ENV}
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

View File

@@ -63,7 +63,7 @@ jobs:
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Show workflow information
run: |
@@ -94,11 +94,11 @@ jobs:
mkdir -p build_assets
COMMID=$(git describe --always --dirty)
echo 'Building Xray for Windows 7...'
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
go build -o build_assets/xray.exe -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
- name: Restore Geodat Cache
uses: actions/cache/restore@v4

View File

@@ -153,7 +153,7 @@ jobs:
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Set up NDK
if: matrix.goos == 'android'
@@ -190,19 +190,17 @@ jobs:
COMMID=$(git describe --always --dirty)
if [[ ${GOOS} == 'windows' ]]; then
echo 'Building Xray for Windows...'
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
go build -o build_assets/xray.exe -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
else
echo 'Building Xray...'
go build -o build_assets/xray -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'Building soft-float Xray for MIPS/MIPSLE 32-bit...'
GOMIPS=softfloat go build -o build_assets/xray_softfloat -trimpath -buildvcs=false -gcflags="-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
else
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
GOMIPS=softfloat go build -o build_assets/xray_softfloat -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
fi
fi

View File

@@ -45,7 +45,7 @@ jobs:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:

View File

@@ -165,13 +165,7 @@ CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-s -w -buildi
Make sure that you are using the same Go version, and remember to set the git commit id (7 bytes):
```bash
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
```
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
```bash
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
```
## Stargazers over time

View File

@@ -42,15 +42,12 @@ func (r *IPRecord) getIPs() ([]net.IP, uint32, error) {
if r == nil {
return nil, 0, errRecordNotFound
}
untilExpire := time.Until(r.Expire).Seconds()
untilExpire := time.Until(r.Expire)
if untilExpire <= 0 {
return nil, 0, errRecordNotFound
}
ttl := uint32(untilExpire) + 1
if ttl == 1 {
r.Expire = time.Now().Add(time.Second) // To ensure that two consecutive requests get the same result
}
ttl := uint32(untilExpire/time.Second) + uint32(1)
if r.RCode != dnsmessage.RCodeSuccess {
return nil, ttl, dns_feature.RCodeError(r.RCode)
}

View File

@@ -18,31 +18,31 @@ func Test_parseResponse(t *testing.T) {
ans := new(dns.Msg)
ans.Id = 0
p = append(p, common.Must2(ans.Pack()))
p = append(p, common.Must2(ans.Pack()).([]byte))
p = append(p, []byte{})
ans = new(dns.Msg)
ans.Id = 1
ans.Answer = append(ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
common.Must2(dns.NewRR("google.com. IN A 8.8.4.4")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN A 8.8.4.4")).(dns.RR),
)
p = append(p, common.Must2(ans.Pack()))
p = append(p, common.Must2(ans.Pack()).([]byte))
ans = new(dns.Msg)
ans.Id = 2
ans.Answer = append(ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")),
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")),
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")).(dns.RR),
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")).(dns.RR),
)
p = append(p, common.Must2(ans.Pack()))
p = append(p, common.Must2(ans.Pack()).([]byte))
tests := []struct {
name string

View File

@@ -449,12 +449,11 @@ type SenderConfig struct {
unknownFields protoimpl.UnknownFields
// Send traffic through the given IP. Only IP is allowed.
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
}
func (x *SenderConfig) Reset() {
@@ -522,13 +521,6 @@ func (x *SenderConfig) GetViaCidr() string {
return ""
}
func (x *SenderConfig) GetTargetStrategy() internet.DomainStrategy {
if x != nil {
return x.TargetStrategy
}
return internet.DomainStrategy(0)
}
type MultiplexingConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -787,7 +779,7 @@ var file_app_proxyman_config_proto_rawDesc = []byte{
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65,
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53,
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f,
0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x9d, 0x03, 0x0a, 0x0c, 0x53, 0x65,
0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xcb, 0x02, 0x0a, 0x0c, 0x53, 0x65,
0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x03, 0x76, 0x69,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f,
@@ -808,28 +800,23 @@ var file_app_proxyman_config_proto_rawDesc = []byte{
0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69,
0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08,
0x76, 0x69, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x76, 0x69, 0x61, 0x43, 0x69, 0x64, 0x72, 0x12, 0x50, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65,
0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65,
0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0xa4, 0x01, 0x0a, 0x12, 0x4d, 0x75,
0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f,
0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f,
0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75,
0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72,
0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33,
0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x76, 0x69, 0x61, 0x43, 0x69, 0x64, 0x72, 0x22, 0xa4, 0x01, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74,
0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63,
0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63,
0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x78, 0x75,
0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72, 0x6f, 0x78,
0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x78,
0x75, 0x64, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33, 0x42, 0x55,
0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70,
0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d,
0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61,
0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f,
0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -863,7 +850,6 @@ var file_app_proxyman_config_proto_goTypes = []any{
(*internet.StreamConfig)(nil), // 13: xray.transport.internet.StreamConfig
(*serial.TypedMessage)(nil), // 14: xray.common.serial.TypedMessage
(*internet.ProxyConfig)(nil), // 15: xray.transport.internet.ProxyConfig
(internet.DomainStrategy)(0), // 16: xray.transport.internet.DomainStrategy
}
var file_app_proxyman_config_proto_depIdxs = []int32{
0, // 0: xray.app.proxyman.AllocationStrategy.type:type_name -> xray.app.proxyman.AllocationStrategy.Type
@@ -880,12 +866,11 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
13, // 11: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
15, // 12: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
8, // 13: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
16, // 14: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
15, // [15:15] is the sub-list for method output_type
15, // [15:15] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
14, // [14:14] is the sub-list for method output_type
14, // [14:14] is the sub-list for method input_type
14, // [14:14] is the sub-list for extension type_name
14, // [14:14] is the sub-list for extension extendee
0, // [0:14] is the sub-list for field type_name
}
func init() { file_app_proxyman_config_proto_init() }

View File

@@ -84,7 +84,6 @@ message SenderConfig {
xray.transport.internet.ProxyConfig proxy_settings = 3;
MultiplexingConfig multiplex_settings = 4;
string via_cidr = 5;
xray.transport.internet.DomainStrategy target_strategy = 6;
}
message MultiplexingConfig {

View File

@@ -325,7 +325,7 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
ctx = session.ContextWithInbound(ctx, &session.Inbound{
Source: source,
Local: net.DestinationFromAddr(w.hub.Addr()), // Due to some limitations, in UDP connections, localIP is always equal to listen interface IP
Local: net.DestinationFromAddr(w.hub.Addr()),
Gateway: net.UDPDestination(w.address, w.port),
Tag: w.tag,
})

View File

@@ -4,7 +4,6 @@ import (
"context"
"crypto/rand"
goerrors "errors"
"github.com/xtls/xray-core/common/dice"
"io"
"math/big"
gonet "net"
@@ -178,25 +177,6 @@ func (h *Handler) Tag() string {
func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
content := session.ContentFromContext(ctx)
if h.senderSettings != nil && h.senderSettings.TargetStrategy.HasStrategy() && ob.Target.Address.Family().IsDomain() && (content == nil || !content.SkipDNSResolve) {
ips, err := internet.LookupForIP(ob.Target.Address.Domain(), h.senderSettings.TargetStrategy, nil)
if err != nil {
errors.LogInfoInner(ctx, err, "failed to resolve ip for target ", ob.Target.Address.Domain())
if h.senderSettings.TargetStrategy.ForceIP() {
err := errors.New("failed to resolve ip for target ", ob.Target.Address.Domain()).Base(err)
session.SubmitOutboundErrorToOriginator(ctx, err)
common.Interrupt(link.Writer)
common.Interrupt(link.Reader)
return
}
} else {
unchangedDomain := ob.Target.Address.Domain()
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
errors.LogInfo(ctx, "target: ", unchangedDomain, " resolved to: ", ob.Target.Address.String())
}
}
if ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address {
link.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
@@ -208,7 +188,6 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
session.SubmitOutboundErrorToOriginator(ctx, err)
errors.LogInfo(ctx, err.Error())
common.Interrupt(link.Writer)
common.Interrupt(link.Reader)
}
}
if ob.Target.Network == net.Network_UDP && ob.Target.Port == 443 {
@@ -308,18 +287,26 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
case domain == "origin":
if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
ob.Gateway = inbound.Local.Address
errors.LogDebug(ctx, "use inbound local ip as sendthrough: ", inbound.Local.Address.String())
if inbound.Conn != nil {
origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String())
if err == nil {
ob.Gateway = net.ParseAddress(origin)
errors.LogDebug(ctx, "use receive package ip as snedthrough: ", origin)
}
}
}
case domain == "srcip":
if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Source.IsValid() && inbound.Source.Address.Family().IsIP() {
ob.Gateway = inbound.Source.Address
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
if inbound.Conn != nil {
clientaddr, _, err := net.SplitHostPort(inbound.Conn.RemoteAddr().String())
if err == nil {
ob.Gateway = net.ParseAddress(clientaddr)
errors.LogDebug(ctx, "use client src ip as snedthrough: ", clientaddr)
}
}
}
//case addr.Family().IsDomain():
default:

View File

@@ -44,7 +44,6 @@ type RoutingContext struct {
OutboundTag string `protobuf:"bytes,12,opt,name=OutboundTag,proto3" json:"OutboundTag,omitempty"`
LocalIPs [][]byte `protobuf:"bytes,13,rep,name=LocalIPs,proto3" json:"LocalIPs,omitempty"`
LocalPort uint32 `protobuf:"varint,14,opt,name=LocalPort,proto3" json:"LocalPort,omitempty"`
VlessRoute uint32 `protobuf:"varint,15,opt,name=VlessRoute,proto3" json:"VlessRoute,omitempty"`
}
func (x *RoutingContext) Reset() {
@@ -175,13 +174,6 @@ func (x *RoutingContext) GetLocalPort() uint32 {
return 0
}
func (x *RoutingContext) GetVlessRoute() uint32 {
if x != nil {
return x.VlessRoute
}
return 0
}
// SubscribeRoutingStatsRequest subscribes to routing statistics channel if
// opened by xray-core.
// * FieldSelectors selects a subset of fields in routing statistics to return.
@@ -851,7 +843,7 @@ var file_app_router_command_command_proto_rawDesc = []byte{
0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65,
0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf6, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x75,
0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd6, 0x04, 0x0a, 0x0e, 0x52, 0x6f, 0x75,
0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x49,
0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x32, 0x0a, 0x07, 0x4e,
@@ -885,9 +877,7 @@ var file_app_router_command_command_proto_rawDesc = []byte{
0x6c, 0x49, 0x50, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x4c, 0x6f, 0x63, 0x61,
0x6c, 0x49, 0x50, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f, 0x72,
0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f,
0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x18, 0x0f, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x72, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,

View File

@@ -27,7 +27,6 @@ message RoutingContext {
string OutboundTag = 12;
repeated bytes LocalIPs = 13;
uint32 LocalPort = 14;
uint32 VlessRoute = 15;
}
// SubscribeRoutingStatsRequest subscribes to routing statistics channel if

View File

@@ -36,10 +36,6 @@ func (c routingContext) GetLocalPort() net.Port {
return net.Port(c.RoutingContext.GetLocalPort())
}
func (c routingContext) GetVlessRoute() net.Port {
return net.Port(c.RoutingContext.GetVlessRoute())
}
func (c routingContext) GetRuleTag() string {
return ""
}

View File

@@ -83,6 +83,21 @@ func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
}, nil
}
func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
g := new(strmatcher.MatcherGroup)
for _, d := range domains {
m, err := domainToMatcher(d)
if err != nil {
return nil, err
}
g.Add(m)
}
return &DomainMatcher{
matchers: g,
}, nil
}
func (m *DomainMatcher) ApplyDomain(domain string) bool {
return len(m.matchers.Match(strings.ToLower(domain))) > 0
}
@@ -166,8 +181,6 @@ func (v *PortMatcher) Apply(ctx routing.Context) bool {
return v.port.Contains(ctx.GetSourcePort())
case "target":
return v.port.Contains(ctx.GetTargetPort())
case "vlessRoute":
return v.port.Contains(ctx.GetVlessRoute())
default:
panic("unreachable, asType should be local or source or target")
}

View File

@@ -328,6 +328,9 @@ func TestChinaSites(t *testing.T) {
domains, err := loadGeoSite("CN")
common.Must(err)
matcher, err := NewDomainMatcher(domains)
common.Must(err)
acMatcher, err := NewMphMatcherGroup(domains)
common.Must(err)
@@ -359,9 +362,12 @@ func TestChinaSites(t *testing.T) {
}
for _, testCase := range testCases {
r := acMatcher.ApplyDomain(testCase.Domain)
if r != testCase.Output {
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
r1 := matcher.ApplyDomain(testCase.Domain)
r2 := acMatcher.ApplyDomain(testCase.Domain)
if r1 != testCase.Output {
t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r1)
} else if r2 != testCase.Output {
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r2)
}
}
}
@@ -408,6 +414,48 @@ func BenchmarkMphDomainMatcher(b *testing.B) {
}
}
func BenchmarkDomainMatcher(b *testing.B) {
domains, err := loadGeoSite("CN")
common.Must(err)
matcher, err := NewDomainMatcher(domains)
common.Must(err)
type TestCase struct {
Domain string
Output bool
}
testCases := []TestCase{
{
Domain: "163.com",
Output: true,
},
{
Domain: "163.com",
Output: true,
},
{
Domain: "164.com",
Output: false,
},
{
Domain: "164.com",
Output: false,
},
}
for i := 0; i < 1024; i++ {
testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, testCase := range testCases {
_ = matcher.ApplyDomain(testCase.Domain)
}
}
}
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
var geoips []*GeoIP

View File

@@ -33,22 +33,29 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
conds := NewConditionChan()
if len(rr.Domain) > 0 {
matcher, err := NewMphMatcherGroup(rr.Domain)
if err != nil {
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
switch rr.DomainMatcher {
case "linear":
matcher, err := NewDomainMatcher(rr.Domain)
if err != nil {
return nil, errors.New("failed to build domain condition").Base(err)
}
conds.Add(matcher)
case "mph", "hybrid":
fallthrough
default:
matcher, err := NewMphMatcherGroup(rr.Domain)
if err != nil {
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
}
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)")
conds.Add(matcher)
}
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)")
conds.Add(matcher)
}
if len(rr.UserEmail) > 0 {
conds.Add(NewUserMatcher(rr.UserEmail))
}
if rr.VlessRouteList != nil {
conds.Add(NewPortMatcher(rr.VlessRouteList, "vlessRoute"))
}
if len(rr.InboundTag) > 0 {
conds.Add(NewInboundTagMatcher(rr.InboundTag))
}

View File

@@ -470,7 +470,7 @@ type RoutingRule struct {
// *RoutingRule_Tag
// *RoutingRule_BalancingTag
TargetTag isRoutingRule_TargetTag `protobuf_oneof:"target_tag"`
RuleTag string `protobuf:"bytes,19,opt,name=rule_tag,json=ruleTag,proto3" json:"rule_tag,omitempty"`
RuleTag string `protobuf:"bytes,20,opt,name=rule_tag,json=ruleTag,proto3" json:"rule_tag,omitempty"`
// List of domains for target domain matching.
Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
// List of GeoIPs for target IP address matching. If this entry exists, the
@@ -491,9 +491,9 @@ type RoutingRule struct {
InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"`
Attributes map[string]string `protobuf:"bytes,15,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
LocalGeoip []*GeoIP `protobuf:"bytes,17,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"`
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
DomainMatcher string `protobuf:"bytes,17,opt,name=domain_matcher,json=domainMatcher,proto3" json:"domain_matcher,omitempty"`
LocalGeoip []*GeoIP `protobuf:"bytes,18,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"`
LocalPortList *net.PortList `protobuf:"bytes,19,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
}
func (x *RoutingRule) Reset() {
@@ -624,6 +624,13 @@ func (x *RoutingRule) GetAttributes() map[string]string {
return nil
}
func (x *RoutingRule) GetDomainMatcher() string {
if x != nil {
return x.DomainMatcher
}
return ""
}
func (x *RoutingRule) GetLocalGeoip() []*GeoIP {
if x != nil {
return x.LocalGeoip
@@ -638,13 +645,6 @@ func (x *RoutingRule) GetLocalPortList() *net.PortList {
return nil
}
func (x *RoutingRule) GetVlessRouteList() *net.PortList {
if x != nil {
return x.VlessRouteList
}
return nil
}
type isRoutingRule_TargetTag interface {
isRoutingRule_TargetTag()
}
@@ -1085,13 +1085,13 @@ var file_app_router_config_proto_rawDesc = []byte{
0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74,
0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69,
0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xe8, 0x06, 0x0a, 0x0b, 0x52, 0x6f,
0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xca, 0x06, 0x0a, 0x0b, 0x52, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a,
0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c,
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e,
0x67, 0x54, 0x61, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x61, 0x67,
0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x61, 0x67, 0x12,
0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x54, 0x61, 0x67, 0x12,
0x2f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
@@ -1123,79 +1123,77 @@ var file_app_router_config_proto_rawDesc = []byte{
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52,
0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65,
0x6f, 0x49, 0x50, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12,
0x41, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69,
0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69,
0x73, 0x74, 0x12, 0x43, 0x0a, 0x10, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50,
0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69,
0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69,
0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62,
0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20,
0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c,
0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65,
0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61,
0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10,
0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x61, 0x67,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
0x54, 0x61, 0x67, 0x22, 0x54, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57,
0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x14, 0x0a,
0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61,
0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x17, 0x53, 0x74,
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57,
0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09,
0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78,
0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78,
0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54,
0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c,
0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x9b, 0x02, 0x0a,
0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67,
0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61,
0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75,
0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c,
0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a,
0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66,
0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70,
0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f,
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61,
0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79,
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x37, 0x0a,
0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x12, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61,
0x6c, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x41, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x61,
0x6c, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74,
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67,
0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75,
0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18,
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74,
0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74,
0x65, 0x67, 0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f,
0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20,
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74,
0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61,
0x63, 0x6b, 0x54, 0x61, 0x67, 0x22, 0x54, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78,
0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12,
0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x17,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61,
0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c,
0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x03, 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08,
0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08,
0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52,
0x54, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54,
0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x9b,
0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75,
0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69,
0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e,
0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67,
0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52,
0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12,
0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70,
0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a,
0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13,
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72,
0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -1248,17 +1246,16 @@ var file_app_router_config_proto_depIdxs = []int32{
14, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
4, // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP
15, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
15, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
17, // 16: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
10, // 17: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
1, // 18: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
8, // 19: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
9, // 20: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
21, // [21:21] is the sub-list for method output_type
21, // [21:21] is the sub-list for method input_type
21, // [21:21] is the sub-list for extension type_name
21, // [21:21] is the sub-list for extension extendee
0, // [0:21] is the sub-list for field type_name
17, // 15: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
10, // 16: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
1, // 17: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
8, // 18: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
9, // 19: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
20, // [20:20] is the sub-list for method output_type
20, // [20:20] is the sub-list for method input_type
20, // [20:20] is the sub-list for extension type_name
20, // [20:20] is the sub-list for extension extendee
0, // [0:20] is the sub-list for field type_name
}
func init() { file_app_router_config_proto_init() }

View File

@@ -79,7 +79,7 @@ message RoutingRule {
// Tag of routing balancer.
string balancing_tag = 12;
}
string rule_tag = 19;
string rule_tag = 20;
// List of domains for target domain matching.
repeated Domain domain = 2;
@@ -109,10 +109,10 @@ message RoutingRule {
map<string, string> attributes = 15;
repeated GeoIP local_geoip = 17;
xray.common.net.PortList local_port_list = 18;
string domain_matcher = 17;
xray.common.net.PortList vless_route_list = 20;
repeated GeoIP local_geoip = 18;
xray.common.net.PortList local_port_list = 19;
}
message BalancingRule {

View File

@@ -13,8 +13,6 @@ const (
Size = 8192
)
var ErrBufferFull = errors.New("buffer is full")
var zero = [Size * 10]byte{0}
var pool = bytespool.GetPool(Size)
@@ -246,14 +244,6 @@ func (b *Buffer) Cap() int32 {
return int32(len(b.v))
}
// Available returns the available capacity of the buffer content.
func (b *Buffer) Available() int32 {
if b == nil {
return 0
}
return int32(len(b.v)) - b.end
}
// IsEmpty returns true if the buffer is empty.
func (b *Buffer) IsEmpty() bool {
return b.Len() == 0
@@ -268,16 +258,13 @@ func (b *Buffer) IsFull() bool {
func (b *Buffer) Write(data []byte) (int, error) {
nBytes := copy(b.v[b.end:], data)
b.end += int32(nBytes)
if nBytes < len(data) {
return nBytes, ErrBufferFull
}
return nBytes, nil
}
// WriteByte writes a single byte into the buffer.
func (b *Buffer) WriteByte(v byte) error {
if b.IsFull() {
return ErrBufferFull
return errors.New("buffer full")
}
b.v[b.end] = v
b.end++

View File

@@ -144,7 +144,7 @@ func Compact(mb MultiBuffer) MultiBuffer {
for i := 1; i < len(mb); i++ {
curr := mb[i]
if curr.Len() > last.Available() {
if last.Len()+curr.Len() > Size {
mb2 = append(mb2, last)
last = curr
} else {

View File

@@ -175,29 +175,6 @@ func TestCompact(t *testing.T) {
}
}
func TestCompactWithConsumed(t *testing.T) {
// make a consumed buffer (a.Start != 0)
a := New()
for range 8192 {
common.Must2(a.WriteString("a"))
}
a.Read(make([]byte, 2))
b := New()
for range 2 {
common.Must2(b.WriteString("b"))
}
mb := MultiBuffer{a, b}
cmb := Compact(mb)
mbc := &MultiBufferContainer{mb}
mbc.Read(make([]byte, 8190))
if w := cmb.String(); w != "bb" {
t.Error("unexpected Compact result ", w)
}
}
func BenchmarkSplitBytes(b *testing.B) {
var mb MultiBuffer
raw := make([]byte, Size)

View File

@@ -23,9 +23,7 @@ func Must(err error) {
}
// Must2 panics if the second parameter is not nil, otherwise returns the first parameter.
// This is useful when function returned "sth, err" and avoid many "if err != nil"
// Internal usage only, if user input can cause err, it must be handled
func Must2[T any](v T, err error) T {
func Must2(v interface{}, err error) interface{} {
Must(err)
return v
}

View File

@@ -32,7 +32,9 @@ func NewAesCTRStream(key []byte, iv []byte) cipher.Stream {
// NewAesGcm creates a AEAD cipher based on AES-GCM.
func NewAesGcm(key []byte) cipher.AEAD {
block := common.Must2(aes.NewCipher(key))
aead := common.Must2(cipher.NewGCM(block))
block, err := aes.NewCipher(key)
common.Must(err)
aead, err := cipher.NewGCM(block)
common.Must(err)
return aead
}

View File

@@ -2,6 +2,8 @@ package crypto_test
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
"testing"
@@ -16,8 +18,11 @@ import (
func TestAuthenticationReaderWriter(t *testing.T) {
key := make([]byte, 16)
rand.Read(key)
block, err := aes.NewCipher(key)
common.Must(err)
aead := NewAesGcm(key)
aead, err := cipher.NewGCM(block)
common.Must(err)
const payloadSize = 1024 * 80
rawPayload := make([]byte, payloadSize)
@@ -66,7 +71,7 @@ func TestAuthenticationReaderWriter(t *testing.T) {
t.Error(r)
}
_, err := reader.ReadMultiBuffer()
_, err = reader.ReadMultiBuffer()
if err != io.EOF {
t.Error("error: ", err)
}
@@ -75,8 +80,11 @@ func TestAuthenticationReaderWriter(t *testing.T) {
func TestAuthenticationReaderWriterPacket(t *testing.T) {
key := make([]byte, 16)
common.Must2(rand.Read(key))
block, err := aes.NewCipher(key)
common.Must(err)
aead := NewAesGcm(key)
aead, err := cipher.NewGCM(block)
common.Must(err)
cache := buf.New()
iv := make([]byte, 12)

View File

@@ -118,7 +118,9 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
}
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
ctx = session.SubContextFromMuxInbound(ctx)
// deep-clone outbounds because it is going to be mutated concurrently
// (Target and OriginalTarget)
ctx = session.ContextCloneOutboundsAndContent(ctx)
errors.LogInfo(ctx, "received request for ", meta.Target)
{
msg := &log.AccessMessage{

View File

@@ -79,20 +79,18 @@ type CommandSwitchAccount struct {
}
var (
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
// Keep in sync with crypto/tls/cipher_suites.go.
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
// Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
)
func (sc *SecurityConfig) GetSecurityType() SecurityType {
if sc == nil || sc.Type == SecurityType_AUTO {
if hasAESGCMHardwareSupport {
if HasAESGCMHardwareSupport {
return SecurityType_AES128_GCM
}
return SecurityType_CHACHA20_POLY1305

View File

@@ -184,7 +184,8 @@ func getConfig() string {
"inboundTag": [
"api-in"
],
"outboundTag": "api"
"outboundTag": "api",
"type": "field"
}
],
"domainStrategy": "AsIs"

View File

@@ -16,15 +16,15 @@ const (
inboundSessionKey ctx.SessionKey = 1
outboundSessionKey ctx.SessionKey = 2
contentSessionKey ctx.SessionKey = 3
muxPreferredSessionKey ctx.SessionKey = 4 // unused
sockoptSessionKey ctx.SessionKey = 5 // used by dokodemo to only receive sockopt.Mark
trackedConnectionErrorKey ctx.SessionKey = 6 // used by observer to get outbound error
dispatcherKey ctx.SessionKey = 7 // used by ss2022 inbounds to get dispatcher
timeoutOnlyKey ctx.SessionKey = 8 // mux context's child contexts to only cancel when its own traffic times out
allowedNetworkKey ctx.SessionKey = 9 // muxcool server control incoming request tcp/udp
handlerSessionKey ctx.SessionKey = 10 // unused
mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer
mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer
muxPreferredSessionKey ctx.SessionKey = 4
sockoptSessionKey ctx.SessionKey = 5
trackedConnectionErrorKey ctx.SessionKey = 6
dispatcherKey ctx.SessionKey = 7
timeoutOnlyKey ctx.SessionKey = 8
allowedNetworkKey ctx.SessionKey = 9
handlerSessionKey ctx.SessionKey = 10
mitmAlpn11Key ctx.SessionKey = 11
mitmServerNameKey ctx.SessionKey = 12
)
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
@@ -42,8 +42,18 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co
return context.WithValue(ctx, outboundSessionKey, outbounds)
}
func SubContextFromMuxInbound(ctx context.Context) context.Context {
newOutbounds := []*Outbound{{}}
func ContextCloneOutboundsAndContent(ctx context.Context) context.Context {
outbounds := OutboundsFromContext(ctx)
newOutbounds := make([]*Outbound, len(outbounds))
for i, ob := range outbounds {
if ob == nil {
continue
}
// copy outbound by value
v := *ob
newOutbounds[i] = &v
}
content := ContentFromContext(ctx)
newContent := Content{}

View File

@@ -46,11 +46,9 @@ type Inbound struct {
Name string
// User is the user that authenticates for the inbound. May be nil if the protocol allows anonymous traffic.
User *protocol.MemoryUser
// VlessRoute is the user-sent VLESS UUID's 7th<<8 | 8th bytes.
VlessRoute net.Port
// Used by splice copy. Conn is actually internet.Connection. May be nil.
// Conn is actually internet.Connection. May be nil.
Conn net.Conn
// Used by splice copy. Timer of the inbound buf copier. May be nil.
// Timer of the inbound buf copier. May be nil.
Timer *signal.ActivityTimer
// CanSpliceCopy is a property for this connection
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
@@ -69,33 +67,31 @@ type Outbound struct {
Tag string
// Name of the outbound proxy that handles the connection.
Name string
// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
// Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
Conn net.Conn
// CanSpliceCopy is a property for this connection
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
CanSpliceCopy int
}
// SniffingRequest controls the behavior of content sniffing. They are from inbound config. Read-only
// SniffingRequest controls the behavior of content sniffing.
type SniffingRequest struct {
ExcludeForDomain []string
OverrideDestinationForProtocol []string
ExcludeForDomain []string // read-only once set
OverrideDestinationForProtocol []string // read-only once set
Enabled bool
MetadataOnly bool
RouteOnly bool
}
// Content is the metadata of the connection content. Mainly used for routing.
// Content is the metadata of the connection content.
type Content struct {
// Protocol of current content.
Protocol string
SniffingRequest SniffingRequest
// HTTP traffic sniffed headers
Attributes map[string]string
// SkipDNSResolve is set from DNS module. the DOH remote server maybe a domain name, this prevents cycle resolving dead loop
SkipDNSResolve bool
}

View File

@@ -41,9 +41,6 @@ type Context interface {
// GetUser returns the user email from the connection content, if exists.
GetUser() string
// GetVlessRoute returns the user-sent VLESS UUID's 7th<<8 | 8th bytes, if exists.
GetVlessRoute() net.Port
// GetAttributes returns extra attributes from the conneciont content.
GetAttributes() map[string]string

View File

@@ -128,14 +128,6 @@ func (ctx *Context) GetUser() string {
return ctx.Inbound.User.Email
}
// GetVlessRoute implements routing.Context.
func (ctx *Context) GetVlessRoute() net.Port {
if ctx.Inbound == nil {
return 0
}
return ctx.Inbound.VlessRoute
}
// GetAttributes implements routing.Context.
func (ctx *Context) GetAttributes() map[string]string {
if ctx.Content == nil {

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/xtls/xray-core
go 1.25
go 1.24
require (
github.com/cloudflare/circl v1.6.1

View File

@@ -28,7 +28,9 @@ func (c *DNSOutboundConfig) Build() (proto.Message, error) {
config.Server.Address = c.Address.Build()
}
switch c.NonIPQuery {
case "", "reject", "drop", "skip":
case "":
c.NonIPQuery = "drop"
case "drop", "skip", "reject":
default:
return nil, errors.New(`unknown "nonIPQuery": `, c.NonIPQuery)
}

View File

@@ -27,6 +27,7 @@ func TestDnsProxyConfig(t *testing.T) {
Address: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})),
Port: 53,
},
Non_IPQuery: "drop",
},
},
})

View File

@@ -14,7 +14,6 @@ import (
)
type FreedomConfig struct {
TargetStrategy string `json:"targetStrategy"`
DomainStrategy string `json:"domainStrategy"`
Redirect string `json:"redirect"`
UserLevel uint32 `json:"userLevel"`
@@ -41,11 +40,7 @@ type Noise struct {
// Build implements Buildable
func (c *FreedomConfig) Build() (proto.Message, error) {
config := new(freedom.Config)
targetStrategy := c.TargetStrategy
if targetStrategy == "" {
targetStrategy = c.DomainStrategy
}
switch strings.ToLower(targetStrategy) {
switch strings.ToLower(c.DomainStrategy) {
case "asis", "":
config.DomainStrategy = freedom.Config_AS_IS
case "useip":
@@ -69,7 +64,7 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
case "forceipv6v4":
config.DomainStrategy = freedom.Config_FORCE_IP64
default:
return nil, errors.New("unsupported domain strategy: ", targetStrategy)
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
}
if c.Fragment != nil {

View File

@@ -74,6 +74,8 @@ type RouterConfig struct {
RuleList []json.RawMessage `json:"rules"`
DomainStrategy *string `json:"domainStrategy"`
Balancers []*BalancingRule `json:"balancers"`
DomainMatcher string `json:"domainMatcher"`
}
func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
@@ -109,6 +111,10 @@ func (c *RouterConfig) Build() (*router.Config, error) {
return nil, err
}
if rule.DomainMatcher == "" {
rule.DomainMatcher = c.DomainMatcher
}
config.Rule = append(config.Rule, rule)
}
for _, rawBalancer := range c.Balancers {
@@ -123,8 +129,11 @@ func (c *RouterConfig) Build() (*router.Config, error) {
type RouterRule struct {
RuleTag string `json:"ruleTag"`
Type string `json:"type"`
OutboundTag string `json:"outboundTag"`
BalancerTag string `json:"balancerTag"`
DomainMatcher string `json:"domainMatcher"`
}
func ParseIP(s string) (*router.CIDR, error) {
@@ -531,7 +540,6 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
Source *StringList `json:"source"`
SourcePort *PortList `json:"sourcePort"`
User *StringList `json:"user"`
VlessRoute *PortList `json:"vlessRoute"`
InboundTag *StringList `json:"inboundTag"`
Protocols *StringList `json:"protocol"`
Attributes map[string]string `json:"attrs"`
@@ -559,6 +567,10 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
return nil, errors.New("neither outboundTag nor balancerTag is specified in routing rule")
}
if rawFieldRule.DomainMatcher != "" {
rule.DomainMatcher = rawFieldRule.DomainMatcher
}
if rawFieldRule.Domain != nil {
for _, domain := range *rawFieldRule.Domain {
rules, err := parseDomainRule(domain)
@@ -629,10 +641,6 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
}
}
if rawFieldRule.VlessRoute != nil {
rule.VlessRouteList = rawFieldRule.VlessRoute.Build()
}
if rawFieldRule.InboundTag != nil {
for _, s := range *rawFieldRule.InboundTag {
rule.InboundTag = append(rule.InboundTag, s)
@@ -658,10 +666,12 @@ func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
if err != nil {
return nil, errors.New("invalid router rule").Base(err)
}
fieldrule, err := parseFieldRule(msg)
if err != nil {
return nil, errors.New("invalid field rule").Base(err)
if rawRule.Type == "" || strings.EqualFold(rawRule.Type, "field") {
fieldrule, err := parseFieldRule(msg)
if err != nil {
return nil, errors.New("invalid field rule").Base(err)
}
return fieldrule, nil
}
return fieldrule, nil
return nil, errors.New("unknown router rule type: ", rawRule.Type)
}

View File

@@ -91,6 +91,7 @@ func TestRouterConfig(t *testing.T) {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": [
"baidu.com",
"qq.com"
@@ -98,15 +99,18 @@ func TestRouterConfig(t *testing.T) {
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"10.0.0.0/8",
"::1/128"
],
"outboundTag": "test"
},{
"type": "field",
"port": "53, 443, 1000-2000",
"outboundTag": "test"
},{
"type": "field",
"port": 123,
"outboundTag": "test"
}
@@ -245,6 +249,7 @@ func TestRouterConfig(t *testing.T) {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": [
"baidu.com",
"qq.com"
@@ -252,6 +257,7 @@ func TestRouterConfig(t *testing.T) {
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"10.0.0.0/8",
"::1/128"

View File

@@ -1,6 +1,7 @@
package conf
import (
"encoding/base64"
"encoding/json"
"path/filepath"
"runtime"
@@ -32,20 +33,12 @@ type VLessInboundConfig struct {
Clients []json.RawMessage `json:"clients"`
Decryption string `json:"decryption"`
Fallbacks []*VLessInboundFallback `json:"fallbacks"`
Flow string `json:"flow"`
}
// Build implements Buildable
func (c *VLessInboundConfig) Build() (proto.Message, error) {
config := new(inbound.Config)
config.Clients = make([]*protocol.User, len(c.Clients))
switch c.Flow {
case vless.None:
c.Flow = ""
case "", vless.XRV:
default:
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
}
for idx, rawUser := range c.Clients {
user := new(protocol.User)
if err := json.Unmarshal(rawUser, user); err != nil {
@@ -63,11 +56,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
account.Id = u.String()
switch account.Flow {
case "":
account.Flow = c.Flow
case vless.None:
account.Flow = ""
case vless.XRV:
case "", vless.XRV:
default:
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
}
@@ -80,10 +69,46 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
config.Clients[idx] = user
}
if c.Decryption != "none" {
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
}
config.Decryption = c.Decryption
if !func() bool {
s := strings.Split(config.Decryption, ".")
if len(s) != 5 || s[2] != "mlkem768Seed" {
return false
}
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return false
}
i, err := strconv.Atoi(t)
if err != nil {
return false
}
config.Minutes = uint32(i)
}
switch s[1] {
case "native":
case "divide":
config.XorMode = 1
case "random":
config.XorMode = 2
default:
return false
}
if b, _ := base64.RawURLEncoding.DecodeString(s[3]); len(b) != 32 {
return false
}
if b, _ := base64.RawURLEncoding.DecodeString(s[4]); len(b) != 64 {
return false
}
config.Decryption = s[4] + "." + s[3]
return true
}() && config.Decryption != "none" {
if config.Decryption == "" {
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
}
return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption)
}
for _, fb := range c.Fallbacks {
var i uint16
@@ -155,16 +180,16 @@ type VLessOutboundConfig struct {
func (c *VLessOutboundConfig) Build() (proto.Message, error) {
config := new(outbound.Config)
if len(c.Vnext) == 0 {
return nil, errors.New(`VLESS settings: "vnext" is empty`)
if len(c.Vnext) != 1 {
return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`)
}
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
for idx, rec := range c.Vnext {
if rec.Address == nil {
return nil, errors.New(`VLESS vnext: "address" is not set`)
}
if len(rec.Users) == 0 {
return nil, errors.New(`VLESS vnext: "users" is empty`)
if len(rec.Users) != 1 {
return nil, errors.New(`VLESS vnext: "users" should have one and only one member`)
}
spec := &protocol.ServerEndpoint{
Address: rec.Address.Build(),
@@ -193,8 +218,44 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
}
if account.Encryption != "none" {
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
if !func() bool {
s := strings.Split(account.Encryption, ".")
if len(s) != 5 || s[2] != "mlkem768Client" {
return false
}
if s[0] != "1rtt" {
t := strings.TrimSuffix(s[0], "min")
if t == s[0] {
return false
}
i, err := strconv.Atoi(t)
if err != nil {
return false
}
account.Minutes = uint32(i)
}
switch s[1] {
case "native":
case "divide":
account.XorMode = 1
case "random":
account.XorMode = 2
default:
return false
}
if b, _ := base64.RawURLEncoding.DecodeString(s[3]); len(b) != 32 {
return false
}
if b, _ := base64.RawURLEncoding.DecodeString(s[4]); len(b) != 1184 {
return false
}
account.Encryption = s[4] + "." + s[3]
return true
}() && account.Encryption != "none" {
if account.Encryption == "" {
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
}
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
}
user.Account = serial.ToTypedMessage(account)

View File

@@ -260,14 +260,13 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
}
type OutboundDetourConfig struct {
Protocol string `json:"protocol"`
SendThrough *string `json:"sendThrough"`
Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *ProxyConfig `json:"proxySettings"`
MuxSettings *MuxConfig `json:"mux"`
TargetStrategy string `json:"targetStrategy"`
Protocol string `json:"protocol"`
SendThrough *string `json:"sendThrough"`
Tag string `json:"tag"`
Settings *json.RawMessage `json:"settings"`
StreamSetting *StreamConfig `json:"streamSettings"`
ProxySettings *ProxyConfig `json:"proxySettings"`
MuxSettings *MuxConfig `json:"mux"`
}
func (c *OutboundDetourConfig) checkChainProxyConfig() error {
@@ -283,32 +282,6 @@ func (c *OutboundDetourConfig) checkChainProxyConfig() error {
// Build implements Buildable.
func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
senderSettings := &proxyman.SenderConfig{}
switch strings.ToLower(c.TargetStrategy) {
case "asis", "":
senderSettings.TargetStrategy = internet.DomainStrategy_AS_IS
case "useip":
senderSettings.TargetStrategy = internet.DomainStrategy_USE_IP
case "useipv4":
senderSettings.TargetStrategy = internet.DomainStrategy_USE_IP4
case "useipv6":
senderSettings.TargetStrategy = internet.DomainStrategy_USE_IP6
case "useipv4v6":
senderSettings.TargetStrategy = internet.DomainStrategy_USE_IP46
case "useipv6v4":
senderSettings.TargetStrategy = internet.DomainStrategy_USE_IP64
case "forceip":
senderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP
case "forceipv4":
senderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP4
case "forceipv6":
senderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP6
case "forceipv4v6":
senderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP46
case "forceipv6v4":
senderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP64
default:
return nil, errors.New("unsupported target domain strategy: ", c.TargetStrategy)
}
if err := c.checkChainProxyConfig(); err != nil {
return nil, err
}

View File

@@ -77,6 +77,7 @@ func TestXrayConfig(t *testing.T) {
"ip": [
"10.0.0.0/8"
],
"type": "field",
"outboundTag": "blocked"
}
]

View File

@@ -93,6 +93,7 @@ func executeSourceIpBlock(cmd *base.Command, args []string) {
"ruleTag" : "%s",
"inboundTag": %s,
"outboundTag": "%s",
"type": "field",
"source": %s
}
]

View File

@@ -17,5 +17,6 @@ func init() {
cmdX25519,
cmdWG,
cmdMLDSA65,
cmdMLKEM768,
)
}

View File

@@ -1,17 +1,13 @@
package all
import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"fmt"
"golang.org/x/crypto/curve25519"
)
func Curve25519Genkey(StdEncoding bool, input_base64 string) {
var output string
var err error
var privateKey, publicKey []byte
var encoding *base64.Encoding
if *input_stdEncoding || StdEncoding {
encoding = base64.StdEncoding
@@ -19,40 +15,32 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
encoding = base64.RawURLEncoding
}
var privateKey []byte
if len(input_base64) > 0 {
privateKey, err = encoding.DecodeString(input_base64)
if err != nil {
output = err.Error()
goto out
}
if len(privateKey) != curve25519.ScalarSize {
output = "Invalid length of private key."
goto out
privateKey, _ = encoding.DecodeString(input_base64)
if len(privateKey) != 32 {
fmt.Println("Invalid length of X25519 private key.")
return
}
}
if privateKey == nil {
privateKey = make([]byte, curve25519.ScalarSize)
if _, err = rand.Read(privateKey); err != nil {
output = err.Error()
goto out
}
privateKey = make([]byte, 32)
rand.Read(privateKey)
}
// Modify random bytes using algorithm described at:
// https://cr.yp.to/ecdh.html.
// https://cr.yp.to/ecdh.html
// (Just to make sure printing the real private key)
privateKey[0] &= 248
privateKey[31] &= 127
privateKey[31] |= 64
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
output = err.Error()
goto out
key, err := ecdh.X25519().NewPrivateKey(privateKey)
if err != nil {
fmt.Println(err.Error())
return
}
output = fmt.Sprintf("Private key: %v\nPublic key: %v",
fmt.Printf("PrivateKey: %v\nPassword: %v",
encoding.EncodeToString(privateKey),
encoding.EncodeToString(publicKey))
out:
fmt.Println(output)
encoding.EncodeToString(key.PublicKey().Bytes()))
}

View File

@@ -11,9 +11,9 @@ import (
var cmdMLDSA65 = &base.Command{
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-DSA-65 post-quantum signature`,
Short: `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`,
Long: `
Generate key pair for ML-DSA-65 post-quantum signature.
Generate key pair for ML-DSA-65 post-quantum signature (REALITY).
Random: {{.Exec}} mldsa65
@@ -25,12 +25,16 @@ func init() {
cmdMLDSA65.Run = executeMLDSA65 // break init loop
}
var input_seed = cmdMLDSA65.Flag.String("i", "", "")
var input_mldsa65 = cmdMLDSA65.Flag.String("i", "", "")
func executeMLDSA65(cmd *base.Command, args []string) {
var seed [32]byte
if len(*input_seed) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
if len(*input_mldsa65) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65)
if len(s) != 32 {
fmt.Println("Invalid length of ML-DSA-65 seed.")
return
}
seed = [32]byte(s)
} else {
rand.Read(seed[:])

View File

@@ -0,0 +1,50 @@
package all
import (
"crypto/mlkem"
"crypto/rand"
"crypto/sha3"
"encoding/base64"
"fmt"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdMLKEM768 = &base.Command{
UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS)`,
Long: `
Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS).
Random: {{.Exec}} mlkem768
From seed: {{.Exec}} mlkem768 -i "seed (base64.RawURLEncoding)"
`,
}
func init() {
cmdMLKEM768.Run = executeMLKEM768 // break init loop
}
var input_mlkem768 = cmdMLKEM768.Flag.String("i", "", "")
func executeMLKEM768(cmd *base.Command, args []string) {
var seed [64]byte
if len(*input_mlkem768) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_mlkem768)
if len(s) != 64 {
fmt.Println("Invalid length of ML-KEM-768 seed.")
return
}
seed = [64]byte(s)
} else {
rand.Read(seed[:])
}
key, _ := mlkem.NewDecapsulationKey768(seed[:])
client := key.EncapsulationKey().Bytes()
hash32 := sha3.Sum256(client)
fmt.Printf("Seed: %v\nClient: %v\nHash11: %v",
base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(client),
base64.RawURLEncoding.EncodeToString(hash32[:11]))
}

View File

@@ -6,7 +6,9 @@ import (
"encoding/base64"
"fmt"
"net"
"reflect"
"strconv"
"unsafe"
"github.com/xtls/xray-core/main/commands/base"
. "github.com/xtls/xray-core/transport/internet/tls"
@@ -137,15 +139,14 @@ func printCertificates(certs []*x509.Certificate) {
}
func printTLSConnDetail(tlsConn *gotls.Conn) {
connectionState := tlsConn.ConnectionState()
var tlsVersion string
if connectionState.Version == gotls.VersionTLS13 {
if tlsConn.ConnectionState().Version == gotls.VersionTLS13 {
tlsVersion = "TLS 1.3"
} else if connectionState.Version == gotls.VersionTLS12 {
} else if tlsConn.ConnectionState().Version == gotls.VersionTLS12 {
tlsVersion = "TLS 1.2"
}
fmt.Println("TLS Version: ", tlsVersion)
curveID := connectionState.CurveID
curveID := *(*gotls.CurveID)(unsafe.Pointer(reflect.ValueOf(tlsConn).Elem().FieldByName("curveID").UnsafeAddr()))
if curveID != 0 {
PostQuantum := (curveID == gotls.X25519MLKEM768)
fmt.Println("TLS Post-Quantum key exchange: ", PostQuantum, "("+curveID.String()+")")

View File

@@ -9,9 +9,9 @@ import (
var cmdUUID = &base.Command{
UsageLine: `{{.Exec}} uuid [-i "example"]`,
Short: `Generate UUIDv4 or UUIDv5`,
Short: `Generate UUIDv4 or UUIDv5 (VLESS)`,
Long: `
Generate UUIDv4 or UUIDv5.
Generate UUIDv4 or UUIDv5 (VLESS).
UUIDv4 (random): {{.Exec}} uuid

View File

@@ -6,9 +6,9 @@ import (
var cmdWG = &base.Command{
UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`,
Short: `Generate key pair for wireguard key exchange`,
Short: `Generate key pair for X25519 key exchange (WireGuard)`,
Long: `
Generate key pair for wireguard key exchange.
Generate key pair for X25519 key exchange (WireGuard).
Random: {{.Exec}} wg

View File

@@ -6,9 +6,9 @@ import (
var cmdX25519 = &base.Command{
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
Short: `Generate key pair for x25519 key exchange`,
Short: `Generate key pair for X25519 key exchange (VLESS, REALITY)`,
Long: `
Generate key pair for x25519 key exchange.
Generate key pair for X25519 key exchange (VLESS, REALITY).
Random: {{.Exec}} x25519

View File

@@ -65,9 +65,6 @@ func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager polic
h.server = config.Server.AsDestination()
}
h.nonIPQuery = config.Non_IPQuery
if h.nonIPQuery == "" {
h.nonIPQuery = "reject"
}
h.blockTypes = config.BlockTypes
return nil
}

View File

@@ -91,7 +91,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
}
}
if dest.Port == 0 {
dest.Port = net.Port(common.Must2(strconv.Atoi(port)))
dest.Port = net.Port(common.Must2(strconv.Atoi(port)).(int))
}
if d.portMap != nil && d.portMap[port] != "" {
h, p, _ := net.SplitHostPort(d.portMap[port])
@@ -99,7 +99,7 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
dest.Address = net.ParseAddress(h)
}
if len(p) > 0 {
dest.Port = net.Port(common.Must2(strconv.Atoi(p)))
dest.Port = net.Port(common.Must2(strconv.Atoi(p)).(int))
}
}
}

View File

@@ -95,9 +95,6 @@ func (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network,
inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel,
}
if isTransportConn(conn) {
inbound.CanSpliceCopy = 3
}
var reader *bufio.Reader
if len(firstbyte) > 0 {
readerWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
@@ -210,9 +207,7 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *buf
}
responseDone := func() error {
if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
inbound.CanSpliceCopy = 1
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(conn)
@@ -375,20 +370,6 @@ func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer
return http.ReadResponse(r, req)
}
// isTransportConn return false if the conn is a raw tcp conn without transport or tls, can process splice copy
func isTransportConn(conn stat.Connection) bool {
if conn != nil {
statConn, ok := conn.(*stat.CounterConnection)
if ok {
conn = statConn.Connection
}
if _, ok := conn.(*net.TCPConn); ok {
return false
}
}
return true
}
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))

View File

@@ -25,6 +25,7 @@ import (
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/reality"
@@ -524,24 +525,40 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
}
}
// UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
// UnwrapRawConn support unwrap encryption, stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
var readCounter, writerCounter stats.Counter
if conn != nil {
statConn, ok := conn.(*stat.CounterConnection)
if ok {
isEncryption := false
if clientConn, ok := conn.(*encryption.ClientConn); ok {
conn = clientConn.Conn
isEncryption = true
}
if serverConn, ok := conn.(*encryption.ServerConn); ok {
conn = serverConn.Conn
isEncryption = true
}
if xorConn, ok := conn.(*encryption.XorConn); ok {
if !xorConn.Divide {
return xorConn, nil, nil // full-random xorConn should not be penetrated
}
conn = xorConn.Conn
}
if statConn, ok := conn.(*stat.CounterConnection); ok {
conn = statConn.Connection
readCounter = statConn.ReadCounter
writerCounter = statConn.WriteCounter
}
if xc, ok := conn.(*tls.Conn); ok {
conn = xc.NetConn()
} else if utlsConn, ok := conn.(*tls.UConn); ok {
conn = utlsConn.NetConn()
} else if realityConn, ok := conn.(*reality.Conn); ok {
conn = realityConn.NetConn()
} else if realityUConn, ok := conn.(*reality.UConn); ok {
conn = realityUConn.NetConn()
if !isEncryption { // avoids double penetration
if xc, ok := conn.(*tls.Conn); ok {
conn = xc.NetConn()
} else if utlsConn, ok := conn.(*tls.UConn); ok {
conn = utlsConn.NetConn()
} else if realityConn, ok := conn.(*reality.Conn); ok {
conn = realityConn.NetConn()
} else if realityUConn, ok := conn.(*reality.UConn); ok {
conn = realityUConn.NetConn()
}
}
if pc, ok := conn.(*proxyproto.Conn); ok {
conn = pc.Raw()
@@ -632,9 +649,20 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
}
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
errors.LogInfo(ctx, "CopyRawConn readv")
errors.LogInfo(ctx, "CopyRawConn (maybe) readv")
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
return errors.New("failed to process response").Base(err)
}
return nil
}
func IsRAWTransport(conn stat.Connection) bool {
iConn := conn
if statConn, ok := iConn.(*stat.CounterConnection); ok {
iConn = statConn.Connection
}
_, ok1 := iConn.(*proxyproto.Conn)
_, ok2 := iConn.(*net.TCPConn)
_, ok3 := iConn.(*internet.UnixConnWrapper)
return ok1 || ok2 || ok3
}

View File

@@ -2,6 +2,7 @@ package shadowsocks
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/sha1"
@@ -57,7 +58,11 @@ func (a *MemoryAccount) CheckIV(iv []byte) error {
}
func createAesGcm(key []byte) cipher.AEAD {
return crypto.NewAesGcm(key)
block, err := aes.NewCipher(key)
common.Must(err)
gcm, err := cipher.NewGCM(block)
common.Must(err)
return gcm
}
func createChaCha20Poly1305(key []byte) cipher.AEAD {

View File

@@ -75,9 +75,6 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel,
}
if isTransportConn(conn) {
inbound.CanSpliceCopy = 3
}
switch network {
case net.Network_TCP:
@@ -202,9 +199,7 @@ func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
}
responseDone := func() error {
if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
inbound.CanSpliceCopy = 1
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(writer)
@@ -264,9 +259,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
if inbound != nil && inbound.Source.IsValid() {
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
}
if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
inbound.CanSpliceCopy = 1
var dest *net.Destination
@@ -315,20 +308,6 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
}
}
// isTransportConn return false if the conn is a raw tcp conn without transport or tls, can process splice copy
func isTransportConn(conn stat.Connection) bool {
if conn != nil {
statConn, ok := conn.(*stat.CounterConnection)
if ok {
conn = statConn.Connection
}
if _, ok := conn.(*net.TCPConn); ok {
return false
}
}
return true
}
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))

View File

@@ -18,6 +18,8 @@ func (a *Account) AsAccount() (protocol.Account, error) {
ID: protocol.NewID(id),
Flow: a.Flow, // needs parser here?
Encryption: a.Encryption, // needs parser here?
XorMode: a.XorMode,
Minutes: a.Minutes,
}, nil
}
@@ -27,8 +29,10 @@ type MemoryAccount struct {
ID *protocol.ID
// Flow of the account. May be "xtls-rprx-vision".
Flow string
// Encryption of the account. Used for client connections, and only accepts "none" for now.
Encryption string
XorMode uint32
Minutes uint32
}
// Equals implements protocol.Account.Equals().
@@ -45,5 +49,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
Id: a.ID.String(),
Flow: a.Flow,
Encryption: a.Encryption,
XorMode: a.XorMode,
Minutes: a.Minutes,
}
}

View File

@@ -28,9 +28,10 @@ type Account struct {
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Flow settings. May be "xtls-rprx-vision".
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
// Encryption settings. Only applies to client side, and only accepts "none" for now.
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
Minutes uint32 `protobuf:"varint,5,opt,name=minutes,proto3" json:"minutes,omitempty"`
}
func (x *Account) Reset() {
@@ -84,23 +85,40 @@ func (x *Account) GetEncryption() string {
return ""
}
func (x *Account) GetXorMode() uint32 {
if x != nil {
return x.XorMode
}
return 0
}
func (x *Account) GetMinutes() uint32 {
if x != nil {
return x.Minutes
}
return 0
}
var File_proxy_vless_account_proto protoreflect.FileDescriptor
var file_proxy_vless_account_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a,
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14,
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10,
0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x81, 0x01,
0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f,
0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a,
0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a,
0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74,
0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65,
0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72,
0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65,
0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e,
0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@@ -11,6 +11,8 @@ message Account {
string id = 1;
// Flow settings. May be "xtls-rprx-vision".
string flow = 2;
// Encryption settings. Only applies to client side, and only accepts "none" for now.
string encryption = 3;
uint32 xorMode = 4;
uint32 minutes = 5;
}

View File

@@ -62,7 +62,7 @@ func EncodeRequestHeader(writer io.Writer, request *protocol.RequestHeader, requ
}
// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.
func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validator vless.Validator) ([]byte, *protocol.RequestHeader, *Addons, bool, error) {
func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validator vless.Validator) (*protocol.RequestHeader, *Addons, bool, error) {
buffer := buf.StackNew()
defer buffer.Release()
@@ -72,7 +72,7 @@ func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validat
request.Version = first.Byte(0)
} else {
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
return nil, nil, nil, false, errors.New("failed to read request version").Base(err)
return nil, nil, false, errors.New("failed to read request version").Base(err)
}
request.Version = buffer.Byte(0)
}
@@ -87,13 +87,13 @@ func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validat
} else {
buffer.Clear()
if _, err := buffer.ReadFullFrom(reader, 16); err != nil {
return nil, nil, nil, false, errors.New("failed to read request user id").Base(err)
return nil, nil, false, errors.New("failed to read request user id").Base(err)
}
copy(id[:], buffer.Bytes())
}
if request.User = validator.Get(id); request.User == nil {
return nil, nil, nil, isfb, errors.New("invalid request user id")
return nil, nil, isfb, errors.New("invalid request user id")
}
if isfb {
@@ -102,12 +102,12 @@ func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validat
requestAddons, err := DecodeHeaderAddons(&buffer, reader)
if err != nil {
return nil, nil, nil, false, errors.New("failed to decode request header addons").Base(err)
return nil, nil, false, errors.New("failed to decode request header addons").Base(err)
}
buffer.Clear()
if _, err := buffer.ReadFullFrom(reader, 1); err != nil {
return nil, nil, nil, false, errors.New("failed to read request command").Base(err)
return nil, nil, false, errors.New("failed to read request command").Base(err)
}
request.Command = protocol.RequestCommand(buffer.Byte(0))
@@ -122,11 +122,11 @@ func DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validat
}
}
if request.Address == nil {
return nil, nil, nil, false, errors.New("invalid request address")
return nil, nil, false, errors.New("invalid request address")
}
return id[:], request, requestAddons, false, nil
return request, requestAddons, false, nil
default:
return nil, nil, nil, isfb, errors.New("invalid request version")
return nil, nil, isfb, errors.New("invalid request version")
}
}
@@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
}
// XtlsRead filter and read xtls protocol
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
err := func() error {
for {
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
@@ -194,15 +194,21 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
if !buffer.IsEmpty() {
timer.Update()
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
// XTLS Vision processes struct TLS Conn's input and rawInput
if inputBuffer, err := buf.ReadFrom(input); err == nil {
if !inputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
// XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput
if peerCache != nil {
if len(*peerCache) != 0 {
buffer = buf.MergeBytes(buffer, *peerCache)
}
}
if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil {
if !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
} else {
if inputBuffer, err := buf.ReadFrom(input); err == nil {
if !inputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
}
}
if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil {
if !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
}
}
}
}

View File

@@ -45,7 +45,7 @@ func TestRequestSerialization(t *testing.T) {
Validator := new(vless.MemoryValidator)
Validator.Add(user)
_, actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
common.Must(err)
if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" {
@@ -86,7 +86,7 @@ func TestInvalidRequest(t *testing.T) {
Validator := new(vless.MemoryValidator)
Validator.Add(user)
_, _, _, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
_, _, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
if err == nil {
t.Error("nil error")
}
@@ -117,7 +117,7 @@ func TestMuxRequest(t *testing.T) {
Validator := new(vless.MemoryValidator)
Validator.Add(user)
_, actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)
common.Must(err)
if r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != "" {

View File

@@ -0,0 +1,266 @@
package encryption
import (
"bytes"
"crypto/cipher"
"crypto/ecdh"
"crypto/mlkem"
"crypto/rand"
"crypto/sha3"
"io"
"net"
"strings"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
)
var ClientCipher byte
func init() {
if protocol.HasAESGCMHardwareSupport {
ClientCipher = 1
}
}
type ClientInstance struct {
sync.RWMutex
nfsEKey *mlkem.EncapsulationKey768
hash11 [11]byte // no more capacity
xorMode uint32
xorPKey *ecdh.PublicKey
minutes time.Duration
expire time.Time
baseKey []byte
ticket []byte
}
type ClientConn struct {
net.Conn
instance *ClientInstance
baseKey []byte
ticket []byte
random []byte
aead cipher.AEAD
nonce []byte
peerAEAD cipher.AEAD
peerNonce []byte
PeerCache []byte
}
func (i *ClientInstance) Init(nfsEKeyBytes, xorPKeyBytes []byte, xorMode, minutes uint32) (err error) {
if i.nfsEKey != nil {
err = errors.New("already initialized")
return
}
if i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes); err != nil {
return
}
hash32 := sha3.Sum256(nfsEKeyBytes)
copy(i.hash11[:], hash32[:])
if xorMode > 0 {
i.xorMode = xorMode
if i.xorPKey, err = ecdh.X25519().NewPublicKey(xorPKeyBytes); err != nil {
return
}
}
i.minutes = time.Duration(minutes) * time.Minute
return
}
func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) {
if i.nfsEKey == nil {
return nil, errors.New("uninitialized")
}
if i.xorMode > 0 {
conn, _ = NewXorConn(conn, i.xorMode, i.xorPKey, nil)
}
c := &ClientConn{Conn: conn}
if i.minutes > 0 {
i.RLock()
if time.Now().Before(i.expire) {
c.instance = i
c.baseKey = i.baseKey
c.ticket = i.ticket
i.RUnlock()
return c, nil
}
i.RUnlock()
}
pfsDKeySeed := make([]byte, 64)
rand.Read(pfsDKeySeed)
pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
nfsAEAD := NewAEAD(ClientCipher, nfsKey, pfsEKeyBytes, encapsulatedNfsKey)
clientHello := make([]byte, 5+11+1+1184+1088+crypto.RandBetween(100, 1000))
EncodeHeader(clientHello, 1, 11+1+1184+1088)
copy(clientHello[5:], i.hash11[:])
clientHello[5+11] = ClientCipher
copy(clientHello[5+11+1:], pfsEKeyBytes)
copy(clientHello[5+11+1+1184:], encapsulatedNfsKey)
padding := clientHello[5+11+1+1184+1088:]
rand.Read(padding) // important
EncodeHeader(padding, 23, len(padding)-5)
nfsAEAD.Seal(padding[:5], clientHello[5:5+11+1], padding[5:len(padding)-16], padding[:5])
if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err
}
// client can send more NFS AEAD paddings / messages if needed
_, t, l, err := ReadAndDiscardPaddings(c.Conn) // allow paddings before server hello
if err != nil {
return nil, err
}
if t != 1 {
return nil, errors.New("unexpected type ", t, ", expect server hello")
}
peerServerHello := make([]byte, 1088+21)
if l != len(peerServerHello) {
return nil, errors.New("unexpected length ", l, " for server hello")
}
if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil {
return nil, err
}
encapsulatedPfsKey := peerServerHello[:1088]
c.ticket = append(i.hash11[:], peerServerHello[1088:]...)
pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
if err != nil {
return nil, err
}
c.baseKey = append(pfsKey, nfsKey...)
VLESS, _ := NewAEAD(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes)
if !bytes.Equal(VLESS, []byte("VLESS")) {
return nil, errors.New("invalid server").AtError()
}
if i.minutes > 0 {
i.Lock()
i.expire = time.Now().Add(i.minutes)
i.baseKey = c.baseKey
i.ticket = c.ticket
i.Unlock()
}
return c, nil
}
func (c *ClientConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
var data []byte
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in server's Read()
}
n += len(b)
if c.aead == nil {
data = make([]byte, 5+32+32+5+len(b)+16)
EncodeHeader(data, 0, 32+32)
copy(data[5:], c.ticket)
c.random = make([]byte, 32)
rand.Read(c.random)
copy(data[5+32:], c.random)
EncodeHeader(data[5+32+32:], 23, len(b)+16)
c.aead = NewAEAD(ClientCipher, c.baseKey, c.random, c.ticket)
c.nonce = make([]byte, 12)
c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, 23, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAEAD(ClientCipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
}
return len(b), nil
}
func (c *ClientConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
if c.peerAEAD == nil {
_, t, l, err := ReadAndDiscardPaddings(c.Conn) // allow paddings before random hello
if err != nil {
if c.instance != nil && strings.HasPrefix(err.Error(), "invalid header: ") { // 0-RTT's 0-RTT
c.instance.Lock()
if bytes.Equal(c.ticket, c.instance.ticket) {
c.instance.expire = time.Now() // expired
}
c.instance.Unlock()
return 0, errors.New("new handshake needed")
}
return 0, err
}
if t != 0 {
return 0, errors.New("unexpected type ", t, ", expect random hello")
}
peerRandomHello := make([]byte, 32)
if l != len(peerRandomHello) {
return 0, errors.New("unexpected length ", l, " for random hello")
}
if _, err := io.ReadFull(c.Conn, peerRandomHello); err != nil {
return 0, err
}
if c.random == nil {
return 0, errors.New("empty c.random")
}
c.peerAEAD = NewAEAD(ClientCipher, c.baseKey, peerRandomHello, c.random)
c.peerNonce = make([]byte, 12)
}
if len(c.PeerCache) != 0 {
n := copy(b, c.PeerCache)
c.PeerCache = c.PeerCache[n:]
return n, nil
}
h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000
if err != nil {
return 0, err
}
if t != 23 {
return 0, errors.New("unexpected type ", t, ", expect encrypted data")
}
peerData := make([]byte, l)
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err
}
dst := peerData[:l-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // avoids another copy()
}
var peerAEAD cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAEAD = NewAEAD(ClientCipher, c.baseKey, peerData, h)
}
_, err = c.peerAEAD.Open(dst[:0], c.peerNonce, peerData, h)
if peerAEAD != nil {
c.peerAEAD = peerAEAD
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, err
}
if len(dst) > len(b) {
c.PeerCache = dst[copy(b, dst):]
dst = b // for len(dst)
}
return len(dst), nil
}

View File

@@ -0,0 +1,93 @@
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hkdf"
"crypto/sha3"
"fmt"
"io"
"net"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/crypto/chacha20poly1305"
)
var MaxNonce = bytes.Repeat([]byte{255}, 12)
func EncodeHeader(h []byte, t byte, l int) {
switch t {
case 1:
h[0] = 1
h[1] = 1
h[2] = 1
case 0:
h[0] = 0
h[1] = 0
h[2] = 0
case 23:
h[0] = 23
h[1] = 3
h[2] = 3
}
h[3] = byte(l >> 8)
h[4] = byte(l)
}
func DecodeHeader(h []byte) (t byte, l int, err error) {
l = int(h[3])<<8 | int(h[4])
if h[0] == 23 && h[1] == 3 && h[2] == 3 {
t = 23
} else if h[0] == 0 && h[1] == 0 && h[2] == 0 {
t = 0
} else if h[0] == 1 && h[1] == 1 && h[2] == 1 {
t = 1
} else {
l = 0
}
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
err = errors.New("invalid header: ", fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read()
}
return
}
func ReadAndDecodeHeader(conn net.Conn) (h []byte, t byte, l int, err error) {
h = make([]byte, 5)
if _, err = io.ReadFull(conn, h); err != nil {
return
}
t, l, err = DecodeHeader(h)
return
}
func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error) {
for {
if h, t, l, err = ReadAndDecodeHeader(conn); err != nil || t != 23 {
return
}
if _, err = io.ReadFull(conn, make([]byte, l)); err != nil {
return
}
}
}
func NewAEAD(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
key, _ := hkdf.Key(sha3.New256, secret, salt, string(info), 32)
if c&1 == 1 {
block, _ := aes.NewCipher(key)
aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(key)
}
return
}
func IncreaseNonce(nonce []byte) {
for i := range 12 {
nonce[11-i]++
if nonce[11-i] != 0 {
break
}
}
}

View File

@@ -0,0 +1,310 @@
package encryption
import (
"bytes"
"crypto/cipher"
"crypto/ecdh"
"crypto/mlkem"
"crypto/rand"
"crypto/sha3"
"fmt"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
)
type ServerSession struct {
expire time.Time
cipher byte
baseKey []byte
randoms sync.Map
}
type ServerInstance struct {
sync.RWMutex
nfsDKey *mlkem.DecapsulationKey768
hash11 [11]byte // no more capacity
xorMode uint32
xorSKey *ecdh.PrivateKey
minutes time.Duration
sessions map[[32]byte]*ServerSession
closed bool
}
type ServerConn struct {
net.Conn
cipher byte
baseKey []byte
ticket []byte
peerRandom []byte
peerAEAD cipher.AEAD
peerNonce []byte
PeerCache []byte
aead cipher.AEAD
nonce []byte
}
func (i *ServerInstance) Init(nfsDKeySeed, xorSKeyBytes []byte, xorMode, minutes uint32) (err error) {
if i.nfsDKey != nil {
err = errors.New("already initialized")
return
}
if i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed); err != nil {
return
}
hash32 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes())
copy(i.hash11[:], hash32[:])
if xorMode > 0 {
i.xorMode = xorMode
if i.xorSKey, err = ecdh.X25519().NewPrivateKey(xorSKeyBytes); err != nil {
return
}
}
if minutes > 0 {
i.minutes = time.Duration(minutes) * time.Minute
i.sessions = make(map[[32]byte]*ServerSession)
go func() {
for {
time.Sleep(time.Minute)
i.Lock()
if i.closed {
i.Unlock()
return
}
now := time.Now()
for ticket, session := range i.sessions {
if now.After(session.expire) {
delete(i.sessions, ticket)
}
}
i.Unlock()
}
}()
}
return
}
func (i *ServerInstance) Close() (err error) {
i.Lock()
i.closed = true
i.Unlock()
return
}
func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) {
if i.nfsDKey == nil {
return nil, errors.New("uninitialized")
}
if i.xorMode > 0 {
var err error
if conn, err = NewXorConn(conn, i.xorMode, nil, i.xorSKey); err != nil {
return nil, err
}
}
c := &ServerConn{Conn: conn}
_, t, l, err := ReadAndDiscardPaddings(c.Conn) // allow paddings before client/ticket hello
if err != nil {
return nil, err
}
if t == 0 {
if i.minutes == 0 {
return nil, errors.New("0-RTT is not allowed")
}
peerTicketHello := make([]byte, 32+32)
if l != len(peerTicketHello) {
return nil, errors.New("unexpected length ", l, " for ticket hello")
}
if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil {
return nil, err
}
if !bytes.Equal(peerTicketHello[:11], i.hash11[:]) {
return nil, errors.New("unexpected hash11: ", fmt.Sprintf("%v", peerTicketHello[:11]))
}
i.RLock()
s := i.sessions[[32]byte(peerTicketHello)]
i.RUnlock()
if s == nil {
noises := make([]byte, crypto.RandBetween(100, 1000))
var err error
for err == nil {
rand.Read(noises)
_, _, err = DecodeHeader(noises)
}
c.Conn.Write(noises) // make client do new handshake
return nil, errors.New("expired ticket")
}
if _, replay := s.randoms.LoadOrStore([32]byte(peerTicketHello[32:]), true); replay {
return nil, errors.New("replay detected")
}
c.cipher = s.cipher
c.baseKey = s.baseKey
c.ticket = peerTicketHello[:32]
c.peerRandom = peerTicketHello[32:]
return c, nil
}
peerClientHello := make([]byte, 11+1+1184+1088)
if l != len(peerClientHello) {
return nil, errors.New("unexpected length ", l, " for client hello")
}
if _, err := io.ReadFull(c.Conn, peerClientHello); err != nil {
return nil, err
}
if !bytes.Equal(peerClientHello[:11], i.hash11[:]) {
return nil, errors.New("unexpected hash11: ", fmt.Sprintf("%v", peerClientHello[:11]))
}
c.cipher = peerClientHello[11]
pfsEKeyBytes := peerClientHello[11+1 : 11+1+1184]
encapsulatedNfsKey := peerClientHello[11+1+1184:]
pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
if err != nil {
return nil, err
}
nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
if err != nil {
return nil, err
}
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
c.baseKey = append(pfsKey, nfsKey...)
pfsAEAD := NewAEAD(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey)
c.ticket = append(i.hash11[:], pfsAEAD.Seal(nil, peerClientHello[:11+1], []byte("VLESS"), pfsEKeyBytes)...)
IncreaseNonce(peerClientHello[:11+1])
serverHello := make([]byte, 5+1088+21+crypto.RandBetween(100, 1000))
EncodeHeader(serverHello, 1, 1088+21)
copy(serverHello[5:], encapsulatedPfsKey)
copy(serverHello[5+1088:], c.ticket[11:])
padding := serverHello[5+1088+21:]
rand.Read(padding) // important
EncodeHeader(padding, 23, len(padding)-5)
pfsAEAD.Seal(padding[:5], peerClientHello[:11+1], padding[5:len(padding)-16], padding[:5])
if _, err := c.Conn.Write(serverHello); err != nil {
return nil, err
}
// server can send more PFS AEAD paddings / messages if needed
if i.minutes > 0 {
i.Lock()
i.sessions[[32]byte(c.ticket)] = &ServerSession{
expire: time.Now().Add(i.minutes),
cipher: c.cipher,
baseKey: c.baseKey,
}
i.Unlock()
}
return c, nil
}
func (c *ServerConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
if c.peerAEAD == nil {
if c.peerRandom == nil { // 1-RTT's 0-RTT
_, t, l, err := ReadAndDiscardPaddings(c.Conn) // allow paddings before ticket hello
if err != nil {
return 0, err
}
if t != 0 {
return 0, errors.New("unexpected type ", t, ", expect ticket hello")
}
peerTicketHello := make([]byte, 32+32)
if l != len(peerTicketHello) {
return 0, errors.New("unexpected length ", l, " for ticket hello")
}
if _, err := io.ReadFull(c.Conn, peerTicketHello); err != nil {
return 0, err
}
if !bytes.Equal(peerTicketHello[:32], c.ticket) {
return 0, errors.New("naughty boy")
}
c.peerRandom = peerTicketHello[32:]
}
c.peerAEAD = NewAEAD(c.cipher, c.baseKey, c.peerRandom, c.ticket)
c.peerNonce = make([]byte, 12)
}
if len(c.PeerCache) != 0 {
n := copy(b, c.PeerCache)
c.PeerCache = c.PeerCache[n:]
return n, nil
}
h, t, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000
if err != nil {
return 0, err
}
if t != 23 {
return 0, errors.New("unexpected type ", t, ", expect encrypted data")
}
peerData := make([]byte, l)
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err
}
dst := peerData[:l-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // avoids another copy()
}
var peerAEAD cipher.AEAD
if bytes.Equal(c.peerNonce, MaxNonce) {
peerAEAD = NewAEAD(c.cipher, c.baseKey, peerData, h)
}
_, err = c.peerAEAD.Open(dst[:0], c.peerNonce, peerData, h)
if peerAEAD != nil {
c.peerAEAD = peerAEAD
}
IncreaseNonce(c.peerNonce)
if err != nil {
return 0, err
}
if len(dst) > len(b) {
c.PeerCache = dst[copy(b, dst):]
dst = b // for len(dst)
}
return len(dst), nil
}
func (c *ServerConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
var data []byte
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in client's Read()
}
n += len(b)
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("empty c.peerRandom")
}
data = make([]byte, 5+32+5+len(b)+16)
EncodeHeader(data, 0, 32)
rand.Read(data[5 : 5+32])
EncodeHeader(data[5+32:], 23, len(b)+16)
c.aead = NewAEAD(c.cipher, c.baseKey, data[5:5+32], c.peerRandom)
c.nonce = make([]byte, 12)
c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5])
} else {
data = make([]byte, 5+len(b)+16)
EncodeHeader(data, 23, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
if bytes.Equal(c.nonce, MaxNonce) {
c.aead = NewAEAD(c.cipher, c.baseKey, data[5:], data[:5])
}
}
IncreaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
}
return len(b), nil
}

View File

@@ -0,0 +1,200 @@
package encryption
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/hkdf"
"crypto/rand"
"crypto/sha3"
"io"
"net"
"github.com/xtls/xray-core/common/errors"
)
type XorConn struct {
net.Conn
Divide bool
head []byte
key []byte
ctr cipher.Stream
peerCtr cipher.Stream
isHeader bool
skipNext bool
out_after0 bool
out_header []byte
out_skip int
in_after0 bool
in_header []byte
in_skip int
}
func NewCTR(key, iv []byte, isServer bool) cipher.Stream {
info := "CLIENT"
if isServer {
info = "SERVER" // avoids attackers sending traffic back to the client, though the encryption layer has its own protection
}
key, _ = hkdf.Key(sha3.New256, key, iv, info, 32) // avoids using pKey directly if attackers sent the basepoint, or whaterver they like
block, _ := aes.NewCipher(key)
return cipher.NewCTR(block, iv)
}
func NewXorConn(conn net.Conn, mode uint32, pKey *ecdh.PublicKey, sKey *ecdh.PrivateKey) (*XorConn, error) {
if mode == 0 || (pKey == nil && sKey == nil) || (pKey != nil && sKey != nil) {
return nil, errors.New("invalid parameters")
}
c := &XorConn{
Conn: conn,
Divide: mode == 1,
isHeader: true,
out_header: make([]byte, 0, 5), // important
in_header: make([]byte, 0, 5), // important
}
if pKey != nil {
c.head = make([]byte, 16+32)
rand.Read(c.head)
eSKey, _ := ecdh.X25519().NewPrivateKey(c.head[16:])
NewCTR(pKey.Bytes(), c.head[:16], false).XORKeyStream(c.head[16:], eSKey.PublicKey().Bytes()) // make X25519 public key distinguishable from random bytes
c.key, _ = eSKey.ECDH(pKey)
c.ctr = NewCTR(c.key, c.head[:16], false)
}
if sKey != nil {
peerHead := make([]byte, 16+32)
if _, err := io.ReadFull(c.Conn, peerHead); err != nil {
return nil, err
}
NewCTR(sKey.PublicKey().Bytes(), peerHead[:16], false).XORKeyStream(peerHead[16:], peerHead[16:]) // we don't use buggy elligator, because we have PSK :)
ePKey, err := ecdh.X25519().NewPublicKey(peerHead[16:])
if err != nil {
return nil, err
}
key, err := sKey.ECDH(ePKey)
if err != nil {
return nil, err
}
c.peerCtr = NewCTR(key, peerHead[:16], false)
c.head = make([]byte, 16)
rand.Read(c.head) // make sure the server always replies random bytes even when received replays, though it is not important
c.ctr = NewCTR(key, c.head, true) // the same key links the upload & download, though the encryption layer has its own link
}
return c, nil
//chacha20.NewUnauthenticatedCipher()
}
func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records
if len(b) == 0 {
return 0, nil
}
if !c.out_after0 {
t, l, _ := DecodeHeader(b)
if t == 23 { // single 23
l = 5
} else { // 1/0 + 23, or noises only
l += 10
if t == 0 {
c.out_after0 = true
if c.Divide {
l -= 5
}
}
}
c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b
l = len(b)
if c.head != nil {
b = append(c.head, b...)
c.head = nil
}
if _, err := c.Conn.Write(b); err != nil {
return 0, err
}
return l, nil
}
if c.Divide {
return c.Conn.Write(b)
}
for p := b; ; { // for XTLS
if len(p) <= c.out_skip {
c.out_skip -= len(p)
break
}
p = p[c.out_skip:]
c.out_skip = 0
need := 5 - len(c.out_header)
if len(p) < need {
c.out_header = append(c.out_header, p...)
c.ctr.XORKeyStream(p, p)
break
}
_, c.out_skip, _ = DecodeHeader(append(c.out_header, p[:need]...))
c.out_header = c.out_header[:0]
c.ctr.XORKeyStream(p[:need], p[:need])
p = p[need:]
}
if _, err := c.Conn.Write(b); err != nil {
return 0, err
}
return len(b), nil
}
func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes...
if len(b) == 0 {
return 0, nil
}
if !c.in_after0 || !c.isHeader {
if c.peerCtr == nil { // for client
peerIv := make([]byte, 16)
if _, err := io.ReadFull(c.Conn, peerIv); err != nil {
return 0, err
}
c.peerCtr = NewCTR(c.key, peerIv, true)
}
if _, err := io.ReadFull(c.Conn, b); err != nil {
return 0, err
}
if c.skipNext {
c.skipNext = false
return len(b), nil
}
c.peerCtr.XORKeyStream(b, b)
if c.isHeader { // always 5-bytes
if t, _, _ := DecodeHeader(b); t == 23 {
c.skipNext = true
} else {
c.isHeader = false
if t == 0 {
c.in_after0 = true
}
}
} else {
c.isHeader = true
}
return len(b), nil
}
if c.Divide {
return c.Conn.Read(b)
}
n, err := c.Conn.Read(b)
for p := b[:n]; ; { // for XTLS
if len(p) <= c.in_skip {
c.in_skip -= len(p)
break
}
p = p[c.in_skip:]
c.in_skip = 0
need := 5 - len(c.in_header)
if len(p) < need {
c.peerCtr.XORKeyStream(p, p)
c.in_header = append(c.in_header, p...)
break
}
c.peerCtr.XORKeyStream(p[:need], p[:need])
_, c.in_skip, _ = DecodeHeader(append(c.in_header, p[:need]...))
c.in_header = c.in_header[:0]
p = p[need:]
}
return n, err
}

View File

@@ -111,11 +111,11 @@ type Config struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
// Decryption settings. Only applies to server side, and only accepts "none"
// for now.
Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
Minutes uint32 `protobuf:"varint,5,opt,name=minutes,proto3" json:"minutes,omitempty"`
}
func (x *Config) Reset() {
@@ -155,6 +155,13 @@ func (x *Config) GetClients() []*protocol.User {
return nil
}
func (x *Config) GetFallbacks() []*Fallback {
if x != nil {
return x.Fallbacks
}
return nil
}
func (x *Config) GetDecryption() string {
if x != nil {
return x.Decryption
@@ -162,11 +169,18 @@ func (x *Config) GetDecryption() string {
return ""
}
func (x *Config) GetFallbacks() []*Fallback {
func (x *Config) GetXorMode() uint32 {
if x != nil {
return x.Fallbacks
return x.XorMode
}
return nil
return 0
}
func (x *Config) GetMinutes() uint32 {
if x != nil {
return x.Minutes
}
return 0
}
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
@@ -185,25 +199,28 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xd4, 0x01,
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e,
0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40,
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40,
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c,
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73,
0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f,
0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56,
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69,
0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x69, 0x6e,
0x75, 0x74, 0x65, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62,
0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f,
0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@@ -19,8 +19,9 @@ message Fallback {
message Config {
repeated xray.common.protocol.User clients = 1;
// Decryption settings. Only applies to server side, and only accepts "none"
// for now.
string decryption = 2;
repeated Fallback fallbacks = 3;
repeated Fallback fallbacks = 2;
string decryption = 3;
uint32 xorMode = 4;
uint32 minutes = 5;
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
gotls "crypto/tls"
"encoding/base64"
"io"
"reflect"
"strconv"
@@ -29,6 +30,7 @@ import (
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -67,6 +69,7 @@ type Handler struct {
policyManager policy.Manager
validator vless.Validator
dns dns.Client
decryption *encryption.ServerInstance
fallbacks map[string]map[string]map[string]*Fallback // or nil
// regexps map[string]*regexp.Regexp // or nil
}
@@ -81,6 +84,15 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
validator: validator,
}
if s := strings.Split(config.Decryption, "."); len(s) == 2 {
nfsDKeySeed, _ := base64.RawURLEncoding.DecodeString(s[0])
xorSKeyBytes, _ := base64.RawURLEncoding.DecodeString(s[1])
handler.decryption = &encryption.ServerInstance{}
if err := handler.decryption.Init(nfsDKeySeed, xorSKeyBytes, config.XorMode, config.Minutes); err != nil {
return nil, errors.New("failed to use mlkem768seed").Base(err).AtError()
}
}
if config.Fallbacks != nil {
handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
// handler.regexps = make(map[string]*regexp.Regexp)
@@ -159,6 +171,9 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool {
// Close implements common.Closable.Close().
func (h *Handler) Close() error {
if h.decryption != nil {
h.decryption.Close()
}
return errors.Combine(common.Close(h.validator))
}
@@ -199,6 +214,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
iConn = statConn.Connection
}
if h.decryption != nil {
var err error
connection, err = h.decryption.Handshake(connection)
if err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
}
}
sessionPolicy := h.policyManager.ForLevel(0)
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning()
@@ -217,7 +240,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
Buffer: buf.MultiBuffer{first},
}
var userSentID []byte // not MemoryAccount.ID
var request *protocol.RequestHeader
var requestAddons *encoding.Addons
var err error
@@ -228,7 +250,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if isfb && firstLen < 18 {
err = errors.New("fallback directly")
} else {
userSentID, request, requestAddons, isfb, err = encoding.DecodeRequestHeader(isfb, first, reader, h.validator)
request, requestAddons, isfb, err = encoding.DecodeRequestHeader(isfb, first, reader, h.validator)
}
if err != nil {
@@ -456,7 +478,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
}
inbound.Name = "vless"
inbound.User = request.User
inbound.VlessRoute = net.PortFromBytes(userSentID[6:8])
account := request.User.Account.(*vless.MemoryAccount)
@@ -464,6 +485,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
// Flow: requestAddons.Flow,
}
var peerCache *[]byte
var input *bytes.Reader
var rawInput *bytes.Buffer
switch requestAddons.Flow {
@@ -476,6 +498,13 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
case protocol.RequestCommandMux:
fallthrough // we will break Mux connections that contain TCP requests
case protocol.RequestCommandTCP:
if serverConn, ok := connection.(*encryption.ServerConn); ok {
peerCache = &serverConn.PeerCache
if xorConn, ok := serverConn.Conn.(*encryption.XorConn); (ok && !xorConn.Divide) || !proxy.IsRAWTransport(iConn) {
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
}
break
}
var t reflect.Type
var p uintptr
if tlsConn, ok := iConn.(*tls.Conn); ok {
@@ -532,7 +561,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
serverReader := link.Reader // .(*pipe.Reader)
serverWriter := link.Writer // .(*pipe.Writer)
trafficState := proxy.NewTrafficState(userSentID)
trafficState := proxy.NewTrafficState(account.ID.Bytes())
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
@@ -544,7 +573,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if requestAddons.Flow == vless.XRV {
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1)
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1)
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1)
} else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))

View File

@@ -4,7 +4,9 @@ import (
"bytes"
"context"
gotls "crypto/tls"
"encoding/base64"
"reflect"
"strings"
"time"
"unsafe"
@@ -24,6 +26,7 @@ import (
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/reality"
@@ -43,6 +46,7 @@ type Handler struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
cone bool
encryption *encryption.ClientInstance
}
// New creates a new VLess outbound handler.
@@ -64,6 +68,16 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
cone: ctx.Value("cone").(bool),
}
a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount)
if s := strings.Split(a.Encryption, "."); len(s) == 2 {
nfsEKeyBytes, _ := base64.RawURLEncoding.DecodeString(s[0])
xorPKeyBytes, _ := base64.RawURLEncoding.DecodeString(s[1])
handler.encryption = &encryption.ClientInstance{}
if err := handler.encryption.Init(nfsEKeyBytes, xorPKeyBytes, a.XorMode, a.Minutes); err != nil {
return nil, errors.New("failed to use mlkem768client").Base(err).AtError()
}
}
return handler, nil
}
@@ -98,6 +112,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
target := ob.Target
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
if h.encryption != nil {
var err error
conn, err = h.encryption.Handshake(conn)
if err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
}
}
command := protocol.RequestCommandTCP
if target.Network == net.Network_UDP {
command = protocol.RequestCommandUDP
@@ -120,6 +142,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
Flow: account.Flow,
}
var peerCache *[]byte
var input *bytes.Reader
var rawInput *bytes.Buffer
allowUDP443 := false
@@ -138,6 +161,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
case protocol.RequestCommandMux:
fallthrough // let server break Mux connections that contain TCP requests
case protocol.RequestCommandTCP:
if clientConn, ok := conn.(*encryption.ClientConn); ok {
peerCache = &clientConn.PeerCache
if xorConn, ok := clientConn.Conn.(*encryption.XorConn); (ok && !xorConn.Divide) || !proxy.IsRAWTransport(iConn) {
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
}
break
}
var t reflect.Type
var p uintptr
if tlsConn, ok := iConn.(*tls.Conn); ok {
@@ -272,7 +302,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
}
if requestAddons.Flow == vless.XRV {
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx)
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, peerCache, input, rawInput, trafficState, ob, false, ctx)
} else {
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))

View File

@@ -18,12 +18,6 @@ type Validator interface {
GetCount() int64
}
func ProcessUUID(id [16]byte) [16]byte {
id[6] = 0
id[7] = 0
return id
}
// MemoryValidator stores valid VLESS users.
type MemoryValidator struct {
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
@@ -39,7 +33,7 @@ func (v *MemoryValidator) Add(u *protocol.MemoryUser) error {
return errors.New("User ", u.Email, " already exists.")
}
}
v.users.Store(ProcessUUID(u.Account.(*MemoryAccount).ID.UUID()), u)
v.users.Store(u.Account.(*MemoryAccount).ID.UUID(), u)
return nil
}
@@ -54,13 +48,13 @@ func (v *MemoryValidator) Del(e string) error {
return errors.New("User ", e, " not found.")
}
v.email.Delete(le)
v.users.Delete(ProcessUUID(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID()))
v.users.Delete(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID())
return nil
}
// Get a VLESS user with UUID, nil if user doesn't exist.
func (v *MemoryValidator) Get(id uuid.UUID) *protocol.MemoryUser {
u, _ := v.users.Load(ProcessUUID(id))
u, _ := v.users.Load(id)
if u != nil {
return u.(*protocol.MemoryUser)
}

View File

@@ -6,6 +6,5 @@
package vless
const (
None = "none"
XRV = "xtls-rprx-vision"
XRV = "xtls-rprx-vision"
)

View File

@@ -2,13 +2,14 @@ package aead
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"io"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
)
func SealVMessAEADHeader(key [16]byte, data []byte) []byte {
@@ -33,7 +34,15 @@ func SealVMessAEADHeader(key [16]byte, data []byte) []byte {
payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
payloadHeaderAEAD := crypto.NewAesGcm(payloadHeaderLengthAEADKey)
payloadHeaderLengthAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderLengthAEADAESBlock)
if err != nil {
panic(err.Error())
}
payloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:])
}
@@ -45,7 +54,15 @@ func SealVMessAEADHeader(key [16]byte, data []byte) []byte {
payloadHeaderAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]
payloadHeaderAEAD := crypto.NewAesGcm(payloadHeaderAEADKey)
payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock)
if err != nil {
panic(err.Error())
}
payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:])
}
@@ -87,7 +104,15 @@ func OpenVMessAEADHeader(key [16]byte, authid [16]byte, data io.Reader) ([]byte,
payloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, string(authid[:]), string(nonce[:]))[:12]
payloadHeaderLengthAEAD := crypto.NewAesGcm(payloadHeaderLengthAEADKey)
payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderLengthAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderLengthAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock)
if err != nil {
panic(err.Error())
}
decryptedAEADHeaderLengthPayload, erropenAEAD := payloadHeaderLengthAEAD.Open(nil, payloadHeaderLengthAEADNonce, payloadHeaderLengthAEADEncrypted[:], authid[:])
@@ -120,7 +145,15 @@ func OpenVMessAEADHeader(key [16]byte, authid [16]byte, data io.Reader) ([]byte,
return nil, false, bytesRead, err
}
payloadHeaderAEAD := crypto.NewAesGcm(payloadHeaderAEADKey)
payloadHeaderAEADAESBlock, err := aes.NewCipher(payloadHeaderAEADKey)
if err != nil {
panic(err.Error())
}
payloadHeaderAEAD, err := cipher.NewGCM(payloadHeaderAEADAESBlock)
if err != nil {
panic(err.Error())
}
decryptedAEADHeaderPayload, erropenAEAD := payloadHeaderAEAD.Open(nil, payloadHeaderAEADNonce, payloadHeaderAEADEncrypted, authid[:])

View File

@@ -3,6 +3,8 @@ package encoding
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
@@ -180,7 +182,8 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon
aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)
aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]
aeadResponseHeaderLengthEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderLengthEncryptionKey)
aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)
var aeadEncryptedResponseHeaderLength [18]byte
var decryptedResponseHeaderLength int
@@ -202,7 +205,8 @@ func (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon
aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)
aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]
aeadResponseHeaderPayloadEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderPayloadEncryptionKey)
aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)
encryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)

View File

@@ -2,6 +2,8 @@ package encoding
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/binary"
"hash/fnv"
@@ -348,7 +350,8 @@ func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr
aeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)
aeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]
aeadResponseHeaderLengthEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderLengthEncryptionKey)
aeadResponseHeaderLengthEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderLengthEncryptionKey)).(cipher.Block)
aeadResponseHeaderLengthEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderLengthEncryptionKeyAESBlock)).(cipher.AEAD)
aeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil)
@@ -362,7 +365,8 @@ func (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr
aeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)
aeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]
aeadResponseHeaderPayloadEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderPayloadEncryptionKey)
aeadResponseHeaderPayloadEncryptionKeyAESBlock := common.Must2(aes.NewCipher(aeadResponseHeaderPayloadEncryptionKey)).(cipher.Block)
aeadResponseHeaderPayloadEncryptionAEAD := common.Must2(cipher.NewGCM(aeadResponseHeaderPayloadEncryptionKeyAESBlock)).(cipher.AEAD)
aeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil)
common.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload)))

View File

@@ -7,7 +7,6 @@ import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
@@ -34,6 +33,7 @@ type routingInfo struct {
ctx context.Context
dispatcher routing.Dispatcher
inboundTag *session.Inbound
outboundTag *session.Outbound
contentTag *session.Content
}
@@ -78,11 +78,18 @@ func (*Server) Network() []net.Network {
// Process implements proxy.Inbound.
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
inbound := session.InboundFromContext(ctx)
inbound.Name = "wireguard"
inbound.CanSpliceCopy = 3
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
s.info = routingInfo{
ctx: ctx,
dispatcher: dispatcher,
inboundTag: session.InboundFromContext(ctx),
contentTag: session.ContentFromContext(ctx),
ctx: core.ToBackgroundDetachedContext(ctx),
dispatcher: dispatcher,
inboundTag: session.InboundFromContext(ctx),
outboundTag: ob,
contentTag: session.ContentFromContext(ctx),
}
ep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String())
@@ -127,25 +134,6 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
defer conn.Close()
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))
sid := session.NewID()
ctx = c.ContextWithID(ctx, sid)
inbound := session.Inbound{} // since promiscuousModeHandler mixed-up context, we shallow copy inbound (tag) and content (configs)
if s.info.inboundTag != nil {
inbound = *s.info.inboundTag
}
inbound.Name = "wireguard"
inbound.CanSpliceCopy = 3
// overwrite the source to use the tun address for each sub context.
// Since gvisor.ForwarderRequest doesn't provide any info to associate the sub-context with the Parent context
// Currently we have no way to link to the original source address
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
ctx = session.ContextWithInbound(ctx, &inbound)
if s.info.contentTag != nil {
ctx = session.ContextWithContent(ctx, s.info.contentTag)
}
ctx = session.SubContextFromMuxInbound(ctx)
plcy := s.policyManager.ForLevel(0)
timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
@@ -156,9 +144,25 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
Reason: "",
})
if s.info.inboundTag != nil {
ctx = session.ContextWithInbound(ctx, s.info.inboundTag)
}
// what's this?
// Session information should not be shared between different connections
// why reuse them in server level? This will cause incorrect destoverride and unexpected routing behavior.
// Disable it temporarily. Maybe s.info should be removed.
// if s.info.outboundTag != nil {
// ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{s.info.outboundTag})
// }
// if s.info.contentTag != nil {
// ctx = session.ContextWithContent(ctx, s.info.contentTag)
// }
link, err := s.info.dispatcher.Dispatch(ctx, dest)
if err != nil {
errors.LogErrorInner(ctx, err, "dispatch connection")
errors.LogErrorInner(s.info.ctx, err, "dispatch connection")
}
defer cancel()
@@ -184,7 +188,7 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
errors.LogDebugInner(ctx, err, "connection ends")
errors.LogDebugInner(s.info.ctx, err, "connection ends")
return
}
}

View File

@@ -100,30 +100,30 @@ func (m SocketConfig_TProxyMode) IsEnabled() bool {
return m != SocketConfig_Off
}
func (s DomainStrategy) HasStrategy() bool {
func (s DomainStrategy) hasStrategy() bool {
return strategy[s][0] != 0
}
func (s DomainStrategy) ForceIP() bool {
func (s DomainStrategy) forceIP() bool {
return strategy[s][0] == 2
}
func (s DomainStrategy) PreferIP4() bool {
func (s DomainStrategy) preferIP4() bool {
return strategy[s][1] == 4 || strategy[s][1] == 0
}
func (s DomainStrategy) PreferIP6() bool {
func (s DomainStrategy) preferIP6() bool {
return strategy[s][1] == 6 || strategy[s][1] == 0
}
func (s DomainStrategy) HasFallback() bool {
func (s DomainStrategy) hasFallback() bool {
return strategy[s][2] != 0
}
func (s DomainStrategy) FallbackIP4() bool {
func (s DomainStrategy) fallbackIP4() bool {
return strategy[s][2] == 4
}
func (s DomainStrategy) FallbackIP6() bool {
func (s DomainStrategy) fallbackIP6() bool {
return strategy[s][2] == 6
}

View File

@@ -85,20 +85,20 @@ var (
obm outbound.Manager
)
func LookupForIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {
func lookupIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {
if dnsClient == nil {
return nil, errors.New("DNS client not initialized").AtError()
}
ips, _, err := dnsClient.LookupIP(domain, dns.IPOption{
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && strategy.PreferIP4(),
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && strategy.PreferIP6(),
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && strategy.preferIP4(),
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && strategy.preferIP6(),
})
{ // Resolve fallback
if (len(ips) == 0 || err != nil) && strategy.HasFallback() && localAddr == nil {
if (len(ips) == 0 || err != nil) && strategy.hasFallback() && localAddr == nil {
ips, _, err = dnsClient.LookupIP(domain, dns.IPOption{
IPv4Enable: strategy.FallbackIP4(),
IPv6Enable: strategy.FallbackIP6(),
IPv4Enable: strategy.fallbackIP4(),
IPv6Enable: strategy.fallbackIP6(),
})
}
}
@@ -113,7 +113,7 @@ func canLookupIP(dst net.Destination, sockopt *SocketConfig) bool {
if dst.Address.Family().IsIP() {
return false
}
return sockopt.DomainStrategy.HasStrategy()
return sockopt.DomainStrategy.hasStrategy()
}
func redirect(ctx context.Context, dst net.Destination, obt string, h outbound.Handler) net.Conn {
@@ -249,17 +249,17 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig
}
if canLookupIP(dest, sockopt) {
ips, err := LookupForIP(dest.Address.String(), sockopt.DomainStrategy, src)
ips, err := lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
if err != nil {
errors.LogErrorInner(ctx, err, "failed to resolve ip")
if sockopt.DomainStrategy.ForceIP() {
if sockopt.DomainStrategy.forceIP() {
return nil, err
}
} else if sockopt.HappyEyeballs == nil || sockopt.HappyEyeballs.TryDelayMs == 0 || sockopt.HappyEyeballs.MaxConcurrentTry == 0 || len(ips) < 2 || len(sockopt.DialerProxy) > 0 || dest.Network != net.Network_TCP {
dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
errors.LogInfo(ctx, "replace destination with "+dest.String())
} else {
return TcpRaceDial(ctx, src, ips, dest.Port, sockopt, dest.Address.String())
return TcpRaceDial(ctx, src, ips, dest.Port, sockopt)
}
}

View File

@@ -2,7 +2,6 @@ package internet
import (
"context"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"time"
)
@@ -13,7 +12,7 @@ type result struct {
index int
}
func TcpRaceDial(ctx context.Context, src net.Address, ips []net.IP, port net.Port, sockopt *SocketConfig, domain string) (net.Conn, error) {
func TcpRaceDial(ctx context.Context, src net.Address, ips []net.IP, port net.Port, sockopt *SocketConfig) (net.Conn, error) {
if len(ips) < 2 {
panic("at least 2 ips is required to race dial")
}
@@ -31,7 +30,6 @@ func TcpRaceDial(ctx context.Context, src net.Address, ips []net.IP, port net.Po
activeNum := uint32(0)
timer := time.NewTimer(0)
var winConn net.Conn
errors.LogDebug(ctx, "happy eyeballs racing dial for ", domain, " with IPs ", ips)
for {
select {
case r := <-resultCh:
@@ -56,7 +54,6 @@ func TcpRaceDial(ctx context.Context, src net.Address, ips []net.IP, port net.Po
timer.Stop()
if winConn == nil {
winConn = r.conn
errors.LogDebug(ctx, "happy eyeballs established connection for ", domain, " with IP ", ips[r.index])
} else {
r.conn.Close()
}
@@ -72,7 +69,6 @@ func TcpRaceDial(ctx context.Context, src net.Address, ips []net.IP, port net.Po
continue
}
if activeNum == 0 {
errors.LogDebugInner(ctx, r.err, "happy eyeballs no connection established for ", domain)
return nil, r.err
}
timer.Stop()

View File

@@ -1,13 +1,15 @@
package kcp
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common"
)
func NewAEADAESGCMBasedOnSeed(seed string) cipher.AEAD {
hashedSeed := sha256.Sum256([]byte(seed))
return crypto.NewAesGcm(hashedSeed[:])
aesBlock := common.Must2(aes.NewCipher(hashedSeed[:16])).(cipher.Block)
return common.Must2(cipher.NewGCM(aesBlock)).(cipher.AEAD)
}

View File

@@ -3,6 +3,8 @@ package reality
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/ed25519"
"crypto/hmac"
@@ -167,7 +169,8 @@ func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destinati
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
return nil, err
}
aead := crypto.NewAesGcm(uConn.AuthKey)
block, _ := aes.NewCipher(uConn.AuthKey)
aead, _ := cipher.NewGCM(block)
if config.Show {
fmt.Printf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead)
}

View File

@@ -297,7 +297,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
if transportConfiguration.DownloadSettings != nil {
globalDialerAccess.Lock()
if streamSettings.DownloadSettings == nil {
streamSettings.DownloadSettings = common.Must2(internet.ToMemoryStreamConfig(transportConfiguration.DownloadSettings))
streamSettings.DownloadSettings = common.Must2(internet.ToMemoryStreamConfig(transportConfiguration.DownloadSettings)).(*internet.MemoryStreamConfig)
if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.Penetrate {
streamSettings.DownloadSettings.SocketSettings = streamSettings.SocketSettings
}
@@ -489,16 +489,15 @@ func (w uploadWriter) Write(b []byte) (int, error) {
}
*/
buffer := buf.MultiBufferContainer{}
common.Must2(buffer.Write(b))
var writed int
for _, buff := range buffer.MultiBuffer {
err := w.WriteMultiBuffer(buf.MultiBuffer{buff})
if err != nil {
return writed, err
}
writed += int(buff.Len())
buffer := buf.New()
n, err := buffer.Write(b)
if err != nil {
return 0, err
}
return writed, nil
err = w.WriteMultiBuffer([]*buf.Buffer{buffer})
if err != nil {
return 0, err
}
return n, nil
}

View File

@@ -1,7 +1,6 @@
package splithttp_test
import (
"bytes"
"context"
"crypto/rand"
"fmt"
@@ -422,12 +421,18 @@ func Test_maxUpload(t *testing.T) {
},
}
uploadReceived := make([]byte, 10001)
var uploadSize int
listen, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {
go func(c stat.Connection) {
defer c.Close()
var b [10240]byte
c.SetReadDeadline(time.Now().Add(2 * time.Second))
io.ReadFull(c, uploadReceived)
n, err := c.Read(b[:])
if err != nil {
return
}
uploadSize = n
common.Must2(c.Write([]byte("Response")))
}(conn)
@@ -436,12 +441,10 @@ func Test_maxUpload(t *testing.T) {
ctx := context.Background()
conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
common.Must(err)
// send a slightly too large upload
upload := make([]byte, 10001)
rand.Read(upload)
_, err = conn.Write(upload)
var upload [10001]byte
_, err = conn.Write(upload[:])
common.Must(err)
var b [10240]byte
@@ -452,8 +455,8 @@ func Test_maxUpload(t *testing.T) {
}
common.Must(conn.Close())
if !bytes.Equal(upload, uploadReceived) {
t.Error("incorrect upload", upload, uploadReceived)
if uploadSize > 10000 || uploadSize == 0 {
t.Error("incorrect upload size: ", uploadSize)
}
common.Must(listen.Close())