mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-04-16 04:25:46 +00:00
**Fixes & Changes:**
1. **Fixed XPadding Placement Dropdown**: - Added the missing `cookie` and `query` options to `xPaddingPlacement` (`stream_xhttp.html`). - *Why:* Previously, users wanting `cookie` obfuscation were forced to use the `header` placement string. This caused Xray-core to blindly intercept the entire monolithic HTTP Cookie header, failing internal padding-length validations and causing the inbound to silently drop the connection. 2. **Fixed Uplink Data Placement Validation**: - Replaced the unsupported `query` option with `cookie` in `uplinkDataPlacement`. - *Why:* Xray-core's `transport_internet.go` explicitly forbids `query` as an uplink placement option. Selecting it from the UI previously sent a payload that would cause Xray-core to instantly throw an `unsupported uplink data placement: query` panic. Adding `cookie` perfectly aligns the UI with Xray-core restrictions. ### Related Issues - Resolves #3992
This commit is contained in:
parent
1606f39a89
commit
367152556a
893 changed files with 132020 additions and 2 deletions
|
|
@ -12,5 +12,9 @@ services:
|
||||||
XRAY_VMESS_AEAD_FORCED: "false"
|
XRAY_VMESS_AEAD_FORCED: "false"
|
||||||
XUI_ENABLE_FAIL2BAN: "true"
|
XUI_ENABLE_FAIL2BAN: "true"
|
||||||
tty: true
|
tty: true
|
||||||
network_mode: host
|
# network_mode: host # Отключено, так как на Windows 11/WSL эта опция не пробрасывает порты на localhost
|
||||||
|
ports:
|
||||||
|
- "2053:2053" # Веб-панель
|
||||||
|
- "2054:2054" # Доп. порт панели/API
|
||||||
|
- "2096:2096" # Порт подписки (Subscription port)
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
||||||
78
subproject/Xray-core-main/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
78
subproject/Xray-core-main/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
name: Bug report
|
||||||
|
description: "Submit Xray-core bug"
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Integrity requirements
|
||||||
|
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.
|
||||||
|
required: true
|
||||||
|
- label: I searched issues and did not find any similar issues.
|
||||||
|
required: true
|
||||||
|
- label: The problem can be successfully reproduced in the latest Release
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: |-
|
||||||
|
Please provide a detailed description of the error. And the information you think valuable.
|
||||||
|
If the problem occurs after the update, please provide the **specific** version
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Reproduction Method
|
||||||
|
description: |-
|
||||||
|
Based on the configuration you provided below, provide the method to reproduce the bug.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
## Configuration and Log Section
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
Restart Xray-core, then operate according to the reproduction method, try to reduce the irrelevant part in the log.
|
||||||
|
Remember to delete parts with personal information (such as UUID and IP).
|
||||||
|
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>
|
||||||
|
```
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Client config
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Server config
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Client log
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Server log
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
78
subproject/Xray-core-main/.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
Normal file
78
subproject/Xray-core-main/.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
name: bug反馈
|
||||||
|
description: "提交 Xray-core bug"
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 完整性要求
|
||||||
|
description: |-
|
||||||
|
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
|
||||||
|
options:
|
||||||
|
- label: 我读完了 issue 模板中的所有注释,确保填写符合要求。
|
||||||
|
required: true
|
||||||
|
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
|
||||||
|
required: true
|
||||||
|
- label: 我提供了完整的配置文件和日志,而不是出于自己的判断只给出截取的部分。
|
||||||
|
required: true
|
||||||
|
- label: 我搜索了 issues, 没有发现已提出的类似问题。
|
||||||
|
required: true
|
||||||
|
- label: 问题在 Release 最新的版本上可以成功复现
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 描述
|
||||||
|
description: |-
|
||||||
|
请提供错误的详细描述。以及你认为有价值的信息。
|
||||||
|
如果问题在更新后出现,请提供**具体**出现问题的版本号。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 重现方式
|
||||||
|
description: |-
|
||||||
|
基于你下面提供的配置,提供重现BUG方法。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
## 配置与日志部分
|
||||||
|
|
||||||
|
### 对于配置文件
|
||||||
|
请提供可以重现问题的配置文件,包括服务端和客户端。
|
||||||
|
不要直接在这里黏贴一大段导出的 config 文件。去掉无用的出入站、规则、选项,这可以帮助确定问题,如果你真的想得到帮助。
|
||||||
|
在去掉不影响复现的部分后,提供实际运行的**完整**文件。
|
||||||
|
完整的含义:可以直接使用这个配置启动核心,**不是截取的部分配置**。对于密钥等参数使用重新生成未实际使用的有效参数填充。
|
||||||
|
|
||||||
|
### 对于日志
|
||||||
|
请先将日志等级设置为 debug, dnsLog 设置为true.
|
||||||
|
重启 Xray-core ,再按复现方式操作,尽量减少日志中的无关部分。
|
||||||
|
记得删除有关个人信息(如UUID与IP)的部分。
|
||||||
|
提供 Xray-core 的日志,而不是面板或者别的东西输出的日志。
|
||||||
|
|
||||||
|
### 最后
|
||||||
|
把下面的每格具体内容需要放在 ```<details><pre><code>``` 和 ```</code></pre></details>``` 中间,如
|
||||||
|
```
|
||||||
|
<details><pre><code>
|
||||||
|
(config)
|
||||||
|
</code></pre></details>
|
||||||
|
```
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 客户端配置
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 服务端配置
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 客户端日志
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 服务端日志
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
4
subproject/Xray-core-main/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
subproject/Xray-core-main/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
contact_links:
|
||||||
|
- name: Community Support and Questions
|
||||||
|
url: https://github.com/XTLS/Xray-core/discussions
|
||||||
|
about: Please ask and answer questions there. The issue tracker is for issues with core.
|
||||||
33
subproject/Xray-core-main/.github/build/friendly-filenames.json
vendored
Normal file
33
subproject/Xray-core-main/.github/build/friendly-filenames.json
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"android-arm64": { "friendlyName": "android-arm64-v8a" },
|
||||||
|
"darwin-amd64": { "friendlyName": "macos-64" },
|
||||||
|
"darwin-arm64": { "friendlyName": "macos-arm64-v8a" },
|
||||||
|
"freebsd-386": { "friendlyName": "freebsd-32" },
|
||||||
|
"freebsd-amd64": { "friendlyName": "freebsd-64" },
|
||||||
|
"freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
|
||||||
|
"freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
|
||||||
|
"linux-386": { "friendlyName": "linux-32" },
|
||||||
|
"linux-amd64": { "friendlyName": "linux-64" },
|
||||||
|
"linux-arm5": { "friendlyName": "linux-arm32-v5" },
|
||||||
|
"linux-arm64": { "friendlyName": "linux-arm64-v8a" },
|
||||||
|
"linux-arm6": { "friendlyName": "linux-arm32-v6" },
|
||||||
|
"linux-arm7": { "friendlyName": "linux-arm32-v7a" },
|
||||||
|
"linux-mips64le": { "friendlyName": "linux-mips64le" },
|
||||||
|
"linux-mips64": { "friendlyName": "linux-mips64" },
|
||||||
|
"linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" },
|
||||||
|
"linux-mipsle": { "friendlyName": "linux-mips32le" },
|
||||||
|
"linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" },
|
||||||
|
"linux-mips": { "friendlyName": "linux-mips32" },
|
||||||
|
"linux-ppc64le": { "friendlyName": "linux-ppc64le" },
|
||||||
|
"linux-ppc64": { "friendlyName": "linux-ppc64" },
|
||||||
|
"linux-riscv64": { "friendlyName": "linux-riscv64" },
|
||||||
|
"linux-loong64": { "friendlyName": "linux-loong64" },
|
||||||
|
"linux-s390x": { "friendlyName": "linux-s390x" },
|
||||||
|
"openbsd-386": { "friendlyName": "openbsd-32" },
|
||||||
|
"openbsd-amd64": { "friendlyName": "openbsd-64" },
|
||||||
|
"openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" },
|
||||||
|
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
|
||||||
|
"windows-386": { "friendlyName": "windows-32" },
|
||||||
|
"windows-amd64": { "friendlyName": "windows-64" },
|
||||||
|
"windows-arm64": { "friendlyName": "windows-arm64-v8a" }
|
||||||
|
}
|
||||||
15
subproject/Xray-core-main/.github/dependabot.yml
vendored
Normal file
15
subproject/Xray-core-main/.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
62
subproject/Xray-core-main/.github/docker/Dockerfile
vendored
Normal file
62
subproject/Xray-core-main/.github/docker/Dockerfile
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# syntax=docker/dockerfile:latest
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:latest AS build
|
||||||
|
|
||||||
|
# Build xray-core
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
||||||
|
|
||||||
|
# Download geodat into a staging directory
|
||||||
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
|
||||||
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
|
||||||
|
|
||||||
|
RUN mkdir -p /tmp/empty
|
||||||
|
|
||||||
|
# Create config files with empty JSON content
|
||||||
|
RUN mkdir -p /tmp/usr/local/etc/xray
|
||||||
|
RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"error": "/var/log/xray/error.log",
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "none",
|
||||||
|
"dnsLog": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
||||||
|
|
||||||
|
# Create log files
|
||||||
|
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||||
|
/tmp/var/log/xray/access.log \
|
||||||
|
/tmp/var/log/xray/error.log
|
||||||
|
|
||||||
|
# Build finally image
|
||||||
|
FROM gcr.io/distroless/static:nonroot
|
||||||
|
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||||
|
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||||
|
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||||
|
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
|
||||||
|
|
||||||
|
VOLUME /usr/local/etc/xray
|
||||||
|
VOLUME /var/log/xray
|
||||||
|
|
||||||
|
ARG TZ=Etc/UTC
|
||||||
|
ENV TZ=$TZ
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/xray" ]
|
||||||
|
CMD [ "-confdir", "/usr/local/etc/xray/" ]
|
||||||
71
subproject/Xray-core-main/.github/docker/Dockerfile.usa
vendored
Normal file
71
subproject/Xray-core-main/.github/docker/Dockerfile.usa
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# syntax=docker/dockerfile:latest
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:latest AS build
|
||||||
|
|
||||||
|
# Build xray-core
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
|
||||||
|
|
||||||
|
# Download geodat into a staging directory
|
||||||
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
|
||||||
|
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
|
||||||
|
|
||||||
|
RUN mkdir -p /tmp/empty
|
||||||
|
|
||||||
|
# Create config files with empty JSON content
|
||||||
|
RUN mkdir -p /tmp/usr/local/etc/xray
|
||||||
|
RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"error": "/var/log/xray/error.log",
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "none",
|
||||||
|
"dnsLog": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||||
|
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
||||||
|
|
||||||
|
# Create log files
|
||||||
|
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||||
|
/tmp/var/log/xray/access.log \
|
||||||
|
/tmp/var/log/xray/error.log
|
||||||
|
|
||||||
|
# Build finally image
|
||||||
|
# Note on Distroless Base Image and Architecture Support:
|
||||||
|
# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:
|
||||||
|
# - linux/amd64
|
||||||
|
# - linux/arm/v7
|
||||||
|
# - linux/arm64/v8
|
||||||
|
# - linux/ppc64le
|
||||||
|
# - linux/s390x
|
||||||
|
# - Upon inspection, the blob contents of the Distroless images across these architectures are nearly identical, with only minor differences in metadata (e.g., 'Architecture' field in the manifest).
|
||||||
|
# - Due to this similarity in content, it is feasible to forcibly specify a single platform (e.g., '--platform=linux/amd64') for unsupported architectures, as the core image content remains compatible with statically compiled binaries like Go applications.
|
||||||
|
FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
|
||||||
|
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||||
|
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||||
|
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||||
|
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||||
|
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
|
||||||
|
|
||||||
|
VOLUME /usr/local/etc/xray
|
||||||
|
VOLUME /var/log/xray
|
||||||
|
|
||||||
|
ARG TZ=Etc/UTC
|
||||||
|
ENV TZ=$TZ
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/xray" ]
|
||||||
|
CMD [ "-confdir", "/usr/local/etc/xray/" ]
|
||||||
133
subproject/Xray-core-main/.github/workflows/docker.yml
vendored
Normal file
133
subproject/Xray-core-main/.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
- released
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Docker image tag:"
|
||||||
|
required: true
|
||||||
|
latest:
|
||||||
|
description: "Set to latest"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
if: (github.event.action != 'published') || (github.event.action == 'published' && github.event.release.prerelease == true)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set repository and image name to lowercase
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: "${{ github.repository }}"
|
||||||
|
run: |
|
||||||
|
echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
|
||||||
|
echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
|
||||||
|
|
||||||
|
- name: Validate and extract tag
|
||||||
|
run: |
|
||||||
|
SOURCE_TAG="${{ github.event.inputs.tag }}"
|
||||||
|
if [[ -z "$SOURCE_TAG" ]]; then
|
||||||
|
SOURCE_TAG="${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
if [[ -z "$SOURCE_TAG" ]]; then
|
||||||
|
SOURCE_TAG="${{ github.event.release.tag_name }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$SOURCE_TAG" ]]; then
|
||||||
|
echo "Error: Could not determine a valid tag source. Input tag and context tag (github.ref_name) are both empty."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SOURCE_TAG" =~ ^v[0-9]+\.[0-9] ]]; then
|
||||||
|
IMAGE_TAG="${SOURCE_TAG#v}"
|
||||||
|
else
|
||||||
|
IMAGE_TAG="$SOURCE_TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Docker image tag: '$IMAGE_TAG'."
|
||||||
|
echo "IMAGE_TAG=$IMAGE_TAG" >>${GITHUB_ENV}
|
||||||
|
|
||||||
|
LATEST=false
|
||||||
|
if [[ "${{ github.event_name }}" == "release" && "${{ github.event.release.prerelease }}" == "false" ]] || [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.latest }}" == "true" ]]; then
|
||||||
|
LATEST=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Latest: '$LATEST'."
|
||||||
|
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Docker image (main architectures)
|
||||||
|
id: build_main_arches
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: .github/docker/Dockerfile
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
linux/arm/v7
|
||||||
|
linux/arm64/v8
|
||||||
|
linux/ppc64le
|
||||||
|
linux/s390x
|
||||||
|
provenance: false
|
||||||
|
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Build Docker image (additional architectures)
|
||||||
|
id: build_additional_arches
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: .github/docker/Dockerfile.usa
|
||||||
|
platforms: |
|
||||||
|
linux/386
|
||||||
|
linux/arm/v6
|
||||||
|
linux/riscv64
|
||||||
|
linux/loong64
|
||||||
|
provenance: false
|
||||||
|
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
|
- name: Create manifest list and push
|
||||||
|
run: |
|
||||||
|
echo "Creating multi-arch manifest with tag: '${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}'."
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
|
||||||
|
${{ env.FULL_IMAGE_NAME }}@${{ steps.build_main_arches.outputs.digest }} \
|
||||||
|
${{ env.FULL_IMAGE_NAME }}@${{ steps.build_additional_arches.outputs.digest }}
|
||||||
|
|
||||||
|
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||||
|
echo "Adding 'latest' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:latest'."
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag ${{ env.FULL_IMAGE_NAME }}:latest \
|
||||||
|
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
|
|
||||||
|
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||||
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
||||||
|
fi
|
||||||
180
subproject/Xray-core-main/.github/workflows/release-win7.yml
vendored
Normal file
180
subproject/Xray-core-main/.github/workflows/release-win7.yml
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
name: Build and Release for Windows 7
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-assets:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
|
||||||
|
- name: Restore Wintun Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-wintun-
|
||||||
|
|
||||||
|
- name: Check Assets Existence
|
||||||
|
id: check-assets
|
||||||
|
run: |
|
||||||
|
[ -d 'resources' ] || mkdir resources
|
||||||
|
LIST=('geoip.dat' 'geosite.dat')
|
||||||
|
for FILE_NAME in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
echo -e "Checking ${FILE_NAME}..."
|
||||||
|
if [ -s "./resources/${FILE_NAME}" ]; then
|
||||||
|
echo -e "${FILE_NAME} exists."
|
||||||
|
else
|
||||||
|
echo -e "${FILE_NAME} does not exist."
|
||||||
|
echo "missing=true" >> $GITHUB_OUTPUT
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
LIST=('amd64' 'x86')
|
||||||
|
for ARCHITECTURE in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
|
||||||
|
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||||
|
echo -e "wintun.dll for ${ARCHITECTURE} exists."
|
||||||
|
else
|
||||||
|
echo -e "wintun.dll for ${ARCHITECTURE} is missing."
|
||||||
|
echo "missing=true" >> $GITHUB_OUTPUT
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Sleep for 90 seconds if Assets Missing
|
||||||
|
if: steps.check-assets.outputs.missing == 'true'
|
||||||
|
run: sleep 90
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: check-assets
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# BEGIN Windows 7
|
||||||
|
- goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
assetname: win7-64
|
||||||
|
- goos: windows
|
||||||
|
goarch: 386
|
||||||
|
assetname: win7-32
|
||||||
|
# END Windows 7
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos}}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- name: Checkout codebase
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Show workflow information
|
||||||
|
run: |
|
||||||
|
_NAME=${{ matrix.assetname }}
|
||||||
|
echo "GOOS: ${{ matrix.goos }}, GOARCH: ${{ matrix.goarch }}, RELEASE_NAME: $_NAME"
|
||||||
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup patched builder
|
||||||
|
run: |
|
||||||
|
GOSDK=$(go env GOROOT)
|
||||||
|
rm -r $GOSDK/*
|
||||||
|
cd $GOSDK
|
||||||
|
curl -O -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip
|
||||||
|
unzip ./go-for-win7-linux-amd64.zip -d $GOSDK
|
||||||
|
rm ./go-for-win7-linux-amd64.zip
|
||||||
|
|
||||||
|
- name: Get project dependencies
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Build Xray
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
# 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
|
||||||
|
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
|
||||||
|
- name: Restore Wintun Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-wintun-
|
||||||
|
|
||||||
|
- name: Add additional assets into package
|
||||||
|
run: |
|
||||||
|
mv -f resources/geo* build_assets/
|
||||||
|
if [[ ${GOOS} == 'windows' ]]; then
|
||||||
|
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
|
||||||
|
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||||
|
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||||
|
fi
|
||||||
|
if [[ ${GOARCH} == '386' ]]; then
|
||||||
|
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||||
|
fi
|
||||||
|
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Copy README.md & LICENSE
|
||||||
|
run: |
|
||||||
|
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||||
|
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||||
|
|
||||||
|
- name: Create ZIP archive
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pushd build_assets || exit 1
|
||||||
|
touch -mt $(date +%Y01010000) *
|
||||||
|
zip -9vr ../Xray-${{ env.ASSET_NAME }}.zip .
|
||||||
|
popd || exit 1
|
||||||
|
FILE=./Xray-${{ env.ASSET_NAME }}.zip
|
||||||
|
DGST=$FILE.dgst
|
||||||
|
for METHOD in {"md5","sha1","sha256","sha512"}
|
||||||
|
do
|
||||||
|
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: ./Xray-${{ env.ASSET_NAME }}.zip*
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
file_glob: true
|
||||||
|
|
||||||
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: Xray-${{ env.ASSET_NAME }}
|
||||||
|
path: |
|
||||||
|
./build_assets/*
|
||||||
287
subproject/Xray-core-main/.github/workflows/release.yml
vendored
Normal file
287
subproject/Xray-core-main/.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-assets:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
|
||||||
|
- name: Restore Wintun Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-wintun-
|
||||||
|
|
||||||
|
- name: Check Assets Existence
|
||||||
|
id: check-assets
|
||||||
|
run: |
|
||||||
|
[ -d 'resources' ] || mkdir resources
|
||||||
|
LIST=('geoip.dat' 'geosite.dat')
|
||||||
|
for FILE_NAME in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
echo -e "Checking ${FILE_NAME}..."
|
||||||
|
if [ -s "./resources/${FILE_NAME}" ]; then
|
||||||
|
echo -e "${FILE_NAME} exists."
|
||||||
|
else
|
||||||
|
echo -e "${FILE_NAME} does not exist."
|
||||||
|
echo "missing=true" >> $GITHUB_OUTPUT
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
LIST=('amd64' 'x86' 'arm64')
|
||||||
|
for ARCHITECTURE in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
|
||||||
|
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||||
|
echo -e "wintun.dll for ${ARCHITECTURE} exists."
|
||||||
|
else
|
||||||
|
echo -e "wintun.dll for ${ARCHITECTURE} is missing."
|
||||||
|
echo "missing=true" >> $GITHUB_OUTPUT
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Trigger Asset Update Workflow if Assets Missing
|
||||||
|
if: steps.check-assets.outputs.missing == 'true'
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
workflow_id: 'scheduled-assets-update.yml',
|
||||||
|
ref: context.ref
|
||||||
|
});
|
||||||
|
console.log('Triggered scheduled-assets-update.yml due to missing assets on branch:', context.ref);
|
||||||
|
|
||||||
|
- name: Sleep for 90 seconds if Assets Missing
|
||||||
|
if: steps.check-assets.outputs.missing == 'true'
|
||||||
|
run: sleep 90
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: check-assets
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
# Include amd64 on all platforms.
|
||||||
|
goos: [windows, freebsd, openbsd, linux, darwin]
|
||||||
|
goarch: [amd64, 386]
|
||||||
|
patch-assetname: [""]
|
||||||
|
exclude:
|
||||||
|
# Exclude i386 on darwin
|
||||||
|
- goarch: 386
|
||||||
|
goos: darwin
|
||||||
|
include:
|
||||||
|
# BEGIN MacOS ARM64
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
# END MacOS ARM64
|
||||||
|
# BEGIN Linux ARM 5 6 7
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: 7
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: 6
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: 5
|
||||||
|
# END Linux ARM 5 6 7
|
||||||
|
# BEGIN Android ARM 8
|
||||||
|
- goos: android
|
||||||
|
goarch: arm64
|
||||||
|
# END Android ARM 8
|
||||||
|
# BEGIN Android AMD64
|
||||||
|
- goos: android
|
||||||
|
goarch: amd64
|
||||||
|
patch-assetname: android-amd64
|
||||||
|
# END Android AMD64
|
||||||
|
# Windows ARM
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm64
|
||||||
|
# BEGIN Other architectures
|
||||||
|
# BEGIN riscv64 & ARM64 & LOONG64
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
- goos: linux
|
||||||
|
goarch: riscv64
|
||||||
|
- goos: linux
|
||||||
|
goarch: loong64
|
||||||
|
# END riscv64 & ARM64 & LOONG64
|
||||||
|
# BEGIN MIPS
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips64
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips64le
|
||||||
|
- goos: linux
|
||||||
|
goarch: mipsle
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips
|
||||||
|
# END MIPS
|
||||||
|
# BEGIN PPC
|
||||||
|
- goos: linux
|
||||||
|
goarch: ppc64
|
||||||
|
- goos: linux
|
||||||
|
goarch: ppc64le
|
||||||
|
# END PPC
|
||||||
|
# BEGIN FreeBSD ARM
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm64
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm
|
||||||
|
goarm: 7
|
||||||
|
# END FreeBSD ARM
|
||||||
|
# BEGIN S390X
|
||||||
|
- goos: linux
|
||||||
|
goarch: s390x
|
||||||
|
# END S390X
|
||||||
|
# END Other architectures
|
||||||
|
# BEGIN OPENBSD ARM
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: arm64
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: arm
|
||||||
|
goarm: 7
|
||||||
|
# END OPENBSD ARM
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- name: Checkout codebase
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set up NDK
|
||||||
|
if: matrix.goos == 'android'
|
||||||
|
run: |
|
||||||
|
wget -qO android-ndk.zip https://dl.google.com/android/repository/android-ndk-r28b-linux.zip
|
||||||
|
unzip android-ndk.zip
|
||||||
|
rm android-ndk.zip
|
||||||
|
declare -A arches=(
|
||||||
|
["amd64"]="x86_64-linux-android24-clang"
|
||||||
|
["arm64"]="aarch64-linux-android24-clang"
|
||||||
|
)
|
||||||
|
echo CC="$(realpath android-ndk-*/toolchains/llvm/prebuilt/linux-x86_64/bin)/${arches[${{ matrix.goarch }}]}" >> $GITHUB_ENV
|
||||||
|
echo CGO_ENABLED=1 >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Show workflow information
|
||||||
|
run: |
|
||||||
|
_NAME=${{ matrix.patch-assetname }}
|
||||||
|
[ -n "$_NAME" ] || _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||||
|
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
|
||||||
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Get project dependencies
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Build Xray
|
||||||
|
run: |
|
||||||
|
mkdir -p build_assets
|
||||||
|
COMMID=$(git describe --always --dirty)
|
||||||
|
if [[ ${GOOS} == 'windows' ]]; then
|
||||||
|
echo 'Building Xray for Windows...'
|
||||||
|
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||||
|
# 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
|
||||||
|
else
|
||||||
|
echo 'Building Xray...'
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
|
||||||
|
- name: Restore Wintun Cache
|
||||||
|
if: matrix.goos == 'windows'
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-wintun-
|
||||||
|
|
||||||
|
- name: Add additional assets into package
|
||||||
|
run: |
|
||||||
|
mv -f resources/geo* build_assets/
|
||||||
|
if [[ ${GOOS} == 'windows' ]]; then
|
||||||
|
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
|
||||||
|
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||||
|
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||||
|
fi
|
||||||
|
if [[ ${GOARCH} == '386' ]]; then
|
||||||
|
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||||
|
fi
|
||||||
|
if [[ ${GOARCH} == 'arm64' ]]; then
|
||||||
|
mv resources/wintun/bin/arm64/wintun.dll build_assets/
|
||||||
|
fi
|
||||||
|
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Copy README.md & LICENSE
|
||||||
|
run: |
|
||||||
|
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||||
|
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||||
|
|
||||||
|
- name: Create ZIP archive
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pushd build_assets || exit 1
|
||||||
|
touch -mt $(date +%Y01010000) *
|
||||||
|
zip -9vr ../Xray-${{ env.ASSET_NAME }}.zip .
|
||||||
|
popd || exit 1
|
||||||
|
FILE=./Xray-${{ env.ASSET_NAME }}.zip
|
||||||
|
DGST=$FILE.dgst
|
||||||
|
for METHOD in {"md5","sha1","sha256","sha512"}
|
||||||
|
do
|
||||||
|
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: ./Xray-${{ env.ASSET_NAME }}.zip*
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
file_glob: true
|
||||||
|
|
||||||
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: Xray-${{ env.ASSET_NAME }}
|
||||||
|
path: |
|
||||||
|
./build_assets/*
|
||||||
129
subproject/Xray-core-main/.github/workflows/scheduled-assets-update.yml
vendored
Normal file
129
subproject/Xray-core-main/.github/workflows/scheduled-assets-update.yml
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
name: Scheduled assets update
|
||||||
|
|
||||||
|
# NOTE: This Github Actions is required by other actions, for preparing other packaging assets in a
|
||||||
|
# routine manner, for example: GeoIP/GeoSite.
|
||||||
|
# Currently updating:
|
||||||
|
# - Geodat (GeoIP/Geosite)
|
||||||
|
# - Wintun (wintun.dll)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# Update GeoData on every day (22:30 UTC)
|
||||||
|
- cron: "30 22 * * *"
|
||||||
|
push:
|
||||||
|
# Prevent triggering update request storm
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/scheduled-assets-update.yml"
|
||||||
|
pull_request:
|
||||||
|
# Prevent triggering update request storm
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/scheduled-assets-update.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
geodat:
|
||||||
|
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
|
||||||
|
- name: Update Geodat
|
||||||
|
id: update
|
||||||
|
uses: nick-fields/retry@v4
|
||||||
|
with:
|
||||||
|
timeout_minutes: 60
|
||||||
|
retry_wait_seconds: 60
|
||||||
|
max_attempts: 60
|
||||||
|
command: |
|
||||||
|
[ -d 'resources' ] || mkdir resources
|
||||||
|
LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite')
|
||||||
|
for i in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
|
||||||
|
FILE_NAME="${INFO[3]}.dat"
|
||||||
|
echo -e "Verifying HASH key..."
|
||||||
|
HASH="$(curl -sL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
||||||
|
if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..."
|
||||||
|
curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat" -o ./resources/${FILE_NAME}
|
||||||
|
echo -e "Verifying HASH key..."
|
||||||
|
[ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||||
|
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Save Geodat Cache
|
||||||
|
uses: actions/cache/save@v5
|
||||||
|
if: ${{ steps.update.outputs.unhit }}
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-${{ github.sha }}-${{ github.run_number }}
|
||||||
|
|
||||||
|
wintun:
|
||||||
|
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Restore Wintun Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-wintun-
|
||||||
|
|
||||||
|
- name: Force downloading if run manually or on file update
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
echo "FORCE_UPDATE=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Update Wintun
|
||||||
|
id: update
|
||||||
|
uses: nick-fields/retry@v4
|
||||||
|
with:
|
||||||
|
timeout_minutes: 60
|
||||||
|
retry_wait_seconds: 60
|
||||||
|
max_attempts: 60
|
||||||
|
command: |
|
||||||
|
[ -d 'resources' ] || mkdir resources
|
||||||
|
LIST=('amd64' 'x86' 'arm64')
|
||||||
|
for ARCHITECTURE in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
FILE_PATH="resources/wintun/bin/${ARCHITECTURE}/wintun.dll"
|
||||||
|
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
|
||||||
|
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||||
|
echo -e "wintun.dll for ${ARCHITECTURE} exists"
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
|
||||||
|
missing=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -s "./resources/wintun/LICENSE.txt" ]; then
|
||||||
|
echo -e "LICENSE for Wintun exists"
|
||||||
|
else
|
||||||
|
echo -e "LICENSE for Wintun is missing"
|
||||||
|
missing=true
|
||||||
|
fi
|
||||||
|
if [[ -v FORCE_UPDATE ]]; then
|
||||||
|
missing=true
|
||||||
|
fi
|
||||||
|
if [[ "$missing" == true ]]; then
|
||||||
|
FILENAME=wintun.zip
|
||||||
|
DOWNLOAD_FILE=wintun-0.14.1.zip
|
||||||
|
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
|
||||||
|
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
|
||||||
|
echo -e "Unpacking wintun..."
|
||||||
|
unzip -u ${FILENAME} -d resources/
|
||||||
|
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Save Wintun Cache
|
||||||
|
uses: actions/cache/save@v5
|
||||||
|
if: ${{ steps.update.outputs.unhit }}
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-wintun-${{ github.sha }}-${{ github.run_number }}
|
||||||
77
subproject/Xray-core-main/.github/workflows/test.yml
vendored
Normal file
77
subproject/Xray-core-main/.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-assets:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
- name: Check Assets Existence
|
||||||
|
id: check-assets
|
||||||
|
run: |
|
||||||
|
[ -d 'resources' ] || mkdir resources
|
||||||
|
LIST=('geoip.dat' 'geosite.dat')
|
||||||
|
for FILE_NAME in "${LIST[@]}"
|
||||||
|
do
|
||||||
|
echo -e "Checking ${FILE_NAME}..."
|
||||||
|
if [ -s "./resources/${FILE_NAME}" ]; then
|
||||||
|
echo -e "${FILE_NAME} exists."
|
||||||
|
else
|
||||||
|
echo -e "${FILE_NAME} does not exist."
|
||||||
|
echo "missing=true" >> $GITHUB_OUTPUT
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
- name: Sleep for 90 seconds if Assets Missing
|
||||||
|
if: steps.check-assets.outputs.missing == 'true'
|
||||||
|
run: sleep 90
|
||||||
|
|
||||||
|
check-proto:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout codebase
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Check Proto Version Header
|
||||||
|
run: |
|
||||||
|
head -n 4 core/config.pb.go > ref.txt
|
||||||
|
find . -name "*.pb.go" ! -name "*_grpc.pb.go" -print0 | while IFS= read -r -d '' file; do
|
||||||
|
if ! cmp -s ref.txt <(head -n 4 "$file"); then
|
||||||
|
echo "Error: Header mismatch in $file"
|
||||||
|
head -n 4 "$file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: check-assets
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
|
steps:
|
||||||
|
- name: Checkout codebase
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
- name: Restore Geodat Cache
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: resources
|
||||||
|
key: xray-geodat-
|
||||||
|
enableCrossOsArchive: true
|
||||||
|
- name: Test
|
||||||
|
run: go test -timeout 1h -v ./...
|
||||||
68
subproject/Xray-core-main/.gitignore
vendored
Normal file
68
subproject/Xray-core-main/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# macOS specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# IDE/editor specific files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Archives and compressed files
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.tar
|
||||||
|
*.gz
|
||||||
|
*.bz2
|
||||||
|
|
||||||
|
# Go build binaries
|
||||||
|
xray
|
||||||
|
xray_softfloat
|
||||||
|
mockgen
|
||||||
|
vprotogen
|
||||||
|
!infra/vprotogen/
|
||||||
|
errorgen
|
||||||
|
!common/errors/errorgen/
|
||||||
|
*.dat
|
||||||
|
|
||||||
|
# Build assets
|
||||||
|
/build_assets/
|
||||||
|
|
||||||
|
# Output from dlv test
|
||||||
|
**/debug.*
|
||||||
|
|
||||||
|
# Certificates and keys
|
||||||
|
*.crt
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# Dependency directories (uncomment if needed)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
coverage.*
|
||||||
|
|
||||||
|
# Node modules (in case of frontend assets)
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# System files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Other common ignores
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
128
subproject/Xray-core-main/CODE_OF_CONDUCT.md
Normal file
128
subproject/Xray-core-main/CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
https://t.me/projectXtls.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
373
subproject/Xray-core-main/LICENSE
Normal file
373
subproject/Xray-core-main/LICENSE
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
214
subproject/Xray-core-main/README.md
Normal file
214
subproject/Xray-core-main/README.md
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
# Project X
|
||||||
|
|
||||||
|
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
|
||||||
|
|
||||||
|
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
[](https://docs.rw)
|
||||||
|
|
||||||
|
[](https://happ.su)
|
||||||
|
|
||||||
|
[](https://blanc.link/VMTSDqW)
|
||||||
|
|
||||||
|
[**Sponsor Xray-core**](https://github.com/XTLS/Xray-core/issues/3668)
|
||||||
|
|
||||||
|
## Donation & NFTs
|
||||||
|
|
||||||
|
### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||||
|
|
||||||
|
[<img alt="Project X NFT" width="150px" src="https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg" />](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||||
|
|
||||||
|
- **TRX(Tron)/USDT/USDC: `TNrDh5VSfwd4RPrwsohr6poyNTfFefNYan`**
|
||||||
|
- **TON: `UQApeV-u2gm43aC1uP76xAC1m6vCylstaN1gpfBmre_5IyTH`**
|
||||||
|
- **BTC: `1JpqcziZZuqv3QQJhZGNGBVdCBrGgkL6cT`**
|
||||||
|
- **XMR: `4ABHQZ3yJZkBnLoqiKvb3f8eqUnX4iMPb6wdant5ZLGQELctcerceSGEfJnoCk6nnyRZm73wrwSgvZ2WmjYLng6R7sR67nq`**
|
||||||
|
- **SOL/USDT/USDC: `3x5NuXHzB5APG6vRinPZcsUv5ukWUY1tBGRSJiEJWtZa`**
|
||||||
|
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
|
||||||
|
- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1**
|
||||||
|
- **VLESS NFT: https://opensea.io/collection/vless**
|
||||||
|
- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
|
||||||
|
- **Related links: [VLESS Post-Quantum Encryption](https://github.com/XTLS/Xray-core/pull/5067), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113), [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)**
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[Project X Official Website](https://xtls.github.io)
|
||||||
|
|
||||||
|
## Telegram
|
||||||
|
|
||||||
|
[Project X](https://t.me/projectXray)
|
||||||
|
|
||||||
|
[Project X Channel](https://t.me/projectXtls)
|
||||||
|
|
||||||
|
[Project VLESS](https://t.me/projectVless) (Русский)
|
||||||
|
|
||||||
|
[Project XHTTP](https://t.me/projectXhttp) (Persian)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
- Linux Script
|
||||||
|
- [XTLS/Xray-install](https://github.com/XTLS/Xray-install) (**Official**)
|
||||||
|
- [tempest](https://github.com/team-cloudchaser/tempest) (supports [`systemd`](https://systemd.io) and [OpenRC](https://github.com/OpenRC/openrc); Linux-only)
|
||||||
|
- Docker
|
||||||
|
- [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)
|
||||||
|
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
|
||||||
|
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
|
||||||
|
- Web Panel
|
||||||
|
- [Remnawave](https://github.com/remnawave/panel)
|
||||||
|
- [3X-UI](https://github.com/MHSanaei/3x-ui)
|
||||||
|
- [PasarGuard](https://github.com/PasarGuard/panel)
|
||||||
|
- [Xray-UI](https://github.com/qist/xray-ui)
|
||||||
|
- [X-Panel](https://github.com/xeefei/X-Panel)
|
||||||
|
- [Marzban](https://github.com/Gozargah/Marzban)
|
||||||
|
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
|
||||||
|
- [TX-UI](https://github.com/AghayeCoder/tx-ui)
|
||||||
|
- [CELERITY](https://github.com/ClickDevTech/CELERITY-panel)
|
||||||
|
- One Click
|
||||||
|
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
|
||||||
|
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||||
|
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||||
|
- Magisk
|
||||||
|
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||||
|
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||||
|
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||||
|
- Homebrew
|
||||||
|
- `brew install xray`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- Example
|
||||||
|
- [VLESS-XTLS-uTLS-REALITY](https://github.com/XTLS/REALITY#readme)
|
||||||
|
- [VLESS-TCP-XTLS-Vision](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision)
|
||||||
|
- [All-in-One-fallbacks-Nginx](https://github.com/XTLS/Xray-examples/tree/main/All-in-One-fallbacks-Nginx)
|
||||||
|
- Xray-examples
|
||||||
|
- [XTLS/Xray-examples](https://github.com/XTLS/Xray-examples)
|
||||||
|
- [chika0801/Xray-examples](https://github.com/chika0801/Xray-examples)
|
||||||
|
- [lxhao61/integrated-examples](https://github.com/lxhao61/integrated-examples)
|
||||||
|
- Tutorial
|
||||||
|
- [XTLS Vision](https://github.com/chika0801/Xray-install)
|
||||||
|
- [REALITY (English)](https://cscot.pages.dev/2023/03/02/Xray-REALITY-tutorial/)
|
||||||
|
- [XTLS-Iran-Reality (English)](https://github.com/SasukeFreestyle/XTLS-Iran-Reality)
|
||||||
|
- [Xray REALITY with 'steal oneself' (English)](https://computerscot.github.io/vless-xtls-utls-reality-steal-oneself.html)
|
||||||
|
- [Xray with WireGuard inbound (English)](https://g800.pages.dev/wireguard)
|
||||||
|
|
||||||
|
## GUI Clients
|
||||||
|
|
||||||
|
- OpenWrt
|
||||||
|
- [PassWall](https://github.com/Openwrt-Passwall/openwrt-passwall), [PassWall 2](https://github.com/Openwrt-Passwall/openwrt-passwall2)
|
||||||
|
- [ShadowSocksR Plus+](https://github.com/fw876/helloworld)
|
||||||
|
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
|
||||||
|
- Asuswrt-Merlin
|
||||||
|
- [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)
|
||||||
|
- [fancyss](https://github.com/hq450/fancyss)
|
||||||
|
- Windows
|
||||||
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
|
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
||||||
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
|
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||||
|
- Android
|
||||||
|
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||||
|
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||||
|
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
||||||
|
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||||
|
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||||
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
|
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||||
|
- iOS & macOS arm64 & tvOS
|
||||||
|
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||||
|
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||||
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
|
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
|
||||||
|
- macOS arm64 & x64
|
||||||
|
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)
|
||||||
|
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||||
|
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||||
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
|
- [GoXRay](https://github.com/goxray/desktop)
|
||||||
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
|
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||||
|
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
|
||||||
|
- Linux
|
||||||
|
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
||||||
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
|
- [GorzRay](https://github.com/ketetefid/GorzRay)
|
||||||
|
- [GoXRay](https://github.com/goxray/desktop)
|
||||||
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
|
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||||
|
|
||||||
|
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||||
|
|
||||||
|
- iOS & macOS arm64 & tvOS
|
||||||
|
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||||
|
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||||
|
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
|
||||||
|
- [Quantumult X](https://apps.apple.com/us/app/quantumult-x/id1443988620)
|
||||||
|
- Xray Tools
|
||||||
|
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
|
||||||
|
- [xray-checker](https://github.com/kutovoys/xray-checker)
|
||||||
|
- Xray Wrapper
|
||||||
|
- [XTLS/libXray](https://github.com/XTLS/libXray)
|
||||||
|
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
|
||||||
|
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
||||||
|
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
||||||
|
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
|
||||||
|
- [xray-api](https://github.com/XVGuardian/xray-api)
|
||||||
|
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||||
|
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
|
||||||
|
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
|
||||||
|
- Cores
|
||||||
|
- [Amnezia VPN](https://github.com/amnezia-vpn)
|
||||||
|
- [mihomo](https://github.com/MetaCubeX/mihomo)
|
||||||
|
- [sing-box](https://github.com/SagerNet/sing-box)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
|
[](https://deepwiki.com/XTLS/Xray-core)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
|
||||||
|
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
|
||||||
|
|
||||||
|
## One-line Compilation
|
||||||
|
|
||||||
|
### Windows (PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:CGO_ENABLED=0
|
||||||
|
go build -o xray.exe -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux / macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reproducible Releases
|
||||||
|
|
||||||
|
Make sure that you are using the same Go version, and remember to set the git commit id (7 bytes):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stargazers over time
|
||||||
|
|
||||||
|
[](https://starchart.cc/XTLS/Xray-core)
|
||||||
5
subproject/Xray-core-main/SECURITY.md
Normal file
5
subproject/Xray-core-main/SECURITY.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
If you found an issue related to security vulnerability or protocol-identification problem, please report it to us via "[Report a vulnerability](https://github.com/XTLS/Xray-core/security/advisories/new)" privately, instead of publish it publicly before we release the fixed version.
|
||||||
|
|
||||||
|
Thanks for your contribution to the FREE Internet!
|
||||||
2
subproject/Xray-core-main/app/app.go
Normal file
2
subproject/Xray-core-main/app/app.go
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package app contains feature implementations of Xray. The features may be enabled during runtime.
|
||||||
|
package app
|
||||||
121
subproject/Xray-core-main/app/commander/commander.go
Normal file
121
subproject/Xray-core-main/app/commander/commander.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package commander
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
core "github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commander is a Xray feature that provides gRPC methods to external clients.
|
||||||
|
type Commander struct {
|
||||||
|
sync.Mutex
|
||||||
|
server *grpc.Server
|
||||||
|
services []Service
|
||||||
|
ohm outbound.Manager
|
||||||
|
tag string
|
||||||
|
listen string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommander creates a new Commander based on the given config.
|
||||||
|
func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
|
||||||
|
c := &Commander{
|
||||||
|
tag: config.Tag,
|
||||||
|
listen: config.Listen,
|
||||||
|
}
|
||||||
|
|
||||||
|
common.Must(core.RequireFeatures(ctx, func(om outbound.Manager) {
|
||||||
|
c.ohm = om
|
||||||
|
}))
|
||||||
|
|
||||||
|
for _, rawConfig := range config.Service {
|
||||||
|
config, err := rawConfig.GetInstance()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawService, err := common.CreateObject(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service, ok := rawService.(Service)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not a Service.")
|
||||||
|
}
|
||||||
|
c.services = append(c.services, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (c *Commander) Type() interface{} {
|
||||||
|
return (*Commander)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (c *Commander) Start() error {
|
||||||
|
c.Lock()
|
||||||
|
c.server = grpc.NewServer()
|
||||||
|
for _, service := range c.services {
|
||||||
|
service.Register(c.server)
|
||||||
|
}
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
var listen = func(listener net.Listener) {
|
||||||
|
if err := c.server.Serve(listener); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.listen) > 0 {
|
||||||
|
if l, err := net.Listen("tcp", c.listen); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
|
||||||
|
go listen(l)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := &OutboundListener{
|
||||||
|
buffer: make(chan net.Conn, 4),
|
||||||
|
done: done.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go listen(listener)
|
||||||
|
|
||||||
|
if err := c.ohm.RemoveHandler(context.Background(), c.tag); err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to remove existing handler")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ohm.AddHandler(context.Background(), &Outbound{
|
||||||
|
tag: c.tag,
|
||||||
|
listener: listener,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (c *Commander) Close() error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
if c.server != nil {
|
||||||
|
c.server.Stop()
|
||||||
|
c.server = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
return NewCommander(ctx, cfg.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
188
subproject/Xray-core-main/app/commander/config.pb.go
Normal file
188
subproject/Xray-core-main/app/commander/config.pb.go
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/commander/config.proto
|
||||||
|
|
||||||
|
package commander
|
||||||
|
|
||||||
|
import (
|
||||||
|
serial "github.com/xtls/xray-core/common/serial"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the settings for Commander.
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// Tag of the outbound handler that handles grpc connections.
|
||||||
|
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
|
// Network address of commander grpc service.
|
||||||
|
Listen string `protobuf:"bytes,3,opt,name=listen,proto3" json:"listen,omitempty"`
|
||||||
|
// Services that supported by this server. All services must implement Service
|
||||||
|
// interface.
|
||||||
|
Service []*serial.TypedMessage `protobuf:"bytes,2,rep,name=service,proto3" json:"service,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_commander_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_commander_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_commander_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetTag() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tag
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetListen() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Listen
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetService() []*serial.TypedMessage {
|
||||||
|
if x != nil {
|
||||||
|
return x.Service
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReflectionConfig is the placeholder config for ReflectionService.
|
||||||
|
type ReflectionConfig struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ReflectionConfig) Reset() {
|
||||||
|
*x = ReflectionConfig{}
|
||||||
|
mi := &file_app_commander_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ReflectionConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ReflectionConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ReflectionConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_commander_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ReflectionConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ReflectionConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_commander_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_commander_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_commander_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1aapp/commander/config.proto\x12\x12xray.app.commander\x1a!common/serial/typed_message.proto\"n\n" +
|
||||||
|
"\x06Config\x12\x10\n" +
|
||||||
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x12\x16\n" +
|
||||||
|
"\x06listen\x18\x03 \x01(\tR\x06listen\x12:\n" +
|
||||||
|
"\aservice\x18\x02 \x03(\v2 .xray.common.serial.TypedMessageR\aservice\"\x12\n" +
|
||||||
|
"\x10ReflectionConfigBX\n" +
|
||||||
|
"\x16com.xray.app.commanderP\x01Z'github.com/xtls/xray-core/app/commander\xaa\x02\x12Xray.App.Commanderb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_commander_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_commander_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_commander_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_commander_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_commander_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_commander_config_proto_rawDesc), len(file_app_commander_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_commander_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_commander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_app_commander_config_proto_goTypes = []any{
|
||||||
|
(*Config)(nil), // 0: xray.app.commander.Config
|
||||||
|
(*ReflectionConfig)(nil), // 1: xray.app.commander.ReflectionConfig
|
||||||
|
(*serial.TypedMessage)(nil), // 2: xray.common.serial.TypedMessage
|
||||||
|
}
|
||||||
|
var file_app_commander_config_proto_depIdxs = []int32{
|
||||||
|
2, // 0: xray.app.commander.Config.service:type_name -> xray.common.serial.TypedMessage
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_commander_config_proto_init() }
|
||||||
|
func file_app_commander_config_proto_init() {
|
||||||
|
if File_app_commander_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_commander_config_proto_rawDesc), len(file_app_commander_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_commander_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_commander_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_commander_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_commander_config_proto = out.File
|
||||||
|
file_app_commander_config_proto_goTypes = nil
|
||||||
|
file_app_commander_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
25
subproject/Xray-core-main/app/commander/config.proto
Normal file
25
subproject/Xray-core-main/app/commander/config.proto
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.commander;
|
||||||
|
option csharp_namespace = "Xray.App.Commander";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/commander";
|
||||||
|
option java_package = "com.xray.app.commander";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "common/serial/typed_message.proto";
|
||||||
|
|
||||||
|
// Config is the settings for Commander.
|
||||||
|
message Config {
|
||||||
|
// Tag of the outbound handler that handles grpc connections.
|
||||||
|
string tag = 1;
|
||||||
|
|
||||||
|
// Network address of commander grpc service.
|
||||||
|
string listen = 3;
|
||||||
|
|
||||||
|
// Services that supported by this server. All services must implement Service
|
||||||
|
// interface.
|
||||||
|
repeated xray.common.serial.TypedMessage service = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReflectionConfig is the placeholder config for ReflectionService.
|
||||||
|
message ReflectionConfig {}
|
||||||
121
subproject/Xray-core-main/app/commander/outbound.go
Normal file
121
subproject/Xray-core-main/app/commander/outbound.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package commander
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
"github.com/xtls/xray-core/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutboundListener is a net.Listener for listening gRPC connections.
|
||||||
|
type OutboundListener struct {
|
||||||
|
buffer chan net.Conn
|
||||||
|
done *done.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OutboundListener) add(conn net.Conn) {
|
||||||
|
select {
|
||||||
|
case l.buffer <- conn:
|
||||||
|
case <-l.done.Wait():
|
||||||
|
conn.Close()
|
||||||
|
default:
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept implements net.Listener.
|
||||||
|
func (l *OutboundListener) Accept() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case <-l.done.Wait():
|
||||||
|
return nil, errors.New("listen closed")
|
||||||
|
case c := <-l.buffer:
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements net.Listener.
|
||||||
|
func (l *OutboundListener) Close() error {
|
||||||
|
common.Must(l.done.Close())
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-l.buffer:
|
||||||
|
c.Close()
|
||||||
|
default:
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr implements net.Listener.
|
||||||
|
func (l *OutboundListener) Addr() net.Addr {
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: net.IP{0, 0, 0, 0},
|
||||||
|
Port: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outbound is a outbound.Handler that handles gRPC connections.
|
||||||
|
type Outbound struct {
|
||||||
|
tag string
|
||||||
|
listener *OutboundListener
|
||||||
|
access sync.RWMutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch implements outbound.Handler.
|
||||||
|
func (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) {
|
||||||
|
co.access.RLock()
|
||||||
|
|
||||||
|
if co.closed {
|
||||||
|
common.Interrupt(link.Reader)
|
||||||
|
common.Interrupt(link.Writer)
|
||||||
|
co.access.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSignal := done.New()
|
||||||
|
c := cnc.NewConnection(cnc.ConnectionInputMulti(link.Writer), cnc.ConnectionOutputMulti(link.Reader), cnc.ConnectionOnClose(closeSignal))
|
||||||
|
co.listener.add(c)
|
||||||
|
co.access.RUnlock()
|
||||||
|
<-closeSignal.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag implements outbound.Handler.
|
||||||
|
func (co *Outbound) Tag() string {
|
||||||
|
return co.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (co *Outbound) Start() error {
|
||||||
|
co.access.Lock()
|
||||||
|
co.closed = false
|
||||||
|
co.access.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (co *Outbound) Close() error {
|
||||||
|
co.access.Lock()
|
||||||
|
defer co.access.Unlock()
|
||||||
|
|
||||||
|
co.closed = true
|
||||||
|
return co.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SenderSettings implements outbound.Handler.
|
||||||
|
func (co *Outbound) SenderSettings() *serial.TypedMessage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxySettings implements outbound.Handler.
|
||||||
|
func (co *Outbound) ProxySettings() *serial.TypedMessage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
27
subproject/Xray-core-main/app/commander/service.go
Normal file
27
subproject/Xray-core-main/app/commander/service.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package commander
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service is a Commander service.
|
||||||
|
type Service interface {
|
||||||
|
// Register registers the service itself to a gRPC server.
|
||||||
|
Register(*grpc.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
type reflectionService struct{}
|
||||||
|
|
||||||
|
func (r reflectionService) Register(s *grpc.Server) {
|
||||||
|
reflection.Register(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*ReflectionConfig)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
return reflectionService{}, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
162
subproject/Xray-core-main/app/dispatcher/config.pb.go
Normal file
162
subproject/Xray-core-main/app/dispatcher/config.pb.go
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/dispatcher/config.proto
|
||||||
|
|
||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionConfig struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SessionConfig) Reset() {
|
||||||
|
*x = SessionConfig{}
|
||||||
|
mi := &file_app_dispatcher_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SessionConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SessionConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dispatcher_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SessionConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dispatcher_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_dispatcher_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dispatcher_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dispatcher_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetSettings() *SessionConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.Settings
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_dispatcher_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_dispatcher_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1bapp/dispatcher/config.proto\x12\x13xray.app.dispatcher\"\x15\n" +
|
||||||
|
"\rSessionConfigJ\x04\b\x01\x10\x02\"H\n" +
|
||||||
|
"\x06Config\x12>\n" +
|
||||||
|
"\bsettings\x18\x01 \x01(\v2\".xray.app.dispatcher.SessionConfigR\bsettingsB[\n" +
|
||||||
|
"\x17com.xray.app.dispatcherP\x01Z(github.com/xtls/xray-core/app/dispatcher\xaa\x02\x13Xray.App.Dispatcherb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_dispatcher_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_dispatcher_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_dispatcher_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_dispatcher_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_dispatcher_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dispatcher_config_proto_rawDesc), len(file_app_dispatcher_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_dispatcher_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_dispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_app_dispatcher_config_proto_goTypes = []any{
|
||||||
|
(*SessionConfig)(nil), // 0: xray.app.dispatcher.SessionConfig
|
||||||
|
(*Config)(nil), // 1: xray.app.dispatcher.Config
|
||||||
|
}
|
||||||
|
var file_app_dispatcher_config_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.app.dispatcher.Config.settings:type_name -> xray.app.dispatcher.SessionConfig
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_dispatcher_config_proto_init() }
|
||||||
|
func file_app_dispatcher_config_proto_init() {
|
||||||
|
if File_app_dispatcher_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dispatcher_config_proto_rawDesc), len(file_app_dispatcher_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_dispatcher_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_dispatcher_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_dispatcher_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_dispatcher_config_proto = out.File
|
||||||
|
file_app_dispatcher_config_proto_goTypes = nil
|
||||||
|
file_app_dispatcher_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
15
subproject/Xray-core-main/app/dispatcher/config.proto
Normal file
15
subproject/Xray-core-main/app/dispatcher/config.proto
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.dispatcher;
|
||||||
|
option csharp_namespace = "Xray.App.Dispatcher";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/dispatcher";
|
||||||
|
option java_package = "com.xray.app.dispatcher";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message SessionConfig {
|
||||||
|
reserved 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
SessionConfig settings = 1;
|
||||||
|
}
|
||||||
519
subproject/Xray-core-main/app/dispatcher/default.go
Normal file
519
subproject/Xray-core-main/app/dispatcher/default.go
Normal file
|
|
@ -0,0 +1,519 @@
|
||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
"github.com/xtls/xray-core/features/policy"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||||
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
"github.com/xtls/xray-core/transport"
|
||||||
|
"github.com/xtls/xray-core/transport/pipe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errSniffingTimeout = errors.New("timeout on sniffing")
|
||||||
|
|
||||||
|
type cachedReader struct {
|
||||||
|
sync.Mutex
|
||||||
|
reader buf.TimeoutReader // *pipe.Reader or *buf.TimeoutWrapperReader
|
||||||
|
cache buf.MultiBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
|
||||||
|
mb, err := r.reader.ReadMultiBufferTimeout(deadline)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Lock()
|
||||||
|
if !mb.IsEmpty() {
|
||||||
|
r.cache, _ = buf.MergeMulti(r.cache, mb)
|
||||||
|
}
|
||||||
|
b.Clear()
|
||||||
|
rawBytes := b.Extend(min(r.cache.Len(), b.Cap()))
|
||||||
|
n := r.cache.Copy(rawBytes)
|
||||||
|
b.Resize(0, int32(n))
|
||||||
|
r.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cachedReader) readInternal() buf.MultiBuffer {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
if r.cache != nil && !r.cache.IsEmpty() {
|
||||||
|
mb := r.cache
|
||||||
|
r.cache = nil
|
||||||
|
return mb
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
|
mb := r.readInternal()
|
||||||
|
if mb != nil {
|
||||||
|
return mb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.reader.ReadMultiBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) {
|
||||||
|
mb := r.readInternal()
|
||||||
|
if mb != nil {
|
||||||
|
return mb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.reader.ReadMultiBufferTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *cachedReader) Interrupt() {
|
||||||
|
r.Lock()
|
||||||
|
if r.cache != nil {
|
||||||
|
r.cache = buf.ReleaseMulti(r.cache)
|
||||||
|
}
|
||||||
|
r.Unlock()
|
||||||
|
if p, ok := r.reader.(*pipe.Reader); ok {
|
||||||
|
p.Interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultDispatcher is a default implementation of Dispatcher.
|
||||||
|
type DefaultDispatcher struct {
|
||||||
|
ohm outbound.Manager
|
||||||
|
router routing.Router
|
||||||
|
policy policy.Manager
|
||||||
|
stats stats.Manager
|
||||||
|
fdns dns.FakeDNSEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
d := new(DefaultDispatcher)
|
||||||
|
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
||||||
|
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||||
|
d.fdns = fdns
|
||||||
|
})
|
||||||
|
return d.Init(config.(*Config), om, router, pm, sm)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes DefaultDispatcher.
|
||||||
|
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
|
||||||
|
d.ohm = om
|
||||||
|
d.router = router
|
||||||
|
d.policy = pm
|
||||||
|
d.stats = sm
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (*DefaultDispatcher) Type() interface{} {
|
||||||
|
return routing.DispatcherType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (*DefaultDispatcher) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (*DefaultDispatcher) Close() error { return nil }
|
||||||
|
|
||||||
|
func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link) {
|
||||||
|
opt := pipe.OptionsFromContext(ctx)
|
||||||
|
uplinkReader, uplinkWriter := pipe.New(opt...)
|
||||||
|
downlinkReader, downlinkWriter := pipe.New(opt...)
|
||||||
|
|
||||||
|
inboundLink := &transport.Link{
|
||||||
|
Reader: downlinkReader,
|
||||||
|
Writer: uplinkWriter,
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundLink := &transport.Link{
|
||||||
|
Reader: uplinkReader,
|
||||||
|
Writer: downlinkWriter,
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionInbound := session.InboundFromContext(ctx)
|
||||||
|
var user *protocol.MemoryUser
|
||||||
|
if sessionInbound != nil {
|
||||||
|
user = sessionInbound.User
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil && len(user.Email) > 0 {
|
||||||
|
p := d.policy.ForLevel(user.Level)
|
||||||
|
if p.Stats.UserUplink {
|
||||||
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
|
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||||
|
inboundLink.Writer = &SizeStatWriter{
|
||||||
|
Counter: c,
|
||||||
|
Writer: inboundLink.Writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Stats.UserDownlink {
|
||||||
|
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||||
|
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||||
|
outboundLink.Writer = &SizeStatWriter{
|
||||||
|
Counter: c,
|
||||||
|
Writer: outboundLink.Writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Stats.UserOnline {
|
||||||
|
name := "user>>>" + user.Email + ">>>online"
|
||||||
|
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
|
||||||
|
userIP := sessionInbound.Source.Address.String()
|
||||||
|
om.AddIP(userIP)
|
||||||
|
context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inboundLink, outboundLink
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager stats.Manager, link *transport.Link) *transport.Link {
|
||||||
|
sessionInbound := session.InboundFromContext(ctx)
|
||||||
|
var user *protocol.MemoryUser
|
||||||
|
if sessionInbound != nil {
|
||||||
|
user = sessionInbound.User
|
||||||
|
}
|
||||||
|
|
||||||
|
link.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader}
|
||||||
|
|
||||||
|
if user != nil && len(user.Email) > 0 {
|
||||||
|
p := policyManager.ForLevel(user.Level)
|
||||||
|
if p.Stats.UserUplink {
|
||||||
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
|
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||||
|
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Stats.UserDownlink {
|
||||||
|
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||||
|
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||||
|
link.Writer = &SizeStatWriter{
|
||||||
|
Counter: c,
|
||||||
|
Writer: link.Writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Stats.UserOnline {
|
||||||
|
name := "user>>>" + user.Email + ">>>online"
|
||||||
|
if om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil {
|
||||||
|
userIP := sessionInbound.Source.Address.String()
|
||||||
|
om.AddIP(userIP)
|
||||||
|
context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
||||||
|
domain := result.Domain()
|
||||||
|
if domain == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, d := range request.ExcludeForDomain {
|
||||||
|
if strings.HasPrefix(d, "regexp:") {
|
||||||
|
pattern := d[7:]
|
||||||
|
re, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfo(ctx, "Unable to compile regex")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if re.MatchString(domain) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.ToLower(domain) == d {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protocolString := result.Protocol()
|
||||||
|
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||||
|
protocolString = resComp.ProtocolForDomainResult()
|
||||||
|
}
|
||||||
|
for _, p := range request.OverrideDestinationForProtocol {
|
||||||
|
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||||
|
fkr0.IsIPInIPPool(destination.Address) {
|
||||||
|
errors.LogInfo(ctx, "Using sniffer ", protocolString, " since the fake DNS missed")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||||
|
if resultSubset.IsProtoSubsetOf(p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch implements routing.Dispatcher.
|
||||||
|
func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
|
||||||
|
if !destination.IsValid() {
|
||||||
|
panic("Dispatcher: Invalid destination.")
|
||||||
|
}
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
if len(outbounds) == 0 {
|
||||||
|
outbounds = []*session.Outbound{{}}
|
||||||
|
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||||
|
}
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
ob.OriginalTarget = destination
|
||||||
|
ob.Target = destination
|
||||||
|
content := session.ContentFromContext(ctx)
|
||||||
|
if content == nil {
|
||||||
|
content = new(session.Content)
|
||||||
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
sniffingRequest := content.SniffingRequest
|
||||||
|
inbound, outbound := d.getLink(ctx)
|
||||||
|
if !sniffingRequest.Enabled {
|
||||||
|
go d.routedDispatch(ctx, outbound, destination)
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
cReader := &cachedReader{
|
||||||
|
reader: outbound.Reader.(*pipe.Reader),
|
||||||
|
}
|
||||||
|
outbound.Reader = cReader
|
||||||
|
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||||
|
if err == nil {
|
||||||
|
content.Protocol = result.Protocol()
|
||||||
|
}
|
||||||
|
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
|
domain := result.Domain()
|
||||||
|
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||||
|
destination.Address = net.ParseAddress(domain)
|
||||||
|
protocol := result.Protocol()
|
||||||
|
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||||
|
protocol = resComp.ProtocolForDomainResult()
|
||||||
|
}
|
||||||
|
isFakeIP := false
|
||||||
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(ob.Target.Address) {
|
||||||
|
isFakeIP = true
|
||||||
|
}
|
||||||
|
if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP {
|
||||||
|
ob.RouteTarget = destination
|
||||||
|
} else {
|
||||||
|
ob.Target = destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.routedDispatch(ctx, outbound, destination)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DispatchLink implements routing.Dispatcher.
|
||||||
|
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
||||||
|
if !destination.IsValid() {
|
||||||
|
return errors.New("Dispatcher: Invalid destination.")
|
||||||
|
}
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
if len(outbounds) == 0 {
|
||||||
|
outbounds = []*session.Outbound{{}}
|
||||||
|
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||||
|
}
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
ob.OriginalTarget = destination
|
||||||
|
ob.Target = destination
|
||||||
|
content := session.ContentFromContext(ctx)
|
||||||
|
if content == nil {
|
||||||
|
content = new(session.Content)
|
||||||
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
|
}
|
||||||
|
outbound = WrapLink(ctx, d.policy, d.stats, outbound)
|
||||||
|
sniffingRequest := content.SniffingRequest
|
||||||
|
if !sniffingRequest.Enabled {
|
||||||
|
d.routedDispatch(ctx, outbound, destination)
|
||||||
|
} else {
|
||||||
|
cReader := &cachedReader{
|
||||||
|
reader: outbound.Reader.(buf.TimeoutReader),
|
||||||
|
}
|
||||||
|
outbound.Reader = cReader
|
||||||
|
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||||
|
if err == nil {
|
||||||
|
content.Protocol = result.Protocol()
|
||||||
|
}
|
||||||
|
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
|
domain := result.Domain()
|
||||||
|
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||||
|
destination.Address = net.ParseAddress(domain)
|
||||||
|
protocol := result.Protocol()
|
||||||
|
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||||
|
protocol = resComp.ProtocolForDomainResult()
|
||||||
|
}
|
||||||
|
isFakeIP := false
|
||||||
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(ob.Target.Address) {
|
||||||
|
isFakeIP = true
|
||||||
|
}
|
||||||
|
if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP {
|
||||||
|
ob.RouteTarget = destination
|
||||||
|
} else {
|
||||||
|
ob.Target = destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.routedDispatch(ctx, outbound, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
||||||
|
payload := buf.NewWithSize(32767)
|
||||||
|
defer payload.Release()
|
||||||
|
|
||||||
|
sniffer := NewSniffer(ctx)
|
||||||
|
|
||||||
|
metaresult, metadataErr := sniffer.SniffMetadata(ctx)
|
||||||
|
|
||||||
|
if metadataOnly {
|
||||||
|
return metaresult, metadataErr
|
||||||
|
}
|
||||||
|
|
||||||
|
contentResult, contentErr := func() (SniffResult, error) {
|
||||||
|
cacheDeadline := 200 * time.Millisecond
|
||||||
|
totalAttempt := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
cachingStartingTimeStamp := time.Now()
|
||||||
|
err := cReader.Cache(payload, cacheDeadline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
|
||||||
|
cacheDeadline -= cachingTimeElapsed
|
||||||
|
|
||||||
|
if !payload.IsEmpty() {
|
||||||
|
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||||
|
switch err {
|
||||||
|
case common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
|
||||||
|
totalAttempt++
|
||||||
|
case protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing
|
||||||
|
// in this case, do not add totalAttempt(allow to read until timeout)
|
||||||
|
default:
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
totalAttempt++
|
||||||
|
}
|
||||||
|
if totalAttempt >= 2 || cacheDeadline <= 0 {
|
||||||
|
return nil, errSniffingTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if contentErr != nil && metadataErr == nil {
|
||||||
|
return metaresult, nil
|
||||||
|
}
|
||||||
|
if contentErr == nil && metadataErr == nil {
|
||||||
|
return CompositeResult(metaresult, contentResult), nil
|
||||||
|
}
|
||||||
|
return contentResult, contentErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
|
||||||
|
var handler outbound.Handler
|
||||||
|
|
||||||
|
routingLink := routing_session.AsRoutingContext(ctx)
|
||||||
|
inTag := routingLink.GetInboundTag()
|
||||||
|
isPickRoute := 0
|
||||||
|
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {
|
||||||
|
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||||
|
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||||
|
isPickRoute = 1
|
||||||
|
errors.LogInfo(ctx, "taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]")
|
||||||
|
handler = h
|
||||||
|
} else {
|
||||||
|
errors.LogError(ctx, "non existing tag for platform initialized detour: ", forcedOutboundTag)
|
||||||
|
common.Close(link.Writer)
|
||||||
|
common.Interrupt(link.Reader)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if d.router != nil {
|
||||||
|
if route, err := d.router.PickRoute(routingLink); err == nil {
|
||||||
|
outTag := route.GetOutboundTag()
|
||||||
|
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||||
|
isPickRoute = 2
|
||||||
|
if route.GetRuleTag() == "" {
|
||||||
|
errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]")
|
||||||
|
} else {
|
||||||
|
errors.LogInfo(ctx, "Hit route rule: [", route.GetRuleTag(), "] so taking detour [", outTag, "] for [", destination, "]")
|
||||||
|
}
|
||||||
|
handler = h
|
||||||
|
} else {
|
||||||
|
errors.LogWarning(ctx, "non existing outTag: ", outTag)
|
||||||
|
common.Close(link.Writer)
|
||||||
|
common.Interrupt(link.Reader)
|
||||||
|
return // DO NOT CHANGE: the traffic shouldn't be processed by default outbound if the specified outbound tag doesn't exist (yet), e.g., VLESS Reverse Proxy
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.LogInfo(ctx, "default route for ", destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler == nil {
|
||||||
|
handler = d.ohm.GetDefaultHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler == nil {
|
||||||
|
errors.LogInfo(ctx, "default outbound handler not exist")
|
||||||
|
common.Close(link.Writer)
|
||||||
|
common.Interrupt(link.Reader)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ob.Tag = handler.Tag()
|
||||||
|
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
||||||
|
if tag := handler.Tag(); tag != "" {
|
||||||
|
if inTag == "" {
|
||||||
|
accessMessage.Detour = tag
|
||||||
|
} else if isPickRoute == 1 {
|
||||||
|
accessMessage.Detour = inTag + " ==> " + tag
|
||||||
|
} else if isPickRoute == 2 {
|
||||||
|
accessMessage.Detour = inTag + " -> " + tag
|
||||||
|
} else {
|
||||||
|
accessMessage.Detour = inTag + " >> " + tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Record(accessMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.Dispatch(ctx, link)
|
||||||
|
}
|
||||||
1
subproject/Xray-core-main/app/dispatcher/dispatcher.go
Normal file
1
subproject/Xray-core-main/app/dispatcher/dispatcher.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package dispatcher
|
||||||
121
subproject/Xray-core-main/app/dispatcher/fakednssniffer.go
Normal file
121
subproject/Xray-core-main/app/dispatcher/fakednssniffer.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newFakeDNSSniffer Creates a Fake DNS metadata sniffer
|
||||||
|
func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {
|
||||||
|
var fakeDNSEngine dns.FakeDNSEngine
|
||||||
|
{
|
||||||
|
fakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil))
|
||||||
|
if fakeDNSEngineFeat != nil {
|
||||||
|
fakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fakeDNSEngine == nil {
|
||||||
|
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||||
|
return protocolSnifferWithMetadata{}, errNotInit
|
||||||
|
}
|
||||||
|
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
if ob.Target.Network == net.Network_TCP || ob.Target.Network == net.Network_UDP {
|
||||||
|
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(ob.Target.Address)
|
||||||
|
if domainFromFakeDNS != "" {
|
||||||
|
errors.LogInfo(ctx, "fake dns got domain: ", domainFromFakeDNS, " for ip: ", ob.Target.Address.String())
|
||||||
|
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
|
||||||
|
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
|
||||||
|
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||||
|
inPool := fkr0.IsIPInIPPool(ob.Target.Address)
|
||||||
|
ipAddressInRangeValue.addressInRange = &inPool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}, metadataSniffer: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeDNSSniffResult struct {
|
||||||
|
domainName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fakeDNSSniffResult) Protocol() string {
|
||||||
|
return "fakedns"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeDNSSniffResult) Domain() string {
|
||||||
|
return f.domainName
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeDNSExtraOpts int
|
||||||
|
|
||||||
|
const ipAddressInRange fakeDNSExtraOpts = 1
|
||||||
|
|
||||||
|
type ipAddressInRangeOpt struct {
|
||||||
|
addressInRange *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSThenOthersSniffResult struct {
|
||||||
|
domainName string
|
||||||
|
protocolOriginalName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {
|
||||||
|
return strings.HasPrefix(protocolName, f.protocolOriginalName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DNSThenOthersSniffResult) Protocol() string {
|
||||||
|
return "fakedns+others"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DNSThenOthersSniffResult) Domain() string {
|
||||||
|
return f.domainName
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (
|
||||||
|
protocolSnifferWithMetadata, error,
|
||||||
|
) { // nolint: unparam
|
||||||
|
// ctx may be used in the future
|
||||||
|
_ = ctx
|
||||||
|
return protocolSnifferWithMetadata{
|
||||||
|
protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||||
|
ipAddressInRangeValue := &ipAddressInRangeOpt{}
|
||||||
|
ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)
|
||||||
|
result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)
|
||||||
|
if err == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if ipAddressInRangeValue.addressInRange != nil {
|
||||||
|
if *ipAddressInRangeValue.addressInRange {
|
||||||
|
for _, v := range others {
|
||||||
|
if v.metadataSniffer || bytes != nil {
|
||||||
|
if result, err := v.protocolSniffer(ctx, bytes); err == nil {
|
||||||
|
return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
errors.LogDebug(ctx, "ip address not in fake dns range, return as is")
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
errors.LogWarning(ctx, "fake dns sniffer did not set address in range option, assume false.")
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
},
|
||||||
|
metadataSniffer: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
142
subproject/Xray-core-main/app/dispatcher/sniffer.go
Normal file
142
subproject/Xray-core-main/app/dispatcher/sniffer.go
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/http"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/quic"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SniffResult interface {
|
||||||
|
Protocol() string
|
||||||
|
Domain() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocolSniffer func(context.Context, []byte) (SniffResult, error)
|
||||||
|
|
||||||
|
type protocolSnifferWithMetadata struct {
|
||||||
|
protocolSniffer protocolSniffer
|
||||||
|
// A Metadata sniffer will be invoked on connection establishment only, with nil body,
|
||||||
|
// for both TCP and UDP connections
|
||||||
|
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
|
||||||
|
metadataSniffer bool
|
||||||
|
network net.Network
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sniffer struct {
|
||||||
|
sniffer []protocolSnifferWithMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSniffer(ctx context.Context) *Sniffer {
|
||||||
|
ret := &Sniffer{
|
||||||
|
sniffer: []protocolSnifferWithMetadata{
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b, c) }, false, net.Network_TCP},
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
||||||
|
others := ret.sniffer
|
||||||
|
ret.sniffer = append(ret.sniffer, sniffer)
|
||||||
|
fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)
|
||||||
|
if err == nil {
|
||||||
|
ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
var errUnknownContent = errors.New("unknown content")
|
||||||
|
|
||||||
|
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||||
|
var pendingSniffer []protocolSnifferWithMetadata
|
||||||
|
for _, si := range s.sniffer {
|
||||||
|
protocolSniffer := si.protocolSniffer
|
||||||
|
if si.metadataSniffer || si.network != network {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result, err := protocolSniffer(c, payload)
|
||||||
|
if err == common.ErrNoClue {
|
||||||
|
pendingSniffer = append(pendingSniffer, si)
|
||||||
|
continue
|
||||||
|
} else if err == protocol.ErrProtoNeedMoreData { // Sniffer protocol matched, but need more data to complete sniffing
|
||||||
|
s.sniffer = []protocolSnifferWithMetadata{si}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && result != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pendingSniffer) > 0 {
|
||||||
|
s.sniffer = pendingSniffer
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errUnknownContent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) {
|
||||||
|
var pendingSniffer []protocolSnifferWithMetadata
|
||||||
|
for _, si := range s.sniffer {
|
||||||
|
s := si.protocolSniffer
|
||||||
|
if !si.metadataSniffer {
|
||||||
|
pendingSniffer = append(pendingSniffer, si)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result, err := s(c, nil)
|
||||||
|
if err == common.ErrNoClue {
|
||||||
|
pendingSniffer = append(pendingSniffer, si)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && result != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pendingSniffer) > 0 {
|
||||||
|
s.sniffer = pendingSniffer
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errUnknownContent
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult {
|
||||||
|
return &compositeResult{domainResult: domainResult, protocolResult: protocolResult}
|
||||||
|
}
|
||||||
|
|
||||||
|
type compositeResult struct {
|
||||||
|
domainResult SniffResult
|
||||||
|
protocolResult SniffResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c compositeResult) Protocol() string {
|
||||||
|
return c.protocolResult.Protocol()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c compositeResult) Domain() string {
|
||||||
|
return c.domainResult.Domain()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c compositeResult) ProtocolForDomainResult() string {
|
||||||
|
return c.domainResult.Protocol()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnifferResultComposite interface {
|
||||||
|
ProtocolForDomainResult() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnifferIsProtoSubsetOf interface {
|
||||||
|
IsProtoSubsetOf(protocolName string) bool
|
||||||
|
}
|
||||||
25
subproject/Xray-core-main/app/dispatcher/stats.go
Normal file
25
subproject/Xray-core-main/app/dispatcher/stats.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package dispatcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SizeStatWriter struct {
|
||||||
|
Counter stats.Counter
|
||||||
|
Writer buf.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
|
w.Counter.Add(int64(mb.Len()))
|
||||||
|
return w.Writer.WriteMultiBuffer(mb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SizeStatWriter) Close() error {
|
||||||
|
return common.Close(w.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *SizeStatWriter) Interrupt() {
|
||||||
|
common.Interrupt(w.Writer)
|
||||||
|
}
|
||||||
44
subproject/Xray-core-main/app/dispatcher/stats_test.go
Normal file
44
subproject/Xray-core-main/app/dispatcher/stats_test.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package dispatcher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dispatcher"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestCounter int64
|
||||||
|
|
||||||
|
func (c *TestCounter) Value() int64 {
|
||||||
|
return int64(*c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TestCounter) Add(v int64) int64 {
|
||||||
|
x := int64(*c) + v
|
||||||
|
*c = TestCounter(x)
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TestCounter) Set(v int64) int64 {
|
||||||
|
*c = TestCounter(v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatsWriter(t *testing.T) {
|
||||||
|
var c TestCounter
|
||||||
|
writer := &SizeStatWriter{
|
||||||
|
Counter: &c,
|
||||||
|
Writer: buf.Discard,
|
||||||
|
}
|
||||||
|
|
||||||
|
mb := buf.MergeBytes(nil, []byte("abcd"))
|
||||||
|
common.Must(writer.WriteMultiBuffer(mb))
|
||||||
|
|
||||||
|
mb = buf.MergeBytes(nil, []byte("efg"))
|
||||||
|
common.Must(writer.WriteMultiBuffer(mb))
|
||||||
|
|
||||||
|
if c.Value() != 7 {
|
||||||
|
t.Fatal("unexpected counter value. want 7, but got ", c.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
339
subproject/Xray-core-main/app/dns/cache_controller.go
Normal file
339
subproject/Xray-core-main/app/dns/cache_controller.go
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
go_errors "errors"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minSizeForEmptyRebuild = 512
|
||||||
|
shrinkAbsoluteThreshold = 10240
|
||||||
|
shrinkRatioThreshold = 0.65
|
||||||
|
migrationBatchSize = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
type CacheController struct {
|
||||||
|
name string
|
||||||
|
disableCache bool
|
||||||
|
serveStale bool
|
||||||
|
serveExpiredTTL int32
|
||||||
|
|
||||||
|
ips map[string]*record
|
||||||
|
dirtyips map[string]*record
|
||||||
|
|
||||||
|
sync.RWMutex
|
||||||
|
pub *pubsub.Service
|
||||||
|
cacheCleanup *task.Periodic
|
||||||
|
highWatermark int
|
||||||
|
requestGroup singleflight.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCacheController(name string, disableCache bool, serveStale bool, serveExpiredTTL uint32) *CacheController {
|
||||||
|
c := &CacheController{
|
||||||
|
name: name,
|
||||||
|
disableCache: disableCache,
|
||||||
|
serveStale: serveStale,
|
||||||
|
serveExpiredTTL: -int32(serveExpiredTTL),
|
||||||
|
ips: make(map[string]*record),
|
||||||
|
pub: pubsub.NewService(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cacheCleanup = &task.Periodic{
|
||||||
|
Interval: 300 * time.Second,
|
||||||
|
Execute: c.CacheCleanup,
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheCleanup clears expired items from cache
|
||||||
|
func (c *CacheController) CacheCleanup() error {
|
||||||
|
expiredKeys, err := c.collectExpiredKeys()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(expiredKeys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.writeAndShrink(expiredKeys)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) collectExpiredKeys() ([]string, error) {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
if len(c.ips) == 0 {
|
||||||
|
return nil, errors.New("nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip collection if a migration is in progress
|
||||||
|
if c.dirtyips != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if c.serveStale && c.serveExpiredTTL != 0 {
|
||||||
|
now = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
expiredKeys := make([]string, 0, len(c.ips)/4) // pre-allocate
|
||||||
|
|
||||||
|
for domain, rec := range c.ips {
|
||||||
|
if (rec.A != nil && rec.A.Expire.Before(now)) ||
|
||||||
|
(rec.AAAA != nil && rec.AAAA.Expire.Before(now)) {
|
||||||
|
expiredKeys = append(expiredKeys, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expiredKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
// double check to prevent upper call multiple cleanup tasks
|
||||||
|
if c.dirtyips != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lenBefore := len(c.ips)
|
||||||
|
if lenBefore > c.highWatermark {
|
||||||
|
c.highWatermark = lenBefore
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if c.serveStale && c.serveExpiredTTL != 0 {
|
||||||
|
now = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range expiredKeys {
|
||||||
|
rec := c.ips[domain]
|
||||||
|
if rec == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rec.A != nil && rec.A.Expire.Before(now) {
|
||||||
|
rec.A = nil
|
||||||
|
}
|
||||||
|
if rec.AAAA != nil && rec.AAAA.Expire.Before(now) {
|
||||||
|
rec.AAAA = nil
|
||||||
|
}
|
||||||
|
if rec.A == nil && rec.AAAA == nil {
|
||||||
|
delete(c.ips, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lenAfter := len(c.ips)
|
||||||
|
|
||||||
|
if lenAfter == 0 {
|
||||||
|
if c.highWatermark >= minSizeForEmptyRebuild {
|
||||||
|
errors.LogDebug(context.Background(), c.name,
|
||||||
|
" rebuilding empty cache map to reclaim memory.",
|
||||||
|
" size_before_cleanup=", lenBefore,
|
||||||
|
" peak_size_before_rebuild=", c.highWatermark,
|
||||||
|
)
|
||||||
|
|
||||||
|
c.ips = make(map[string]*record)
|
||||||
|
c.highWatermark = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
|
||||||
|
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
|
||||||
|
errors.LogDebug(context.Background(), c.name,
|
||||||
|
" shrinking cache map to reclaim memory.",
|
||||||
|
" new_size=", lenAfter,
|
||||||
|
" peak_size_before_shrink=", c.highWatermark,
|
||||||
|
" reduction_since_peak=", reductionFromPeak,
|
||||||
|
)
|
||||||
|
|
||||||
|
c.dirtyips = c.ips
|
||||||
|
c.ips = make(map[string]*record, int(float64(lenAfter)*1.1))
|
||||||
|
c.highWatermark = lenAfter
|
||||||
|
go c.migrate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type migrationEntry struct {
|
||||||
|
key string
|
||||||
|
value *record
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) migrate() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
errors.LogError(context.Background(), c.name, " panic during cache migration: ", r)
|
||||||
|
c.Lock()
|
||||||
|
c.dirtyips = nil
|
||||||
|
// c.ips = make(map[string]*record)
|
||||||
|
// c.highWatermark = 0
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.RLock()
|
||||||
|
dirtyips := c.dirtyips
|
||||||
|
c.RUnlock()
|
||||||
|
|
||||||
|
// double check to prevent upper call multiple cleanup tasks
|
||||||
|
if dirtyips == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogDebug(context.Background(), c.name, " starting background cache migration for ", len(dirtyips), " items")
|
||||||
|
|
||||||
|
batch := make([]migrationEntry, 0, migrationBatchSize)
|
||||||
|
for domain, recD := range dirtyips {
|
||||||
|
batch = append(batch, migrationEntry{domain, recD})
|
||||||
|
|
||||||
|
if len(batch) >= migrationBatchSize {
|
||||||
|
c.flush(batch)
|
||||||
|
batch = batch[:0]
|
||||||
|
runtime.Gosched()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(batch) > 0 {
|
||||||
|
c.flush(batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
c.dirtyips = nil
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
errors.LogDebug(context.Background(), c.name, " cache migration completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) flush(batch []migrationEntry) {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
for _, dirty := range batch {
|
||||||
|
if cur := c.ips[dirty.key]; cur != nil {
|
||||||
|
merge := &record{}
|
||||||
|
if cur.A == nil {
|
||||||
|
merge.A = dirty.value.A
|
||||||
|
} else {
|
||||||
|
merge.A = cur.A
|
||||||
|
}
|
||||||
|
if cur.AAAA == nil {
|
||||||
|
merge.AAAA = dirty.value.AAAA
|
||||||
|
} else {
|
||||||
|
merge.AAAA = cur.AAAA
|
||||||
|
}
|
||||||
|
c.ips[dirty.key] = merge
|
||||||
|
} else {
|
||||||
|
c.ips[dirty.key] = dirty.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) updateRecord(req *dnsRequest, rep *IPRecord) {
|
||||||
|
rtt := time.Since(req.start)
|
||||||
|
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
c.pub.Publish(req.domain+"4", rep)
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
c.pub.Publish(req.domain+"6", rep)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.disableCache {
|
||||||
|
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", rep.IP, ", rtt: ", rtt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
lockWait := time.Since(req.start) - rtt
|
||||||
|
|
||||||
|
newRec := &record{}
|
||||||
|
oldRec := c.ips[req.domain]
|
||||||
|
var dirtyRec *record
|
||||||
|
if c.dirtyips != nil {
|
||||||
|
dirtyRec = c.dirtyips[req.domain]
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubRecord *IPRecord
|
||||||
|
var pubSuffix string
|
||||||
|
|
||||||
|
switch req.reqType {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
newRec.A = rep
|
||||||
|
if oldRec != nil && oldRec.AAAA != nil {
|
||||||
|
newRec.AAAA = oldRec.AAAA
|
||||||
|
pubRecord = oldRec.AAAA
|
||||||
|
} else if dirtyRec != nil && dirtyRec.AAAA != nil {
|
||||||
|
pubRecord = dirtyRec.AAAA
|
||||||
|
}
|
||||||
|
pubSuffix = "6"
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
newRec.AAAA = rep
|
||||||
|
if oldRec != nil && oldRec.A != nil {
|
||||||
|
newRec.A = oldRec.A
|
||||||
|
pubRecord = oldRec.A
|
||||||
|
} else if dirtyRec != nil && dirtyRec.A != nil {
|
||||||
|
pubRecord = dirtyRec.A
|
||||||
|
}
|
||||||
|
pubSuffix = "4"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ips[req.domain] = newRec
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
if pubRecord != nil {
|
||||||
|
_, ttl, err := pubRecord.getIPs()
|
||||||
|
if ttl > 0 && !go_errors.Is(err, errRecordNotFound) {
|
||||||
|
c.pub.Publish(req.domain+pubSuffix, pubRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", rep.IP, ", rtt: ", rtt, ", lock: ", lockWait)
|
||||||
|
|
||||||
|
if !c.serveStale || c.serveExpiredTTL != 0 {
|
||||||
|
common.Must(c.cacheCleanup.Start())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) findRecords(domain string) *record {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
rec := c.ips[domain]
|
||||||
|
if rec == nil && c.dirtyips != nil {
|
||||||
|
rec = c.dirtyips[domain]
|
||||||
|
}
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
|
||||||
|
// ipv4 and ipv6 belong to different subscription groups
|
||||||
|
if option.IPv4Enable {
|
||||||
|
sub4 = c.pub.Subscribe(domain + "4")
|
||||||
|
}
|
||||||
|
if option.IPv6Enable {
|
||||||
|
sub6 = c.pub.Subscribe(domain + "6")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeSubscribers(sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
|
||||||
|
if sub4 != nil {
|
||||||
|
sub4.Close()
|
||||||
|
}
|
||||||
|
if sub6 != nil {
|
||||||
|
sub6.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
64
subproject/Xray-core-main/app/dns/config.go
Normal file
64
subproject/Xray-core-main/app/dns/config.go
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeMap = map[DomainMatchingType]strmatcher.Type{
|
||||||
|
DomainMatchingType_Full: strmatcher.Full,
|
||||||
|
DomainMatchingType_Subdomain: strmatcher.Domain,
|
||||||
|
DomainMatchingType_Keyword: strmatcher.Substr,
|
||||||
|
DomainMatchingType_Regex: strmatcher.Regex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// References:
|
||||||
|
// https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml
|
||||||
|
// https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan
|
||||||
|
var localTLDsAndDotlessDomains = []*NameServer_PriorityDomain{
|
||||||
|
{Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "localhost"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "lan"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "home.arpa"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "example"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "invalid"},
|
||||||
|
{Type: DomainMatchingType_Subdomain, Domain: "test"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
|
||||||
|
Rule: "geosite:private",
|
||||||
|
Size: uint32(len(localTLDsAndDotlessDomains)),
|
||||||
|
}
|
||||||
|
|
||||||
|
func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {
|
||||||
|
strMType, f := typeMap[t]
|
||||||
|
if !f {
|
||||||
|
return nil, errors.New("unknown mapping type", t).AtWarning()
|
||||||
|
}
|
||||||
|
matcher, err := strMType.New(domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to create str matcher").Base(err)
|
||||||
|
}
|
||||||
|
return matcher, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||||
|
ips := make([]net.IP, 0, len(addrs))
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if addr.Family().IsIP() {
|
||||||
|
ips = append(ips, addr.IP())
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Failed to convert address", addr, "to Net IP.").AtWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomTag() string {
|
||||||
|
id := uuid.New()
|
||||||
|
return "xray.system." + id.String()
|
||||||
|
}
|
||||||
748
subproject/Xray-core-main/app/dns/config.pb.go
Normal file
748
subproject/Xray-core-main/app/dns/config.pb.go
Normal file
|
|
@ -0,0 +1,748 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/dns/config.proto
|
||||||
|
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
router "github.com/xtls/xray-core/app/router"
|
||||||
|
net "github.com/xtls/xray-core/common/net"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type DomainMatchingType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DomainMatchingType_Full DomainMatchingType = 0
|
||||||
|
DomainMatchingType_Subdomain DomainMatchingType = 1
|
||||||
|
DomainMatchingType_Keyword DomainMatchingType = 2
|
||||||
|
DomainMatchingType_Regex DomainMatchingType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for DomainMatchingType.
|
||||||
|
var (
|
||||||
|
DomainMatchingType_name = map[int32]string{
|
||||||
|
0: "Full",
|
||||||
|
1: "Subdomain",
|
||||||
|
2: "Keyword",
|
||||||
|
3: "Regex",
|
||||||
|
}
|
||||||
|
DomainMatchingType_value = map[string]int32{
|
||||||
|
"Full": 0,
|
||||||
|
"Subdomain": 1,
|
||||||
|
"Keyword": 2,
|
||||||
|
"Regex": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x DomainMatchingType) Enum() *DomainMatchingType {
|
||||||
|
p := new(DomainMatchingType)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DomainMatchingType) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_app_dns_config_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DomainMatchingType) Type() protoreflect.EnumType {
|
||||||
|
return &file_app_dns_config_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x DomainMatchingType) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DomainMatchingType.Descriptor instead.
|
||||||
|
func (DomainMatchingType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryStrategy int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
QueryStrategy_USE_IP QueryStrategy = 0
|
||||||
|
QueryStrategy_USE_IP4 QueryStrategy = 1
|
||||||
|
QueryStrategy_USE_IP6 QueryStrategy = 2
|
||||||
|
QueryStrategy_USE_SYS QueryStrategy = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for QueryStrategy.
|
||||||
|
var (
|
||||||
|
QueryStrategy_name = map[int32]string{
|
||||||
|
0: "USE_IP",
|
||||||
|
1: "USE_IP4",
|
||||||
|
2: "USE_IP6",
|
||||||
|
3: "USE_SYS",
|
||||||
|
}
|
||||||
|
QueryStrategy_value = map[string]int32{
|
||||||
|
"USE_IP": 0,
|
||||||
|
"USE_IP4": 1,
|
||||||
|
"USE_IP6": 2,
|
||||||
|
"USE_SYS": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x QueryStrategy) Enum() *QueryStrategy {
|
||||||
|
p := new(QueryStrategy)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x QueryStrategy) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_app_dns_config_proto_enumTypes[1].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (QueryStrategy) Type() protoreflect.EnumType {
|
||||||
|
return &file_app_dns_config_proto_enumTypes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use QueryStrategy.Descriptor instead.
|
||||||
|
func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameServer struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||||
|
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||||
|
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||||
|
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
|
||||||
|
ExpectedGeoip []*router.GeoIP `protobuf:"bytes,3,rep,name=expected_geoip,json=expectedGeoip,proto3" json:"expected_geoip,omitempty"`
|
||||||
|
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||||
|
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||||
|
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
|
||||||
|
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
|
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
||||||
|
DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
|
||||||
|
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
|
||||||
|
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
|
||||||
|
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
|
||||||
|
UnexpectedGeoip []*router.GeoIP `protobuf:"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3" json:"unexpected_geoip,omitempty"`
|
||||||
|
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
|
||||||
|
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) Reset() {
|
||||||
|
*x = NameServer{}
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NameServer) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NameServer) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NameServer.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NameServer) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetAddress() *net.Endpoint {
|
||||||
|
if x != nil {
|
||||||
|
return x.Address
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetClientIp() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientIp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetSkipFallback() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.SkipFallback
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
||||||
|
if x != nil {
|
||||||
|
return x.PrioritizedDomain
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetExpectedGeoip() []*router.GeoIP {
|
||||||
|
if x != nil {
|
||||||
|
return x.ExpectedGeoip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule {
|
||||||
|
if x != nil {
|
||||||
|
return x.OriginalRules
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetQueryStrategy() QueryStrategy {
|
||||||
|
if x != nil {
|
||||||
|
return x.QueryStrategy
|
||||||
|
}
|
||||||
|
return QueryStrategy_USE_IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetActPrior() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.ActPrior
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetTag() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tag
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetTimeoutMs() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.TimeoutMs
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetDisableCache() bool {
|
||||||
|
if x != nil && x.DisableCache != nil {
|
||||||
|
return *x.DisableCache
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetServeStale() bool {
|
||||||
|
if x != nil && x.ServeStale != nil {
|
||||||
|
return *x.ServeStale
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetServeExpiredTTL() uint32 {
|
||||||
|
if x != nil && x.ServeExpiredTTL != nil {
|
||||||
|
return *x.ServeExpiredTTL
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetFinalQuery() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.FinalQuery
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {
|
||||||
|
if x != nil {
|
||||||
|
return x.UnexpectedGeoip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetActUnprior() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.ActUnprior
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer) GetPolicyID() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.PolicyID
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// NameServer list used by this DNS client.
|
||||||
|
// A special value 'localhost' as a domain address can be set to use DNS on local system.
|
||||||
|
NameServer []*NameServer `protobuf:"bytes,5,rep,name=name_server,json=nameServer,proto3" json:"name_server,omitempty"`
|
||||||
|
// Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes
|
||||||
|
// (IPv6).
|
||||||
|
ClientIp []byte `protobuf:"bytes,3,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||||
|
StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"`
|
||||||
|
// Tag is the inbound tag of DNS client.
|
||||||
|
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
|
// DisableCache disables DNS cache
|
||||||
|
DisableCache bool `protobuf:"varint,8,opt,name=disableCache,proto3" json:"disableCache,omitempty"`
|
||||||
|
ServeStale bool `protobuf:"varint,12,opt,name=serveStale,proto3" json:"serveStale,omitempty"`
|
||||||
|
ServeExpiredTTL uint32 `protobuf:"varint,13,opt,name=serveExpiredTTL,proto3" json:"serveExpiredTTL,omitempty"`
|
||||||
|
QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||||
|
DisableFallback bool `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"`
|
||||||
|
DisableFallbackIfMatch bool `protobuf:"varint,11,opt,name=disableFallbackIfMatch,proto3" json:"disableFallbackIfMatch,omitempty"`
|
||||||
|
EnableParallelQuery bool `protobuf:"varint,14,opt,name=enableParallelQuery,proto3" json:"enableParallelQuery,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetNameServer() []*NameServer {
|
||||||
|
if x != nil {
|
||||||
|
return x.NameServer
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetClientIp() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientIp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetStaticHosts() []*Config_HostMapping {
|
||||||
|
if x != nil {
|
||||||
|
return x.StaticHosts
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetTag() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tag
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableCache() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableCache
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetServeStale() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServeStale
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetServeExpiredTTL() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ServeExpiredTTL
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetQueryStrategy() QueryStrategy {
|
||||||
|
if x != nil {
|
||||||
|
return x.QueryStrategy
|
||||||
|
}
|
||||||
|
return QueryStrategy_USE_IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableFallback() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableFallback
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetDisableFallbackIfMatch() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableFallbackIfMatch
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEnableParallelQuery() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.EnableParallelQuery
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameServer_PriorityDomain struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
|
||||||
|
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_PriorityDomain) Reset() {
|
||||||
|
*x = NameServer_PriorityDomain{}
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_PriorityDomain) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NameServer_PriorityDomain) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_PriorityDomain) GetType() DomainMatchingType {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return DomainMatchingType_Full
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_PriorityDomain) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameServer_OriginalRule struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Rule string `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"`
|
||||||
|
Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_OriginalRule) Reset() {
|
||||||
|
*x = NameServer_OriginalRule{}
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_OriginalRule) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NameServer_OriginalRule) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_OriginalRule) GetRule() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Rule
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NameServer_OriginalRule) GetSize() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Size
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config_HostMapping struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
|
||||||
|
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||||
|
Ip [][]byte `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
||||||
|
// domain. Xray will use this domain for IP queries.
|
||||||
|
ProxiedDomain string `protobuf:"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3" json:"proxied_domain,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) Reset() {
|
||||||
|
*x = Config_HostMapping{}
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config_HostMapping) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config_HostMapping.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config_HostMapping) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) GetType() DomainMatchingType {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return DomainMatchingType_Full
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) GetDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) GetIp() [][]byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config_HostMapping) GetProxiedDomain() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProxiedDomain
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_dns_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_dns_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x14app/dns/config.proto\x12\fxray.app.dns\x1a\x1ccommon/net/destination.proto\x1a\x17app/router/config.proto\"\xdf\a\n" +
|
||||||
|
"\n" +
|
||||||
|
"NameServer\x123\n" +
|
||||||
|
"\aaddress\x18\x01 \x01(\v2\x19.xray.common.net.EndpointR\aaddress\x12\x1b\n" +
|
||||||
|
"\tclient_ip\x18\x05 \x01(\fR\bclientIp\x12\"\n" +
|
||||||
|
"\fskipFallback\x18\x06 \x01(\bR\fskipFallback\x12V\n" +
|
||||||
|
"\x12prioritized_domain\x18\x02 \x03(\v2'.xray.app.dns.NameServer.PriorityDomainR\x11prioritizedDomain\x12=\n" +
|
||||||
|
"\x0eexpected_geoip\x18\x03 \x03(\v2\x16.xray.app.router.GeoIPR\rexpectedGeoip\x12L\n" +
|
||||||
|
"\x0eoriginal_rules\x18\x04 \x03(\v2%.xray.app.dns.NameServer.OriginalRuleR\roriginalRules\x12B\n" +
|
||||||
|
"\x0equery_strategy\x18\a \x01(\x0e2\x1b.xray.app.dns.QueryStrategyR\rqueryStrategy\x12\x1a\n" +
|
||||||
|
"\bactPrior\x18\b \x01(\bR\bactPrior\x12\x10\n" +
|
||||||
|
"\x03tag\x18\t \x01(\tR\x03tag\x12\x1c\n" +
|
||||||
|
"\ttimeoutMs\x18\n" +
|
||||||
|
" \x01(\x04R\ttimeoutMs\x12'\n" +
|
||||||
|
"\fdisableCache\x18\v \x01(\bH\x00R\fdisableCache\x88\x01\x01\x12#\n" +
|
||||||
|
"\n" +
|
||||||
|
"serveStale\x18\x0f \x01(\bH\x01R\n" +
|
||||||
|
"serveStale\x88\x01\x01\x12-\n" +
|
||||||
|
"\x0fserveExpiredTTL\x18\x10 \x01(\rH\x02R\x0fserveExpiredTTL\x88\x01\x01\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"finalQuery\x18\f \x01(\bR\n" +
|
||||||
|
"finalQuery\x12A\n" +
|
||||||
|
"\x10unexpected_geoip\x18\r \x03(\v2\x16.xray.app.router.GeoIPR\x0funexpectedGeoip\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"actUnprior\x18\x0e \x01(\bR\n" +
|
||||||
|
"actUnprior\x12\x1a\n" +
|
||||||
|
"\bpolicyID\x18\x11 \x01(\rR\bpolicyID\x1a^\n" +
|
||||||
|
"\x0ePriorityDomain\x124\n" +
|
||||||
|
"\x04type\x18\x01 \x01(\x0e2 .xray.app.dns.DomainMatchingTypeR\x04type\x12\x16\n" +
|
||||||
|
"\x06domain\x18\x02 \x01(\tR\x06domain\x1a6\n" +
|
||||||
|
"\fOriginalRule\x12\x12\n" +
|
||||||
|
"\x04rule\x18\x01 \x01(\tR\x04rule\x12\x12\n" +
|
||||||
|
"\x04size\x18\x02 \x01(\rR\x04sizeB\x0f\n" +
|
||||||
|
"\r_disableCacheB\r\n" +
|
||||||
|
"\v_serveStaleB\x12\n" +
|
||||||
|
"\x10_serveExpiredTTL\"\x98\x05\n" +
|
||||||
|
"\x06Config\x129\n" +
|
||||||
|
"\vname_server\x18\x05 \x03(\v2\x18.xray.app.dns.NameServerR\n" +
|
||||||
|
"nameServer\x12\x1b\n" +
|
||||||
|
"\tclient_ip\x18\x03 \x01(\fR\bclientIp\x12C\n" +
|
||||||
|
"\fstatic_hosts\x18\x04 \x03(\v2 .xray.app.dns.Config.HostMappingR\vstaticHosts\x12\x10\n" +
|
||||||
|
"\x03tag\x18\x06 \x01(\tR\x03tag\x12\"\n" +
|
||||||
|
"\fdisableCache\x18\b \x01(\bR\fdisableCache\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"serveStale\x18\f \x01(\bR\n" +
|
||||||
|
"serveStale\x12(\n" +
|
||||||
|
"\x0fserveExpiredTTL\x18\r \x01(\rR\x0fserveExpiredTTL\x12B\n" +
|
||||||
|
"\x0equery_strategy\x18\t \x01(\x0e2\x1b.xray.app.dns.QueryStrategyR\rqueryStrategy\x12(\n" +
|
||||||
|
"\x0fdisableFallback\x18\n" +
|
||||||
|
" \x01(\bR\x0fdisableFallback\x126\n" +
|
||||||
|
"\x16disableFallbackIfMatch\x18\v \x01(\bR\x16disableFallbackIfMatch\x120\n" +
|
||||||
|
"\x13enableParallelQuery\x18\x0e \x01(\bR\x13enableParallelQuery\x1a\x92\x01\n" +
|
||||||
|
"\vHostMapping\x124\n" +
|
||||||
|
"\x04type\x18\x01 \x01(\x0e2 .xray.app.dns.DomainMatchingTypeR\x04type\x12\x16\n" +
|
||||||
|
"\x06domain\x18\x02 \x01(\tR\x06domain\x12\x0e\n" +
|
||||||
|
"\x02ip\x18\x03 \x03(\fR\x02ip\x12%\n" +
|
||||||
|
"\x0eproxied_domain\x18\x04 \x01(\tR\rproxiedDomainJ\x04\b\a\x10\b*E\n" +
|
||||||
|
"\x12DomainMatchingType\x12\b\n" +
|
||||||
|
"\x04Full\x10\x00\x12\r\n" +
|
||||||
|
"\tSubdomain\x10\x01\x12\v\n" +
|
||||||
|
"\aKeyword\x10\x02\x12\t\n" +
|
||||||
|
"\x05Regex\x10\x03*B\n" +
|
||||||
|
"\rQueryStrategy\x12\n" +
|
||||||
|
"\n" +
|
||||||
|
"\x06USE_IP\x10\x00\x12\v\n" +
|
||||||
|
"\aUSE_IP4\x10\x01\x12\v\n" +
|
||||||
|
"\aUSE_IP6\x10\x02\x12\v\n" +
|
||||||
|
"\aUSE_SYS\x10\x03BF\n" +
|
||||||
|
"\x10com.xray.app.dnsP\x01Z!github.com/xtls/xray-core/app/dns\xaa\x02\fXray.App.Dnsb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_dns_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_dns_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_dns_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_dns_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dns_config_proto_rawDesc), len(file_app_dns_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_dns_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
|
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
|
var file_app_dns_config_proto_goTypes = []any{
|
||||||
|
(DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType
|
||||||
|
(QueryStrategy)(0), // 1: xray.app.dns.QueryStrategy
|
||||||
|
(*NameServer)(nil), // 2: xray.app.dns.NameServer
|
||||||
|
(*Config)(nil), // 3: xray.app.dns.Config
|
||||||
|
(*NameServer_PriorityDomain)(nil), // 4: xray.app.dns.NameServer.PriorityDomain
|
||||||
|
(*NameServer_OriginalRule)(nil), // 5: xray.app.dns.NameServer.OriginalRule
|
||||||
|
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
||||||
|
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
||||||
|
(*router.GeoIP)(nil), // 8: xray.app.router.GeoIP
|
||||||
|
}
|
||||||
|
var file_app_dns_config_proto_depIdxs = []int32{
|
||||||
|
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||||
|
4, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain
|
||||||
|
8, // 2: xray.app.dns.NameServer.expected_geoip:type_name -> xray.app.router.GeoIP
|
||||||
|
5, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
|
||||||
|
1, // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||||
|
8, // 5: xray.app.dns.NameServer.unexpected_geoip:type_name -> xray.app.router.GeoIP
|
||||||
|
2, // 6: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||||
|
6, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||||
|
1, // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||||
|
0, // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
|
||||||
|
0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
||||||
|
11, // [11:11] is the sub-list for method output_type
|
||||||
|
11, // [11:11] is the sub-list for method input_type
|
||||||
|
11, // [11:11] is the sub-list for extension type_name
|
||||||
|
11, // [11:11] is the sub-list for extension extendee
|
||||||
|
0, // [0:11] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_dns_config_proto_init() }
|
||||||
|
func file_app_dns_config_proto_init() {
|
||||||
|
if File_app_dns_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file_app_dns_config_proto_msgTypes[0].OneofWrappers = []any{}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dns_config_proto_rawDesc), len(file_app_dns_config_proto_rawDesc)),
|
||||||
|
NumEnums: 2,
|
||||||
|
NumMessages: 5,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_dns_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_dns_config_proto_depIdxs,
|
||||||
|
EnumInfos: file_app_dns_config_proto_enumTypes,
|
||||||
|
MessageInfos: file_app_dns_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_dns_config_proto = out.File
|
||||||
|
file_app_dns_config_proto_goTypes = nil
|
||||||
|
file_app_dns_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
95
subproject/Xray-core-main/app/dns/config.proto
Normal file
95
subproject/Xray-core-main/app/dns/config.proto
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.dns;
|
||||||
|
option csharp_namespace = "Xray.App.Dns";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/dns";
|
||||||
|
option java_package = "com.xray.app.dns";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "common/net/destination.proto";
|
||||||
|
import "app/router/config.proto";
|
||||||
|
|
||||||
|
message NameServer {
|
||||||
|
xray.common.net.Endpoint address = 1;
|
||||||
|
bytes client_ip = 5;
|
||||||
|
bool skipFallback = 6;
|
||||||
|
|
||||||
|
message PriorityDomain {
|
||||||
|
DomainMatchingType type = 1;
|
||||||
|
string domain = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OriginalRule {
|
||||||
|
string rule = 1;
|
||||||
|
uint32 size = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated PriorityDomain prioritized_domain = 2;
|
||||||
|
repeated xray.app.router.GeoIP expected_geoip = 3;
|
||||||
|
repeated OriginalRule original_rules = 4;
|
||||||
|
QueryStrategy query_strategy = 7;
|
||||||
|
bool actPrior = 8;
|
||||||
|
string tag = 9;
|
||||||
|
uint64 timeoutMs = 10;
|
||||||
|
optional bool disableCache = 11;
|
||||||
|
optional bool serveStale = 15;
|
||||||
|
optional uint32 serveExpiredTTL = 16;
|
||||||
|
bool finalQuery = 12;
|
||||||
|
repeated xray.app.router.GeoIP unexpected_geoip = 13;
|
||||||
|
bool actUnprior = 14;
|
||||||
|
uint32 policyID = 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DomainMatchingType {
|
||||||
|
Full = 0;
|
||||||
|
Subdomain = 1;
|
||||||
|
Keyword = 2;
|
||||||
|
Regex = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum QueryStrategy {
|
||||||
|
USE_IP = 0;
|
||||||
|
USE_IP4 = 1;
|
||||||
|
USE_IP6 = 2;
|
||||||
|
USE_SYS = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
// NameServer list used by this DNS client.
|
||||||
|
// A special value 'localhost' as a domain address can be set to use DNS on local system.
|
||||||
|
repeated NameServer name_server = 5;
|
||||||
|
|
||||||
|
// Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes
|
||||||
|
// (IPv6).
|
||||||
|
bytes client_ip = 3;
|
||||||
|
|
||||||
|
message HostMapping {
|
||||||
|
DomainMatchingType type = 1;
|
||||||
|
string domain = 2;
|
||||||
|
|
||||||
|
repeated bytes ip = 3;
|
||||||
|
|
||||||
|
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
||||||
|
// domain. Xray will use this domain for IP queries.
|
||||||
|
string proxied_domain = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated HostMapping static_hosts = 4;
|
||||||
|
|
||||||
|
// Tag is the inbound tag of DNS client.
|
||||||
|
string tag = 6;
|
||||||
|
|
||||||
|
reserved 7;
|
||||||
|
|
||||||
|
// DisableCache disables DNS cache
|
||||||
|
bool disableCache = 8;
|
||||||
|
bool serveStale = 12;
|
||||||
|
uint32 serveExpiredTTL = 13;
|
||||||
|
|
||||||
|
QueryStrategy query_strategy = 9;
|
||||||
|
|
||||||
|
bool disableFallback = 10;
|
||||||
|
bool disableFallbackIfMatch = 11;
|
||||||
|
|
||||||
|
bool enableParallelQuery = 14;
|
||||||
|
}
|
||||||
606
subproject/Xray-core-main/app/dns/dns.go
Normal file
606
subproject/Xray-core-main/app/dns/dns.go
Normal file
|
|
@ -0,0 +1,606 @@
|
||||||
|
// Package dns is an implementation of core.DNS feature.
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
go_errors "errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/platform"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNS is a DNS rely server.
|
||||||
|
type DNS struct {
|
||||||
|
sync.Mutex
|
||||||
|
disableFallback bool
|
||||||
|
disableFallbackIfMatch bool
|
||||||
|
enableParallelQuery bool
|
||||||
|
ipOption *dns.IPOption
|
||||||
|
hosts *StaticHosts
|
||||||
|
clients []*Client
|
||||||
|
ctx context.Context
|
||||||
|
domainMatcher strmatcher.IndexMatcher
|
||||||
|
matcherInfos []*DomainMatcherInfo
|
||||||
|
checkSystem bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
||||||
|
type DomainMatcherInfo struct {
|
||||||
|
clientIdx uint16
|
||||||
|
domainRuleIdx uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new DNS server with given configuration.
|
||||||
|
func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||||
|
var clientIP net.IP
|
||||||
|
switch len(config.ClientIp) {
|
||||||
|
case 0, net.IPv4len, net.IPv6len:
|
||||||
|
clientIP = net.IP(config.ClientIp)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unexpected client IP length ", len(config.ClientIp))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipOption dns.IPOption
|
||||||
|
checkSystem := false
|
||||||
|
switch config.QueryStrategy {
|
||||||
|
case QueryStrategy_USE_IP:
|
||||||
|
ipOption = dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_SYS:
|
||||||
|
ipOption = dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
checkSystem = true
|
||||||
|
case QueryStrategy_USE_IP4:
|
||||||
|
ipOption = dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_IP6:
|
||||||
|
ipOption = dns.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hosts *StaticHosts
|
||||||
|
mphLoaded := false
|
||||||
|
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||||
|
if domainMatcherPath != "" {
|
||||||
|
if f, err := os.Open(domainMatcherPath); err == nil {
|
||||||
|
defer f.Close()
|
||||||
|
if m, err := router.LoadGeoSiteMatcher(f, "HOSTS"); err == nil {
|
||||||
|
f.Seek(0, 0)
|
||||||
|
if hostIPs, err := router.LoadGeoSiteHosts(f); err == nil {
|
||||||
|
if sh, err := NewStaticHostsFromCache(m, hostIPs); err == nil {
|
||||||
|
hosts = sh
|
||||||
|
mphLoaded = true
|
||||||
|
errors.LogDebug(ctx, "MphDomainMatcher loaded from cache for DNS hosts, size: ", sh.matchers.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mphLoaded {
|
||||||
|
sh, err := NewStaticHosts(config.StaticHosts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to create hosts").Base(err)
|
||||||
|
}
|
||||||
|
hosts = sh
|
||||||
|
}
|
||||||
|
|
||||||
|
var clients []*Client
|
||||||
|
domainRuleCount := 0
|
||||||
|
|
||||||
|
var defaultTag = config.Tag
|
||||||
|
if len(config.Tag) == 0 {
|
||||||
|
defaultTag = generateRandomTag()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ns := range config.NameServer {
|
||||||
|
domainRuleCount += len(ns.PrioritizedDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
||||||
|
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
|
||||||
|
domainMatcher := &strmatcher.MatcherGroup{}
|
||||||
|
|
||||||
|
for _, ns := range config.NameServer {
|
||||||
|
clientIdx := len(clients)
|
||||||
|
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {
|
||||||
|
midx := domainMatcher.Add(domainRule)
|
||||||
|
matcherInfos[midx] = &DomainMatcherInfo{
|
||||||
|
clientIdx: uint16(clientIdx),
|
||||||
|
domainRuleIdx: uint16(originalRuleIdx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myClientIP := clientIP
|
||||||
|
switch len(ns.ClientIp) {
|
||||||
|
case net.IPv4len, net.IPv6len:
|
||||||
|
myClientIP = net.IP(ns.ClientIp)
|
||||||
|
}
|
||||||
|
|
||||||
|
disableCache := config.DisableCache
|
||||||
|
if ns.DisableCache != nil {
|
||||||
|
disableCache = *ns.DisableCache
|
||||||
|
}
|
||||||
|
|
||||||
|
serveStale := config.ServeStale
|
||||||
|
if ns.ServeStale != nil {
|
||||||
|
serveStale = *ns.ServeStale
|
||||||
|
}
|
||||||
|
|
||||||
|
serveExpiredTTL := config.ServeExpiredTTL
|
||||||
|
if ns.ServeExpiredTTL != nil {
|
||||||
|
serveExpiredTTL = *ns.ServeExpiredTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
var tag = defaultTag
|
||||||
|
if len(ns.Tag) > 0 {
|
||||||
|
tag = ns.Tag
|
||||||
|
}
|
||||||
|
clientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)
|
||||||
|
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
|
||||||
|
return nil, errors.New("no QueryStrategy available for ", ns.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, &matcherInfos, updateDomain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to create client").Base(err)
|
||||||
|
}
|
||||||
|
clients = append(clients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no DNS client in config, add a `localhost` DNS client
|
||||||
|
if len(clients) == 0 {
|
||||||
|
clients = append(clients, NewLocalDNSClient(ipOption))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DNS{
|
||||||
|
hosts: hosts,
|
||||||
|
ipOption: &ipOption,
|
||||||
|
clients: clients,
|
||||||
|
ctx: ctx,
|
||||||
|
domainMatcher: domainMatcher,
|
||||||
|
matcherInfos: matcherInfos,
|
||||||
|
disableFallback: config.DisableFallback,
|
||||||
|
disableFallbackIfMatch: config.DisableFallbackIfMatch,
|
||||||
|
enableParallelQuery: config.EnableParallelQuery,
|
||||||
|
checkSystem: checkSystem,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (*DNS) Type() interface{} {
|
||||||
|
return dns.ClientType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (s *DNS) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (s *DNS) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOwnLink implements proxy.dns.ownLinkVerifier
|
||||||
|
func (s *DNS) IsOwnLink(ctx context.Context) bool {
|
||||||
|
inbound := session.InboundFromContext(ctx)
|
||||||
|
if inbound == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, client := range s.clients {
|
||||||
|
if client.tag == inbound.Tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIP implements dns.Client.
|
||||||
|
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
// Normalize the FQDN form query
|
||||||
|
domain = strings.TrimSuffix(domain, ".")
|
||||||
|
if domain == "" {
|
||||||
|
return nil, 0, errors.New("empty domain name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.checkSystem {
|
||||||
|
supportIPv4, supportIPv6 := checkRoutes()
|
||||||
|
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||||
|
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||||
|
} else {
|
||||||
|
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
|
||||||
|
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
|
||||||
|
}
|
||||||
|
|
||||||
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static host lookup
|
||||||
|
switch addrs, err := s.hosts.Lookup(domain, option); {
|
||||||
|
case err != nil:
|
||||||
|
if go_errors.Is(err, dns.ErrEmptyResponse) {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
return nil, 0, errors.New("returning nil for domain ", domain).Base(err)
|
||||||
|
case addrs == nil: // Domain not recorded in static host
|
||||||
|
break
|
||||||
|
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
|
||||||
|
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain())
|
||||||
|
domain = addrs[0].Domain()
|
||||||
|
default: // Successfully found ip records in static host
|
||||||
|
errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs)
|
||||||
|
ips, err := toNetIP(addrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return ips, 10, nil // Hosts ttl is 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name servers lookup
|
||||||
|
if s.enableParallelQuery {
|
||||||
|
return s.parallelQuery(domain, option)
|
||||||
|
} else {
|
||||||
|
return s.serialQuery(domain, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNS) sortClients(domain string) []*Client {
|
||||||
|
clients := make([]*Client, 0, len(s.clients))
|
||||||
|
clientUsed := make([]bool, len(s.clients))
|
||||||
|
clientNames := make([]string, 0, len(s.clients))
|
||||||
|
domainRules := []string{}
|
||||||
|
|
||||||
|
// Priority domain matching
|
||||||
|
hasMatch := false
|
||||||
|
MatchSlice := s.domainMatcher.Match(domain)
|
||||||
|
sort.Slice(MatchSlice, func(i, j int) bool {
|
||||||
|
return MatchSlice[i] < MatchSlice[j]
|
||||||
|
})
|
||||||
|
for _, match := range MatchSlice {
|
||||||
|
info := s.matcherInfos[match]
|
||||||
|
client := s.clients[info.clientIdx]
|
||||||
|
domainRule := client.domains[info.domainRuleIdx]
|
||||||
|
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||||
|
if clientUsed[info.clientIdx] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clientUsed[info.clientIdx] = true
|
||||||
|
clients = append(clients, client)
|
||||||
|
clientNames = append(clientNames, client.Name())
|
||||||
|
hasMatch = true
|
||||||
|
if client.finalQuery {
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) {
|
||||||
|
// Default round-robin query
|
||||||
|
for idx, client := range s.clients {
|
||||||
|
if clientUsed[idx] || client.skipFallback {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clientUsed[idx] = true
|
||||||
|
clients = append(clients, client)
|
||||||
|
clientNames = append(clientNames, client.Name())
|
||||||
|
if client.finalQuery {
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(domainRules) > 0 {
|
||||||
|
errors.LogDebug(s.ctx, "domain ", domain, " matches following rules: ", domainRules)
|
||||||
|
}
|
||||||
|
if len(clientNames) > 0 {
|
||||||
|
errors.LogDebug(s.ctx, "domain ", domain, " will use DNS in order: ", clientNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clients) == 0 {
|
||||||
|
if len(s.clients) > 0 {
|
||||||
|
clients = append(clients, s.clients[0])
|
||||||
|
clientNames = append(clientNames, s.clients[0].Name())
|
||||||
|
errors.LogWarning(s.ctx, "domain ", domain, " will use the first DNS: ", clientNames)
|
||||||
|
} else {
|
||||||
|
errors.LogError(s.ctx, "no DNS clients available for domain ", domain, " and no default clients configured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeQueryErrors(domain string, errs []error) error {
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
var noRNF error
|
||||||
|
for _, err := range errs {
|
||||||
|
if go_errors.Is(err, errRecordNotFound) {
|
||||||
|
continue // server no response, ignore
|
||||||
|
} else if noRNF == nil {
|
||||||
|
noRNF = err
|
||||||
|
} else if !go_errors.Is(err, noRNF) {
|
||||||
|
return errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if go_errors.Is(noRNF, dns.ErrEmptyResponse) {
|
||||||
|
return dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
if noRNF == nil {
|
||||||
|
noRNF = errRecordNotFound
|
||||||
|
}
|
||||||
|
return errors.New("returning nil for domain ", domain).Base(noRNF)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNS) serialQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
var errs []error
|
||||||
|
for _, client := range s.sortClients(domain) {
|
||||||
|
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||||
|
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, ttl, err := client.QueryIP(s.ctx, domain, option)
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return ips, ttl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name(), " in serial query mode")
|
||||||
|
if err == nil {
|
||||||
|
err = dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
return nil, 0, mergeQueryErrors(domain, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNS) parallelQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
var errs []error
|
||||||
|
clients := s.sortClients(domain)
|
||||||
|
|
||||||
|
resultsChan := asyncQueryAll(domain, option, clients, s.ctx)
|
||||||
|
|
||||||
|
groups, groupOf := makeGroups( /*s.ctx,*/ clients)
|
||||||
|
results := make([]*queryResult, len(clients))
|
||||||
|
pending := make([]int, len(groups))
|
||||||
|
for gi, g := range groups {
|
||||||
|
pending[gi] = g.end - g.start + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
nextGroup := 0
|
||||||
|
for range clients {
|
||||||
|
result := <-resultsChan
|
||||||
|
results[result.index] = &result
|
||||||
|
|
||||||
|
gi := groupOf[result.index]
|
||||||
|
pending[gi]--
|
||||||
|
|
||||||
|
for nextGroup < len(groups) {
|
||||||
|
g := groups[nextGroup]
|
||||||
|
|
||||||
|
// group race, minimum rtt -> return
|
||||||
|
for j := g.start; j <= g.end; j++ {
|
||||||
|
r := results[j]
|
||||||
|
if r != nil && r.err == nil && len(r.ips) > 0 {
|
||||||
|
return r.ips, r.ttl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// current group is incomplete and no one success -> continue pending
|
||||||
|
if pending[nextGroup] > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// all failed -> log and continue next group
|
||||||
|
for j := g.start; j <= g.end; j++ {
|
||||||
|
r := results[j]
|
||||||
|
e := r.err
|
||||||
|
if e == nil {
|
||||||
|
e = dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
errors.LogInfoInner(s.ctx, e, "failed to lookup ip for domain ", domain, " at server ", clients[j].Name(), " in parallel query mode")
|
||||||
|
errs = append(errs, e)
|
||||||
|
}
|
||||||
|
nextGroup++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, mergeQueryErrors(domain, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryResult struct {
|
||||||
|
ips []net.IP
|
||||||
|
ttl uint32
|
||||||
|
err error
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncQueryAll(domain string, option dns.IPOption, clients []*Client, ctx context.Context) chan queryResult {
|
||||||
|
if len(clients) == 0 {
|
||||||
|
ch := make(chan queryResult)
|
||||||
|
close(ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan queryResult, len(clients))
|
||||||
|
for i, client := range clients {
|
||||||
|
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
|
||||||
|
errors.LogDebug(ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
|
||||||
|
ch <- queryResult{err: dns.ErrEmptyResponse, index: i}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(i int, c *Client) {
|
||||||
|
qctx := ctx
|
||||||
|
if !c.server.IsDisableCache() {
|
||||||
|
nctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), c.timeoutMs*2)
|
||||||
|
qctx = nctx
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
ips, ttl, err := c.QueryIP(qctx, domain, option)
|
||||||
|
ch <- queryResult{ips: ips, ttl: ttl, err: err, index: i}
|
||||||
|
}(i, client)
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
type group struct{ start, end int }
|
||||||
|
|
||||||
|
// merge only adjacent and rule-equivalent Client into a single group
|
||||||
|
func makeGroups( /*ctx context.Context,*/ clients []*Client) ([]group, []int) {
|
||||||
|
n := len(clients)
|
||||||
|
if n == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
groups := make([]group, 0, n)
|
||||||
|
groupOf := make([]int, n)
|
||||||
|
|
||||||
|
s, e := 0, 0
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
if clients[i-1].policyID == clients[i].policyID {
|
||||||
|
e = i
|
||||||
|
} else {
|
||||||
|
for k := s; k <= e; k++ {
|
||||||
|
groupOf[k] = len(groups)
|
||||||
|
}
|
||||||
|
groups = append(groups, group{start: s, end: e})
|
||||||
|
s, e = i, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k := s; k <= e; k++ {
|
||||||
|
groupOf[k] = len(groups)
|
||||||
|
}
|
||||||
|
groups = append(groups, group{start: s, end: e})
|
||||||
|
|
||||||
|
// var b strings.Builder
|
||||||
|
// b.WriteString("dns grouping: total clients=")
|
||||||
|
// b.WriteString(strconv.Itoa(n))
|
||||||
|
// b.WriteString(", groups=")
|
||||||
|
// b.WriteString(strconv.Itoa(len(groups)))
|
||||||
|
|
||||||
|
// for gi, g := range groups {
|
||||||
|
// b.WriteString("\n [")
|
||||||
|
// b.WriteString(strconv.Itoa(g.start))
|
||||||
|
// b.WriteString("..")
|
||||||
|
// b.WriteString(strconv.Itoa(g.end))
|
||||||
|
// b.WriteString("] gid=")
|
||||||
|
// b.WriteString(strconv.Itoa(gi))
|
||||||
|
// b.WriteString(" pid=")
|
||||||
|
// b.WriteString(strconv.FormatUint(uint64(clients[g.start].policyID), 10))
|
||||||
|
// b.WriteString(" members: ")
|
||||||
|
|
||||||
|
// for i := g.start; i <= g.end; i++ {
|
||||||
|
// if i > g.start {
|
||||||
|
// b.WriteString(", ")
|
||||||
|
// }
|
||||||
|
// b.WriteString(strconv.Itoa(i))
|
||||||
|
// b.WriteByte(':')
|
||||||
|
// b.WriteString(clients[i].Name())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// errors.LogDebug(ctx, b.String())
|
||||||
|
|
||||||
|
return groups, groupOf
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func probeRoutes() (ipv4 bool, ipv6 bool) {
|
||||||
|
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
|
||||||
|
ipv4 = true
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
|
||||||
|
ipv6 = true
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeCache struct {
|
||||||
|
sync.Once
|
||||||
|
sync.RWMutex
|
||||||
|
expire time.Time
|
||||||
|
ipv4, ipv6 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRoutes() (bool, bool) {
|
||||||
|
if !isGUIPlatform {
|
||||||
|
routeCache.Once.Do(func() {
|
||||||
|
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
|
||||||
|
})
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
routeCache.RWMutex.RLock()
|
||||||
|
now := time.Now()
|
||||||
|
if routeCache.expire.After(now) {
|
||||||
|
routeCache.RWMutex.RUnlock()
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
routeCache.RWMutex.RUnlock()
|
||||||
|
|
||||||
|
routeCache.RWMutex.Lock()
|
||||||
|
defer routeCache.RWMutex.Unlock()
|
||||||
|
|
||||||
|
now = time.Now()
|
||||||
|
if routeCache.expire.After(now) { // double-check
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
|
||||||
|
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
|
||||||
|
return routeCache.ipv4, routeCache.ipv6
|
||||||
|
}
|
||||||
|
|
||||||
|
var isGUIPlatform = detectGUIPlatform()
|
||||||
|
|
||||||
|
func detectGUIPlatform() bool {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "android", "ios", "windows", "darwin":
|
||||||
|
return true
|
||||||
|
case "linux", "freebsd", "openbsd":
|
||||||
|
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
1065
subproject/Xray-core-main/app/dns/dns_test.go
Normal file
1065
subproject/Xray-core-main/app/dns/dns_test.go
Normal file
File diff suppressed because it is too large
Load diff
263
subproject/Xray-core-main/app/dns/dnscommon.go
Normal file
263
subproject/Xray-core-main/app/dns/dnscommon.go
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fqdn normalizes domain make sure it ends with '.'
|
||||||
|
// case-sensitive
|
||||||
|
func Fqdn(domain string) string {
|
||||||
|
if len(domain) > 0 && strings.HasSuffix(domain, ".") {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
return domain + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
type record struct {
|
||||||
|
A *IPRecord
|
||||||
|
AAAA *IPRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPRecord is a cacheable item for a resolved domain
|
||||||
|
type IPRecord struct {
|
||||||
|
ReqID uint16
|
||||||
|
IP []net.IP
|
||||||
|
Expire time.Time
|
||||||
|
RCode dnsmessage.RCode
|
||||||
|
RawHeader *dnsmessage.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IPRecord) getIPs() ([]net.IP, int32, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, 0, errRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
untilExpire := time.Until(r.Expire).Seconds()
|
||||||
|
ttl := int32(math.Ceil(untilExpire))
|
||||||
|
|
||||||
|
if r.RCode != dnsmessage.RCodeSuccess {
|
||||||
|
return nil, ttl, dns_feature.RCodeError(r.RCode)
|
||||||
|
}
|
||||||
|
if len(r.IP) == 0 {
|
||||||
|
return nil, ttl, dns_feature.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.IP, ttl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errRecordNotFound = errors.New("record not found")
|
||||||
|
|
||||||
|
type dnsRequest struct {
|
||||||
|
reqType dnsmessage.Type
|
||||||
|
domain string
|
||||||
|
start time.Time
|
||||||
|
expire time.Time
|
||||||
|
msg *dnsmessage.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
||||||
|
if len(clientIP) == 0 && padding == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const EDNS0SUBNET = 0x8
|
||||||
|
const EDNS0PADDING = 0xc
|
||||||
|
|
||||||
|
opt := new(dnsmessage.Resource)
|
||||||
|
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
||||||
|
body := dnsmessage.OPTResource{}
|
||||||
|
opt.Body = &body
|
||||||
|
|
||||||
|
if len(clientIP) != 0 {
|
||||||
|
var netmask int
|
||||||
|
var family uint16
|
||||||
|
|
||||||
|
if len(clientIP) == 4 {
|
||||||
|
family = 1
|
||||||
|
netmask = 24 // 24 for IPV4, 96 for IPv6
|
||||||
|
} else {
|
||||||
|
family = 2
|
||||||
|
netmask = 96
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint16(b[0:], family)
|
||||||
|
b[2] = byte(netmask)
|
||||||
|
b[3] = 0
|
||||||
|
switch family {
|
||||||
|
case 1:
|
||||||
|
ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8))
|
||||||
|
needLength := (netmask + 8 - 1) / 8 // division rounding up
|
||||||
|
b = append(b, ip[:needLength]...)
|
||||||
|
case 2:
|
||||||
|
ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8))
|
||||||
|
needLength := (netmask + 8 - 1) / 8 // division rounding up
|
||||||
|
b = append(b, ip[:needLength]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
body.Options = append(body.Options,
|
||||||
|
dnsmessage.Option{
|
||||||
|
Code: EDNS0SUBNET,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if padding != 0 {
|
||||||
|
body.Options = append(body.Options,
|
||||||
|
dnsmessage.Option{
|
||||||
|
Code: EDNS0PADDING,
|
||||||
|
Data: make([]byte, padding),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
|
||||||
|
qA := dnsmessage.Question{
|
||||||
|
Name: dnsmessage.MustNewName(domain),
|
||||||
|
Type: dnsmessage.TypeA,
|
||||||
|
Class: dnsmessage.ClassINET,
|
||||||
|
}
|
||||||
|
|
||||||
|
qAAAA := dnsmessage.Question{
|
||||||
|
Name: dnsmessage.MustNewName(domain),
|
||||||
|
Type: dnsmessage.TypeAAAA,
|
||||||
|
Class: dnsmessage.ClassINET,
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqs []*dnsRequest
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
msg := new(dnsmessage.Message)
|
||||||
|
msg.Header.ID = reqIDGen()
|
||||||
|
msg.Header.RecursionDesired = true
|
||||||
|
msg.Questions = []dnsmessage.Question{qA}
|
||||||
|
if reqOpts != nil {
|
||||||
|
msg.Additionals = append(msg.Additionals, *reqOpts)
|
||||||
|
}
|
||||||
|
reqs = append(reqs, &dnsRequest{
|
||||||
|
reqType: dnsmessage.TypeA,
|
||||||
|
domain: domain,
|
||||||
|
start: now,
|
||||||
|
msg: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
msg := new(dnsmessage.Message)
|
||||||
|
msg.Header.ID = reqIDGen()
|
||||||
|
msg.Header.RecursionDesired = true
|
||||||
|
msg.Questions = []dnsmessage.Question{qAAAA}
|
||||||
|
if reqOpts != nil {
|
||||||
|
msg.Additionals = append(msg.Additionals, *reqOpts)
|
||||||
|
}
|
||||||
|
reqs = append(reqs, &dnsRequest{
|
||||||
|
reqType: dnsmessage.TypeAAAA,
|
||||||
|
domain: domain,
|
||||||
|
start: now,
|
||||||
|
msg: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return reqs
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseResponse parses DNS answers from the returned payload
|
||||||
|
func parseResponse(payload []byte) (*IPRecord, error) {
|
||||||
|
var parser dnsmessage.Parser
|
||||||
|
h, err := parser.Start(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to parse DNS response").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
if err := parser.SkipAllQuestions(); err != nil {
|
||||||
|
return nil, errors.New("failed to skip questions in DNS response").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
ipRecord := &IPRecord{
|
||||||
|
ReqID: h.ID,
|
||||||
|
RCode: h.RCode,
|
||||||
|
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
|
||||||
|
RawHeader: &h,
|
||||||
|
}
|
||||||
|
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
ah, err := parser.AnswerHeader()
|
||||||
|
if err != nil {
|
||||||
|
if err != dnsmessage.ErrSectionDone {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to parse answer section for domain: ", ah.Name.String())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl := ah.TTL
|
||||||
|
if ttl == 0 {
|
||||||
|
ttl = 1
|
||||||
|
}
|
||||||
|
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||||
|
if ipRecord.Expire.After(expire) {
|
||||||
|
ipRecord.Expire = expire
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ah.Type {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
ans, err := parser.AResource()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to parse A record for domain: ", ah.Name)
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]).IP())
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
ans, err := parser.AAAAResource()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to parse AAAA record for domain: ", ah.Name)
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
newIP := net.IPAddress(ans.AAAA[:]).IP()
|
||||||
|
if len(newIP) == net.IPv6len {
|
||||||
|
ipRecord.IP = append(ipRecord.IP, newIP)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := parser.SkipAnswer(); err != nil {
|
||||||
|
errors.LogInfoInner(context.Background(), err, "failed to skip answer")
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipRecord, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toDnsContext create a new background context with parent inbound, session and dns log
|
||||||
|
func toDnsContext(ctx context.Context, addr string) context.Context {
|
||||||
|
dnsCtx := core.ToBackgroundDetachedContext(ctx)
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, session.ContentFromContext(ctx))
|
||||||
|
dnsCtx = log.ContextWithAccessMessage(dnsCtx, &log.AccessMessage{
|
||||||
|
From: "DNS",
|
||||||
|
To: addr,
|
||||||
|
Status: log.AccessAccepted,
|
||||||
|
Reason: "",
|
||||||
|
})
|
||||||
|
return dnsCtx
|
||||||
|
}
|
||||||
185
subproject/Xray-core-main/app/dns/dnscommon_test.go
Normal file
185
subproject/Xray-core-main/app/dns/dnscommon_test.go
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseResponse(t *testing.T) {
|
||||||
|
var p [][]byte
|
||||||
|
|
||||||
|
ans := new(dns.Msg)
|
||||||
|
ans.Id = 0
|
||||||
|
p = append(p, common.Must2(ans.Pack()))
|
||||||
|
|
||||||
|
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")),
|
||||||
|
)
|
||||||
|
p = append(p, common.Must2(ans.Pack()))
|
||||||
|
|
||||||
|
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:4860:4860::8888")),
|
||||||
|
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8844")),
|
||||||
|
)
|
||||||
|
p = append(p, common.Must2(ans.Pack()))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
want *IPRecord
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"empty",
|
||||||
|
&IPRecord{0, []net.IP(nil), time.Time{}, dnsmessage.RCodeSuccess, nil},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"error",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a record",
|
||||||
|
&IPRecord{
|
||||||
|
1,
|
||||||
|
[]net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4")},
|
||||||
|
time.Time{},
|
||||||
|
dnsmessage.RCodeSuccess,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aaaa record",
|
||||||
|
&IPRecord{2, []net.IP{net.ParseIP("2001:4860:4860::8888"), net.ParseIP("2001:4860:4860::8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseResponse(p[i])
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("handleResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != nil {
|
||||||
|
// reset the time and RawHeader
|
||||||
|
got.Expire = time.Time{}
|
||||||
|
got.RawHeader = nil
|
||||||
|
}
|
||||||
|
if cmp.Diff(got, tt.want) != "" {
|
||||||
|
t.Error(cmp.Diff(got, tt.want))
|
||||||
|
// t.Errorf("handleResponse() = %#v, want %#v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_buildReqMsgs(t *testing.T) {
|
||||||
|
stubID := func() uint16 {
|
||||||
|
return uint16(rand.Uint32())
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
domain string
|
||||||
|
option dns_feature.IPOption
|
||||||
|
reqOpts *dnsmessage.Resource
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"dual stack", args{"test.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}, nil}, 2},
|
||||||
|
{"ipv4 only", args{"test.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
FakeEnable: false,
|
||||||
|
}, nil}, 1},
|
||||||
|
{"ipv6 only", args{"test.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
}, nil}, 1},
|
||||||
|
{"none/error", args{"test.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: false,
|
||||||
|
FakeEnable: false,
|
||||||
|
}, nil}, 0},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
|
||||||
|
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_genEDNS0Options(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
clientIP net.IP
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *dnsmessage.Resource
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
{"ipv4", args{net.ParseIP("4.3.2.1")}, nil},
|
||||||
|
{"ipv6", args{net.ParseIP("2001::4321")}, nil},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := genEDNS0Options(tt.args.clientIP, 0); got == nil {
|
||||||
|
t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFqdn(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
domain string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"with fqdn", args{"www.example.com."}, "www.example.com."},
|
||||||
|
{"without fqdn", args{"www.example.com"}, "www.example.com."},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Fqdn(tt.args.domain); got != tt.want {
|
||||||
|
t.Errorf("Fqdn() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
250
subproject/Xray-core-main/app/dns/fakedns/fake.go
Normal file
250
subproject/Xray-core-main/app/dns/fakedns/fake.go
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
package fakedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/cache"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Holder struct {
|
||||||
|
domainToIP cache.Lru
|
||||||
|
ipRange *net.IPNet
|
||||||
|
mu *sync.Mutex
|
||||||
|
|
||||||
|
config *FakeDnsPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) IsIPInIPPool(ip net.Address) bool {
|
||||||
|
if ip.Family().IsDomain() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fkdns.ipRange.Contains(ip.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
|
||||||
|
isIPv6 := fkdns.ipRange.IP.To4() == nil
|
||||||
|
if (isIPv6 && ipv6) || (!isIPv6 && ipv4) {
|
||||||
|
return fkdns.GetFakeIPForDomain(domain)
|
||||||
|
}
|
||||||
|
return []net.Address{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Holder) Type() interface{} {
|
||||||
|
return (*dns.FakeDNSEngine)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) Start() error {
|
||||||
|
if fkdns.config != nil && fkdns.config.IpPool != "" && fkdns.config.LruSize != 0 {
|
||||||
|
return fkdns.initializeFromConfig()
|
||||||
|
}
|
||||||
|
return errors.New("invalid fakeDNS setting")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) Close() error {
|
||||||
|
fkdns.domainToIP = nil
|
||||||
|
fkdns.ipRange = nil
|
||||||
|
fkdns.mu = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeDNSHolder() (*Holder, error) {
|
||||||
|
var fkdns *Holder
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
|
||||||
|
return nil, errors.New("Unable to create Fake Dns Engine").Base(err).AtError()
|
||||||
|
}
|
||||||
|
err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fkdns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
|
||||||
|
return &Holder{nil, nil, nil, conf}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) initializeFromConfig() error {
|
||||||
|
return fkdns.initialize(fkdns.config.IpPool, int(fkdns.config.LruSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
||||||
|
var ipRange *net.IPNet
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {
|
||||||
|
return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError()
|
||||||
|
}
|
||||||
|
|
||||||
|
ones, bits := ipRange.Mask.Size()
|
||||||
|
rooms := bits - ones
|
||||||
|
if math.Log2(float64(lruSize)) >= float64(rooms) {
|
||||||
|
return errors.New("LRU size is bigger than subnet size").AtError()
|
||||||
|
}
|
||||||
|
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||||
|
fkdns.ipRange = ipRange
|
||||||
|
fkdns.mu = new(sync.Mutex)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFakeIPForDomain checks and generates a fake IP for a domain name
|
||||||
|
func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
|
||||||
|
fkdns.mu.Lock()
|
||||||
|
defer fkdns.mu.Unlock()
|
||||||
|
if v, ok := fkdns.domainToIP.Get(domain); ok {
|
||||||
|
return []net.Address{v.(net.Address)}
|
||||||
|
}
|
||||||
|
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
|
||||||
|
ones, bits := fkdns.ipRange.Mask.Size()
|
||||||
|
rooms := bits - ones
|
||||||
|
if rooms < 64 {
|
||||||
|
currentTimeMillis %= (uint64(1) << rooms)
|
||||||
|
}
|
||||||
|
bigIntIP := big.NewInt(0).SetBytes(fkdns.ipRange.IP)
|
||||||
|
bigIntIP = bigIntIP.Add(bigIntIP, new(big.Int).SetUint64(currentTimeMillis))
|
||||||
|
var ip net.Address
|
||||||
|
for {
|
||||||
|
ip = net.IPAddress(bigIntIP.Bytes())
|
||||||
|
|
||||||
|
// if we run for a long time, we may go back to beginning and start seeing the IP in use
|
||||||
|
if _, ok := fkdns.domainToIP.PeekKeyFromValue(ip); !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
bigIntIP = bigIntIP.Add(bigIntIP, big.NewInt(1))
|
||||||
|
if !fkdns.ipRange.Contains(bigIntIP.Bytes()) {
|
||||||
|
bigIntIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fkdns.domainToIP.Put(domain, ip)
|
||||||
|
return []net.Address{ip}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainFromFakeDNS checks if an IP is a fake IP and have corresponding domain name
|
||||||
|
func (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string {
|
||||||
|
if !ip.Family().IsIP() || !fkdns.ipRange.Contains(ip.IP()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if k, ok := fkdns.domainToIP.GetKeyFromValue(ip); ok {
|
||||||
|
return k.(string)
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), "A fake ip request to ", ip, ", however there is no matching domain name in fake DNS")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type HolderMulti struct {
|
||||||
|
holders []*Holder
|
||||||
|
|
||||||
|
config *FakeDnsPoolMulti
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) IsIPInIPPool(ip net.Address) bool {
|
||||||
|
if ip.Family().IsDomain() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if v.IsIPInIPPool(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
|
||||||
|
var ret []net.Address
|
||||||
|
for _, v := range h.holders {
|
||||||
|
ret = append(ret, v.GetFakeIPForDomain3(domain, ipv4, ipv6)...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) GetFakeIPForDomain(domain string) []net.Address {
|
||||||
|
var ret []net.Address
|
||||||
|
for _, v := range h.holders {
|
||||||
|
ret = append(ret, v.GetFakeIPForDomain(domain)...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) GetDomainFromFakeDNS(ip net.Address) string {
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if domain := v.GetDomainFromFakeDNS(ip); domain != "" {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) Type() interface{} {
|
||||||
|
return (*dns.FakeDNSEngine)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) Start() error {
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if v.config != nil && v.config.IpPool != "" && v.config.LruSize != 0 {
|
||||||
|
if err := v.Start(); err != nil {
|
||||||
|
return errors.New("Cannot start all fake dns pools").Base(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("invalid fakeDNS setting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) Close() error {
|
||||||
|
for _, v := range h.holders {
|
||||||
|
if err := v.Close(); err != nil {
|
||||||
|
return errors.New("Cannot close all fake dns pools").Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HolderMulti) createHolderGroups() error {
|
||||||
|
for _, v := range h.config.Pools {
|
||||||
|
holder, err := NewFakeDNSHolderConfigOnly(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.holders = append(h.holders, holder)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
||||||
|
holderMulti := &HolderMulti{nil, conf}
|
||||||
|
if err := holderMulti.createHolderGroups(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return holderMulti, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*FakeDnsPool)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
var f *Holder
|
||||||
|
var err error
|
||||||
|
if f, err = NewFakeDNSHolderConfigOnly(config.(*FakeDnsPool)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
common.Must(common.RegisterConfig((*FakeDnsPoolMulti)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
var f *HolderMulti
|
||||||
|
var err error
|
||||||
|
if f, err = NewFakeDNSHolderMulti(config.(*FakeDnsPoolMulti)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
1
subproject/Xray-core-main/app/dns/fakedns/fakedns.go
Normal file
1
subproject/Xray-core-main/app/dns/fakedns/fakedns.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package fakedns
|
||||||
180
subproject/Xray-core-main/app/dns/fakedns/fakedns.pb.go
Normal file
180
subproject/Xray-core-main/app/dns/fakedns/fakedns.pb.go
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/dns/fakedns/fakedns.proto
|
||||||
|
|
||||||
|
package fakedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeDnsPool struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
IpPool string `protobuf:"bytes,1,opt,name=ip_pool,json=ipPool,proto3" json:"ip_pool,omitempty"` //CIDR of IP pool used as fake DNS IP
|
||||||
|
LruSize int64 `protobuf:"varint,2,opt,name=lruSize,proto3" json:"lruSize,omitempty"` //Size of Pool for remembering relationship between domain name and IP address
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPool) Reset() {
|
||||||
|
*x = FakeDnsPool{}
|
||||||
|
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPool) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FakeDnsPool) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FakeDnsPool) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FakeDnsPool.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FakeDnsPool) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPool) GetIpPool() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.IpPool
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPool) GetLruSize() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.LruSize
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeDnsPoolMulti struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Pools []*FakeDnsPool `protobuf:"bytes,1,rep,name=pools,proto3" json:"pools,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) Reset() {
|
||||||
|
*x = FakeDnsPoolMulti{}
|
||||||
|
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FakeDnsPoolMulti) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FakeDnsPoolMulti.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FakeDnsPoolMulti) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FakeDnsPoolMulti) GetPools() []*FakeDnsPool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Pools
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_dns_fakedns_fakedns_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_dns_fakedns_fakedns_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1dapp/dns/fakedns/fakedns.proto\x12\x14xray.app.dns.fakedns\"@\n" +
|
||||||
|
"\vFakeDnsPool\x12\x17\n" +
|
||||||
|
"\aip_pool\x18\x01 \x01(\tR\x06ipPool\x12\x18\n" +
|
||||||
|
"\alruSize\x18\x02 \x01(\x03R\alruSize\"K\n" +
|
||||||
|
"\x10FakeDnsPoolMulti\x127\n" +
|
||||||
|
"\x05pools\x18\x01 \x03(\v2!.xray.app.dns.fakedns.FakeDnsPoolR\x05poolsB^\n" +
|
||||||
|
"\x18com.xray.app.dns.fakednsP\x01Z)github.com/xtls/xray-core/app/dns/fakedns\xaa\x02\x14Xray.App.Dns.Fakednsb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_dns_fakedns_fakedns_proto_rawDescOnce sync.Once
|
||||||
|
file_app_dns_fakedns_fakedns_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_dns_fakedns_fakedns_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_dns_fakedns_fakedns_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_dns_fakedns_fakedns_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dns_fakedns_fakedns_proto_rawDesc), len(file_app_dns_fakedns_fakedns_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_dns_fakedns_fakedns_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_app_dns_fakedns_fakedns_proto_goTypes = []any{
|
||||||
|
(*FakeDnsPool)(nil), // 0: xray.app.dns.fakedns.FakeDnsPool
|
||||||
|
(*FakeDnsPoolMulti)(nil), // 1: xray.app.dns.fakedns.FakeDnsPoolMulti
|
||||||
|
}
|
||||||
|
var file_app_dns_fakedns_fakedns_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.app.dns.fakedns.FakeDnsPoolMulti.pools:type_name -> xray.app.dns.fakedns.FakeDnsPool
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_dns_fakedns_fakedns_proto_init() }
|
||||||
|
func file_app_dns_fakedns_fakedns_proto_init() {
|
||||||
|
if File_app_dns_fakedns_fakedns_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dns_fakedns_fakedns_proto_rawDesc), len(file_app_dns_fakedns_fakedns_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_dns_fakedns_fakedns_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_dns_fakedns_fakedns_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_dns_fakedns_fakedns_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_dns_fakedns_fakedns_proto = out.File
|
||||||
|
file_app_dns_fakedns_fakedns_proto_goTypes = nil
|
||||||
|
file_app_dns_fakedns_fakedns_proto_depIdxs = nil
|
||||||
|
}
|
||||||
16
subproject/Xray-core-main/app/dns/fakedns/fakedns.proto
Normal file
16
subproject/Xray-core-main/app/dns/fakedns/fakedns.proto
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.dns.fakedns;
|
||||||
|
option csharp_namespace = "Xray.App.Dns.Fakedns";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/dns/fakedns";
|
||||||
|
option java_package = "com.xray.app.dns.fakedns";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message FakeDnsPool{
|
||||||
|
string ip_pool = 1; //CIDR of IP pool used as fake DNS IP
|
||||||
|
int64 lruSize = 2; //Size of Pool for remembering relationship between domain name and IP address
|
||||||
|
}
|
||||||
|
|
||||||
|
message FakeDnsPoolMulti{
|
||||||
|
repeated FakeDnsPool pools = 1;
|
||||||
|
}
|
||||||
205
subproject/Xray-core-main/app/dns/fakedns/fakedns_test.go
Normal file
205
subproject/Xray-core-main/app/dns/fakedns/fakedns_test.go
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
package fakedns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ipPrefix = "198.1"
|
||||||
|
|
||||||
|
func TestNewFakeDnsHolder(_ *testing.T) {
|
||||||
|
_, err := NewFakeDNSHolder()
|
||||||
|
common.Must(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeDnsHolderCreateMapping(t *testing.T) {
|
||||||
|
fkdns, err := NewFakeDNSHolder()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
addr := fkdns.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
assert.Equal(t, ipPrefix, addr[0].IP().String()[0:len(ipPrefix)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeDnsHolderCreateMappingMany(t *testing.T) {
|
||||||
|
fkdns, err := NewFakeDNSHolder()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
addr := fkdns.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
assert.Equal(t, ipPrefix, addr[0].IP().String()[0:len(ipPrefix)])
|
||||||
|
|
||||||
|
addr2 := fkdns.GetFakeIPForDomain("fakednstest2.example.com")
|
||||||
|
assert.Equal(t, ipPrefix, addr2[0].IP().String()[0:len(ipPrefix)])
|
||||||
|
assert.NotEqual(t, addr[0].IP().String(), addr2[0].IP().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeDnsHolderCreateMappingManyAndResolve(t *testing.T) {
|
||||||
|
fkdns, err := NewFakeDNSHolder()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
addr := fkdns.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
addr2 := fkdns.GetFakeIPForDomain("fakednstest2.example.com")
|
||||||
|
|
||||||
|
{
|
||||||
|
result := fkdns.GetDomainFromFakeDNS(addr[0])
|
||||||
|
assert.Equal(t, "fakednstest.example.com", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
result := fkdns.GetDomainFromFakeDNS(addr2[0])
|
||||||
|
assert.Equal(t, "fakednstest2.example.com", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeDnsHolderCreateMappingManySingleDomain(t *testing.T) {
|
||||||
|
fkdns, err := NewFakeDNSHolder()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
addr := fkdns.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
addr2 := fkdns.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
assert.Equal(t, addr[0].IP().String(), addr2[0].IP().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFakeIPForDomainConcurrently(t *testing.T) {
|
||||||
|
fkdns, err := NewFakeDNSHolder()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
total := 200
|
||||||
|
addr := make([][]net.Address, total)
|
||||||
|
var errg errgroup.Group
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
errg.Go(testGetFakeIP(i, addr, fkdns))
|
||||||
|
}
|
||||||
|
errg.Wait()
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
for j := i + 1; j < total; j++ {
|
||||||
|
assert.NotEqual(t, addr[i][0].IP().String(), addr[j][0].IP().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetFakeIP(index int, addr [][]net.Address, fkdns *Holder) func() error {
|
||||||
|
return func() error {
|
||||||
|
addr[index] = fkdns.GetFakeIPForDomain("fakednstest" + strconv.Itoa(index) + ".example.com")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
||||||
|
fkdns, err := NewFakeDNSHolderConfigOnly(&FakeDnsPool{
|
||||||
|
IpPool: dns.FakeIPv4Pool,
|
||||||
|
LruSize: 256,
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
err = fkdns.Start()
|
||||||
|
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
addr := fkdns.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
addr2 := fkdns.GetFakeIPForDomain("fakednstest2.example.com")
|
||||||
|
|
||||||
|
for i := 0; i <= 8192; i++ {
|
||||||
|
{
|
||||||
|
result := fkdns.GetDomainFromFakeDNS(addr[0])
|
||||||
|
assert.Equal(t, "fakednstest.example.com", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
result := fkdns.GetDomainFromFakeDNS(addr2[0])
|
||||||
|
assert.Equal(t, "fakednstest2.example.com", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
uuid := uuid.New()
|
||||||
|
domain := uuid.String() + ".fakednstest.example.com"
|
||||||
|
tempAddr := fkdns.GetFakeIPForDomain(domain)
|
||||||
|
rsaddr := tempAddr[0].IP().String()
|
||||||
|
|
||||||
|
result := fkdns.GetDomainFromFakeDNS(net.ParseAddress(rsaddr))
|
||||||
|
assert.Equal(t, domain, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeDNSMulti(t *testing.T) {
|
||||||
|
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
|
||||||
|
Pools: []*FakeDnsPool{{
|
||||||
|
IpPool: "240.0.0.0/12",
|
||||||
|
LruSize: 256,
|
||||||
|
}, {
|
||||||
|
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||||
|
LruSize: 256,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
err = fakeMulti.Start()
|
||||||
|
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Should not throw error")
|
||||||
|
_ = fakeMulti
|
||||||
|
|
||||||
|
t.Run("checkInRange", func(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{240, 0, 0, 5}))
|
||||||
|
assert.True(t, inPool)
|
||||||
|
})
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
ip, err := net.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
|
||||||
|
assert.True(t, inPool)
|
||||||
|
})
|
||||||
|
t.Run("ipv4_inverse", func(t *testing.T) {
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{241, 0, 0, 5}))
|
||||||
|
assert.False(t, inPool)
|
||||||
|
})
|
||||||
|
t.Run("ipv6_inverse", func(t *testing.T) {
|
||||||
|
ip, err := net.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
|
||||||
|
assert.False(t, inPool)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allocateTwoAddressForTwoPool", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain("fakednstest.example.com")
|
||||||
|
assert.Len(t, address, 2, "should be 2 address one for each pool")
|
||||||
|
t.Run("eachOfThemShouldResolve:0", func(t *testing.T) {
|
||||||
|
domain := fakeMulti.GetDomainFromFakeDNS(address[0])
|
||||||
|
assert.Equal(t, "fakednstest.example.com", domain)
|
||||||
|
})
|
||||||
|
t.Run("eachOfThemShouldResolve:1", func(t *testing.T) {
|
||||||
|
domain := fakeMulti.GetDomainFromFakeDNS(address[1])
|
||||||
|
assert.Equal(t, "fakednstest.example.com", domain)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("understandIPTypeSelector", func(t *testing.T) {
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain3("fakednstestipv4.example.com", true, false)
|
||||||
|
assert.Len(t, address, 1, "should be 1 address")
|
||||||
|
assert.True(t, address[0].Family().IsIPv4())
|
||||||
|
})
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain3("fakednstestipv6.example.com", false, true)
|
||||||
|
assert.Len(t, address, 1, "should be 1 address")
|
||||||
|
assert.True(t, address[0].Family().IsIPv6())
|
||||||
|
})
|
||||||
|
t.Run("ipv46", func(t *testing.T) {
|
||||||
|
address := fakeMulti.GetFakeIPForDomain3("fakednstestipv46.example.com", true, true)
|
||||||
|
assert.Len(t, address, 2, "should be 2 address")
|
||||||
|
assert.True(t, address[0].Family().IsIPv4())
|
||||||
|
assert.True(t, address[1].Family().IsIPv6())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
173
subproject/Xray-core-main/app/dns/hosts.go
Normal file
173
subproject/Xray-core-main/app/dns/hosts.go
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StaticHosts represents static domain-ip mapping in DNS server.
|
||||||
|
type StaticHosts struct {
|
||||||
|
ips [][]net.Address
|
||||||
|
matchers strmatcher.IndexMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStaticHosts creates a new StaticHosts instance.
|
||||||
|
func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||||
|
g := new(strmatcher.MatcherGroup)
|
||||||
|
sh := &StaticHosts{
|
||||||
|
ips: make([][]net.Address, len(hosts)+16),
|
||||||
|
matchers: g,
|
||||||
|
}
|
||||||
|
|
||||||
|
defer runtime.GC()
|
||||||
|
for i, mapping := range hosts {
|
||||||
|
hosts[i] = nil
|
||||||
|
matcher, err := toStrMatcher(mapping.Type, mapping.Domain)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to create domain matcher, ignore domain rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := g.Add(matcher)
|
||||||
|
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||||
|
switch {
|
||||||
|
case len(mapping.ProxiedDomain) > 0:
|
||||||
|
if mapping.ProxiedDomain[0] == '#' {
|
||||||
|
rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips = append(ips, dns.RCodeError(rcode))
|
||||||
|
} else {
|
||||||
|
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||||
|
}
|
||||||
|
case len(mapping.Ip) > 0:
|
||||||
|
for _, ip := range mapping.Ip {
|
||||||
|
addr := net.IPAddress(ip)
|
||||||
|
if addr == nil {
|
||||||
|
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips = append(ips, addr)
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sh.ips[id] = ips
|
||||||
|
}
|
||||||
|
|
||||||
|
return sh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||||
|
filtered := make([]net.Address, 0, len(ips))
|
||||||
|
for _, ip := range ips {
|
||||||
|
if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
|
||||||
|
filtered = append(filtered, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||||
|
ips := make([]net.Address, 0)
|
||||||
|
found := false
|
||||||
|
for _, id := range h.matchers.Match(domain) {
|
||||||
|
for _, v := range h.ips[id] {
|
||||||
|
if err, ok := v.(dns.RCodeError); ok {
|
||||||
|
if uint16(err) == 0 {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ips = append(ips, h.ips[id]...)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
|
||||||
|
switch addrs, err := h.lookupInternal(domain); {
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||||
|
return addrs, nil
|
||||||
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||||
|
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||||
|
if maxDepth > 0 {
|
||||||
|
unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if unwrapped != nil {
|
||||||
|
return unwrapped, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs, nil
|
||||||
|
default: // IP record found, return a non-nil IP array
|
||||||
|
return filterIP(addrs, option), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||||
|
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||||
|
return h.lookup(domain, option, 5)
|
||||||
|
}
|
||||||
|
func NewStaticHostsFromCache(matcher strmatcher.IndexMatcher, hostIPs map[string][]string) (*StaticHosts, error) {
|
||||||
|
sh := &StaticHosts{
|
||||||
|
ips: make([][]net.Address, matcher.Size()+1),
|
||||||
|
matchers: matcher,
|
||||||
|
}
|
||||||
|
|
||||||
|
order := hostIPs["_ORDER"]
|
||||||
|
var offset uint32
|
||||||
|
|
||||||
|
img, ok := matcher.(*strmatcher.IndexMatcherGroup)
|
||||||
|
if !ok {
|
||||||
|
// Single matcher (e.g. only manual or only one geosite)
|
||||||
|
if len(order) > 0 {
|
||||||
|
pattern := order[0]
|
||||||
|
ips := parseIPs(hostIPs[pattern])
|
||||||
|
for i := uint32(1); i <= matcher.Size(); i++ {
|
||||||
|
sh.ips[i] = ips
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, m := range img.Matchers {
|
||||||
|
if i < len(order) {
|
||||||
|
pattern := order[i]
|
||||||
|
ips := parseIPs(hostIPs[pattern])
|
||||||
|
for j := uint32(1); j <= m.Size(); j++ {
|
||||||
|
sh.ips[offset+j] = ips
|
||||||
|
}
|
||||||
|
offset += m.Size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIPs(raw []string) []net.Address {
|
||||||
|
addrs := make([]net.Address, 0, len(raw))
|
||||||
|
for _, s := range raw {
|
||||||
|
if len(s) > 1 && s[0] == '#' {
|
||||||
|
rcode, _ := strconv.Atoi(s[1:])
|
||||||
|
addrs = append(addrs, dns.RCodeError(rcode))
|
||||||
|
} else {
|
||||||
|
addrs = append(addrs, net.ParseAddress(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
188
subproject/Xray-core-main/app/dns/hosts_test.go
Normal file
188
subproject/Xray-core-main/app/dns/hosts_test.go
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStaticHosts(t *testing.T) {
|
||||||
|
pb := []*Config_HostMapping{
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Subdomain,
|
||||||
|
Domain: "lan",
|
||||||
|
ProxiedDomain: "#3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "example.com",
|
||||||
|
Ip: [][]byte{
|
||||||
|
{1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "proxy.xray.com",
|
||||||
|
Ip: [][]byte{
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
ProxiedDomain: "another-proxy.xray.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Full,
|
||||||
|
Domain: "proxy2.xray.com",
|
||||||
|
ProxiedDomain: "proxy.xray.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Subdomain,
|
||||||
|
Domain: "example.cn",
|
||||||
|
Ip: [][]byte{
|
||||||
|
{2, 2, 2, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Subdomain,
|
||||||
|
Domain: "baidu.com",
|
||||||
|
Ip: [][]byte{
|
||||||
|
{127, 0, 0, 1},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := NewStaticHosts(pb)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
{
|
||||||
|
_, err := hosts.Lookup("example.com.lan", dns.IPOption{})
|
||||||
|
if dns.RCodeFromError(err) != 3 {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips, _ := hosts.Lookup("example.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
if len(ips) != 1 {
|
||||||
|
t.Error("expect 1 IP, but got ", len(ips))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff([]byte(ips[0].IP()), []byte{1, 1, 1, 1}); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
domain, _ := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
if len(domain) != 1 {
|
||||||
|
t.Error("expect 1 domain, but got ", len(domain))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(domain[0].Domain(), "another-proxy.xray.com"); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
domain, _ := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
if len(domain) != 1 {
|
||||||
|
t.Error("expect 1 domain, but got ", len(domain))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(domain[0].Domain(), "another-proxy.xray.com"); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips, _ := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
if len(ips) != 1 {
|
||||||
|
t.Error("expect 1 IP, but got ", len(ips))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff([]byte(ips[0].IP()), []byte{2, 2, 2, 2}); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips, _ := hosts.Lookup("baidu.com", dns.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
if len(ips) != 1 {
|
||||||
|
t.Error("expect 1 IP, but got ", len(ips))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff([]byte(ips[0].IP()), []byte(net.LocalHostIPv6.IP())); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestStaticHostsFromCache(t *testing.T) {
|
||||||
|
sites := []*router.GeoSite{
|
||||||
|
{
|
||||||
|
CountryCode: "cloudflare-dns.com",
|
||||||
|
Domain: []*router.Domain{
|
||||||
|
{Type: router.Domain_Full, Value: "example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CountryCode: "geosite:cn",
|
||||||
|
Domain: []*router.Domain{
|
||||||
|
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deps := map[string][]string{
|
||||||
|
"HOSTS": {"cloudflare-dns.com", "geosite:cn"},
|
||||||
|
}
|
||||||
|
hostIPs := map[string][]string{
|
||||||
|
"cloudflare-dns.com": {"1.1.1.1"},
|
||||||
|
"geosite:cn": {"2.2.2.2"},
|
||||||
|
"_ORDER": {"cloudflare-dns.com", "geosite:cn"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := router.SerializeGeoSiteList(sites, deps, hostIPs, &buf)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
// Load matcher
|
||||||
|
m, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), "HOSTS")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
// Load hostIPs
|
||||||
|
f := bytes.NewReader(buf.Bytes())
|
||||||
|
hips, err := router.LoadGeoSiteHosts(f)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
hosts, err := NewStaticHostsFromCache(m, hips)
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
{
|
||||||
|
ips, _ := hosts.Lookup("example.com", dns.IPOption{IPv4Enable: true})
|
||||||
|
if len(ips) != 1 || ips[0].String() != "1.1.1.1" {
|
||||||
|
t.Error("failed to lookup example.com from cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips, _ := hosts.Lookup("baidu.cn", dns.IPOption{IPv4Enable: true})
|
||||||
|
if len(ips) != 1 || ips[0].String() != "2.2.2.2" {
|
||||||
|
t.Error("failed to lookup baidu.cn from cache deps")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
342
subproject/Xray-core-main/app/dns/nameserver.go
Normal file
342
subproject/Xray-core-main/app/dns/nameserver.go
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/router"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/platform"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mphMatcherWrapper struct {
|
||||||
|
m strmatcher.IndexMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *mphMatcherWrapper) Match(s string) bool {
|
||||||
|
return w.m.Match(s) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *mphMatcherWrapper) String() string {
|
||||||
|
return "mph-matcher"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is the interface for Name Server.
|
||||||
|
type Server interface {
|
||||||
|
// Name of the Client.
|
||||||
|
Name() string
|
||||||
|
|
||||||
|
IsDisableCache() bool
|
||||||
|
|
||||||
|
// QueryIP sends IP queries to its configured server.
|
||||||
|
QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is the interface for DNS client.
|
||||||
|
type Client struct {
|
||||||
|
server Server
|
||||||
|
skipFallback bool
|
||||||
|
domains []string
|
||||||
|
expectedIPs router.GeoIPMatcher
|
||||||
|
unexpectedIPs router.GeoIPMatcher
|
||||||
|
actPrior bool
|
||||||
|
actUnprior bool
|
||||||
|
tag string
|
||||||
|
timeoutMs time.Duration
|
||||||
|
finalQuery bool
|
||||||
|
ipOption *dns.IPOption
|
||||||
|
checkSystem bool
|
||||||
|
policyID uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a name server object according to the network destination url.
|
||||||
|
func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (Server, error) {
|
||||||
|
if address := dest.Address; address.Family().IsDomain() {
|
||||||
|
u, err := url.Parse(address.Domain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(u.String(), "localhost"):
|
||||||
|
return NewLocalNameServer(), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
|
||||||
|
return NewDoHNameServer(u, dispatcher, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
|
||||||
|
return NewDoHNameServer(u, dispatcher, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
|
||||||
|
return NewDoHNameServer(u, nil, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode
|
||||||
|
return NewDoHNameServer(u, nil, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||||
|
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
|
||||||
|
return NewQUICNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
|
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
|
||||||
|
return NewTCPNameServer(u, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
|
case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
|
||||||
|
return NewTCPLocalNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
|
case strings.EqualFold(u.String(), "fakedns"):
|
||||||
|
var fd dns.FakeDNSEngine
|
||||||
|
err = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||||
|
fd = fdns
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewFakeDNSServer(fd), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dest.Network == net.Network_Unknown {
|
||||||
|
dest.Network = net.Network_UDP
|
||||||
|
}
|
||||||
|
if dest.Network == net.Network_UDP { // UDP classic DNS mode
|
||||||
|
return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("No available name server could be created from ", dest).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
|
||||||
|
func NewClient(
|
||||||
|
ctx context.Context,
|
||||||
|
ns *NameServer,
|
||||||
|
clientIP net.IP,
|
||||||
|
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||||
|
tag string,
|
||||||
|
ipOption dns.IPOption,
|
||||||
|
matcherInfos *[]*DomainMatcherInfo,
|
||||||
|
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),
|
||||||
|
) (*Client, error) {
|
||||||
|
client := &Client{}
|
||||||
|
|
||||||
|
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||||
|
// Create a new server for each client for now
|
||||||
|
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||||
|
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||||
|
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
||||||
|
ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
|
||||||
|
// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
|
||||||
|
// Because the `localhost` DNS client will append len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
|
||||||
|
// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
|
||||||
|
// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
|
||||||
|
// Related issues:
|
||||||
|
// https://github.com/v2fly/v2ray-core/issues/529
|
||||||
|
// https://github.com/v2fly/v2ray-core/issues/719
|
||||||
|
for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
|
||||||
|
*matcherInfos = append(*matcherInfos, &DomainMatcherInfo{
|
||||||
|
clientIdx: uint16(0),
|
||||||
|
domainRuleIdx: uint16(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish domain rules
|
||||||
|
var rules []string
|
||||||
|
ruleCurr := 0
|
||||||
|
ruleIter := 0
|
||||||
|
|
||||||
|
// Check if domain matcher cache is provided via environment
|
||||||
|
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||||
|
var mphLoaded bool
|
||||||
|
|
||||||
|
if domainMatcherPath != "" && ns.Tag != "" {
|
||||||
|
f, err := filesystem.NewFileReader(domainMatcherPath)
|
||||||
|
if err == nil {
|
||||||
|
defer f.Close()
|
||||||
|
g, err := router.LoadGeoSiteMatcher(f, ns.Tag)
|
||||||
|
if err == nil {
|
||||||
|
errors.LogDebug(ctx, "MphDomainMatcher loaded from cache for ", ns.Tag, " dns tag)")
|
||||||
|
updateDomainRule(&mphMatcherWrapper{m: g}, 0, *matcherInfos)
|
||||||
|
rules = append(rules, "[MPH Cache]")
|
||||||
|
mphLoaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mphLoaded {
|
||||||
|
for i, domain := range ns.PrioritizedDomain {
|
||||||
|
ns.PrioritizedDomain[i] = nil
|
||||||
|
domainRule, err := toStrMatcher(domain.Type, domain.Domain)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to create domain matcher, ignore domain rule [type: ", domain.Type, ", domain: ", domain.Domain, "]")
|
||||||
|
domainRule, _ = toStrMatcher(DomainMatchingType_Full, "hack.fix.index.for.illegal.domain.rule")
|
||||||
|
}
|
||||||
|
originalRuleIdx := ruleCurr
|
||||||
|
if ruleCurr < len(ns.OriginalRules) {
|
||||||
|
rule := ns.OriginalRules[ruleCurr]
|
||||||
|
if ruleCurr >= len(rules) {
|
||||||
|
rules = append(rules, rule.Rule)
|
||||||
|
}
|
||||||
|
ruleIter++
|
||||||
|
if ruleIter >= int(rule.Size) {
|
||||||
|
ruleIter = 0
|
||||||
|
ruleCurr++
|
||||||
|
}
|
||||||
|
} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
|
||||||
|
rules = append(rules, domainRule.String())
|
||||||
|
ruleCurr++
|
||||||
|
}
|
||||||
|
updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns.PrioritizedDomain = nil
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
|
// Establish expected IPs
|
||||||
|
var expectedMatcher router.GeoIPMatcher
|
||||||
|
if len(ns.ExpectedGeoip) > 0 {
|
||||||
|
expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
ns.ExpectedGeoip = nil
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish unexpected IPs
|
||||||
|
var unexpectedMatcher router.GeoIPMatcher
|
||||||
|
if len(ns.UnexpectedGeoip) > 0 {
|
||||||
|
unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
ns.UnexpectedGeoip = nil
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
switch ns.Address.Address.GetAddress().(type) {
|
||||||
|
case *net.IPOrDomain_Domain:
|
||||||
|
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String())
|
||||||
|
case *net.IPOrDomain_Ip:
|
||||||
|
errors.LogInfo(ctx, "DNS: client ", net.IP(ns.Address.Address.GetIp()), " uses clientIP ", clientIP.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeoutMs = 4000 * time.Millisecond
|
||||||
|
if ns.TimeoutMs > 0 {
|
||||||
|
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSystem := ns.QueryStrategy == QueryStrategy_USE_SYS
|
||||||
|
|
||||||
|
client.server = server
|
||||||
|
client.skipFallback = ns.SkipFallback
|
||||||
|
client.domains = rules
|
||||||
|
client.expectedIPs = expectedMatcher
|
||||||
|
client.unexpectedIPs = unexpectedMatcher
|
||||||
|
client.actPrior = ns.ActPrior
|
||||||
|
client.actUnprior = ns.ActUnprior
|
||||||
|
client.tag = tag
|
||||||
|
client.timeoutMs = timeoutMs
|
||||||
|
client.finalQuery = ns.FinalQuery
|
||||||
|
client.ipOption = &ipOption
|
||||||
|
client.checkSystem = checkSystem
|
||||||
|
client.policyID = ns.PolicyID
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the server name the client manages.
|
||||||
|
func (c *Client) Name() string {
|
||||||
|
return c.server.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP sends DNS query to the name server with the client's IP.
|
||||||
|
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
if c.checkSystem {
|
||||||
|
supportIPv4, supportIPv6 := checkRoutes()
|
||||||
|
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||||
|
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||||
|
} else {
|
||||||
|
option.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable
|
||||||
|
option.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable
|
||||||
|
}
|
||||||
|
|
||||||
|
if !option.IPv4Enable && !option.IPv6Enable {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, c.timeoutMs)
|
||||||
|
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})
|
||||||
|
ips, ttl, err := c.server.QueryIP(ctx, domain, option)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.expectedIPs != nil && !c.actPrior {
|
||||||
|
ips, _ = c.expectedIPs.FilterIPs(ips)
|
||||||
|
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name())
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.unexpectedIPs != nil && !c.actUnprior {
|
||||||
|
_, ips = c.unexpectedIPs.FilterIPs(ips)
|
||||||
|
errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name())
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.expectedIPs != nil && c.actPrior {
|
||||||
|
ipsNew, _ := c.expectedIPs.FilterIPs(ips)
|
||||||
|
if len(ipsNew) > 0 {
|
||||||
|
ips = ipsNew
|
||||||
|
errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.unexpectedIPs != nil && c.actUnprior {
|
||||||
|
_, ipsNew := c.unexpectedIPs.FilterIPs(ips)
|
||||||
|
if len(ipsNew) > 0 {
|
||||||
|
ips = ipsNew
|
||||||
|
errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, ttl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
|
||||||
|
switch queryStrategy {
|
||||||
|
case QueryStrategy_USE_IP:
|
||||||
|
return ipOption
|
||||||
|
case QueryStrategy_USE_SYS:
|
||||||
|
return ipOption
|
||||||
|
case QueryStrategy_USE_IP4:
|
||||||
|
return dns.IPOption{
|
||||||
|
IPv4Enable: ipOption.IPv4Enable,
|
||||||
|
IPv6Enable: false,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
case QueryStrategy_USE_IP6:
|
||||||
|
return dns.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: ipOption.IPv6Enable,
|
||||||
|
FakeEnable: false,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ipOption
|
||||||
|
}
|
||||||
|
}
|
||||||
173
subproject/Xray-core-main/app/dns/nameserver_cached.go
Normal file
173
subproject/Xray-core-main/app/dns/nameserver_cached.go
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
go_errors "errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/signal/pubsub"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CachedNameserver interface {
|
||||||
|
getCacheController() *CacheController
|
||||||
|
|
||||||
|
sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns.IPOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryIP is called from dns.Server->queryIPTimeout
|
||||||
|
func queryIP(ctx context.Context, s CachedNameserver, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
fqdn := Fqdn(domain)
|
||||||
|
|
||||||
|
cache := s.getCacheController()
|
||||||
|
if !cache.disableCache {
|
||||||
|
if rec := cache.findRecords(fqdn); rec != nil {
|
||||||
|
ips, ttl, err := merge(option, rec.A, rec.AAAA)
|
||||||
|
if !go_errors.Is(err, errRecordNotFound) {
|
||||||
|
if ttl > 0 {
|
||||||
|
errors.LogDebugInner(ctx, err, cache.name, " cache HIT ", fqdn, " -> ", ips)
|
||||||
|
log.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
|
||||||
|
return ips, uint32(ttl), err
|
||||||
|
}
|
||||||
|
if cache.serveStale && (cache.serveExpiredTTL == 0 || cache.serveExpiredTTL < ttl) {
|
||||||
|
errors.LogDebugInner(ctx, err, cache.name, " cache OPTIMISTE ", fqdn, " -> ", ips)
|
||||||
|
log.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheOptimiste, Elapsed: 0, Error: err})
|
||||||
|
go pull(ctx, s, fqdn, option)
|
||||||
|
return ips, 1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", fqdn, " at ", cache.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(ctx, s, fqdn, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pull(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) {
|
||||||
|
nctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 8*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
fetch(nctx, s, fqdn, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
key := fqdn
|
||||||
|
switch {
|
||||||
|
case option.IPv4Enable && option.IPv6Enable:
|
||||||
|
key = key + "46"
|
||||||
|
case option.IPv4Enable:
|
||||||
|
key = key + "4"
|
||||||
|
case option.IPv6Enable:
|
||||||
|
key = key + "6"
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _, _ := s.getCacheController().requestGroup.Do(key, func() (any, error) {
|
||||||
|
return doFetch(ctx, s, fqdn, option), nil
|
||||||
|
})
|
||||||
|
ret := v.(result)
|
||||||
|
|
||||||
|
return ret.ips, ret.ttl, ret.error
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
ips []net.IP
|
||||||
|
ttl uint32
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func doFetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) result {
|
||||||
|
sub4, sub6 := s.getCacheController().registerSubscribers(fqdn, option)
|
||||||
|
defer closeSubscribers(sub4, sub6)
|
||||||
|
|
||||||
|
noResponseErrCh := make(chan error, 2)
|
||||||
|
onEvent := func(sub *pubsub.Subscriber) (*IPRecord, error) {
|
||||||
|
if sub == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case err := <-noResponseErrCh:
|
||||||
|
return nil, err
|
||||||
|
case msg := <-sub.Wait():
|
||||||
|
sub.Close()
|
||||||
|
return msg.(*IPRecord), nil // should panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
|
||||||
|
|
||||||
|
rec4, err4 := onEvent(sub4)
|
||||||
|
rec6, err6 := onEvent(sub6)
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
if err4 != nil {
|
||||||
|
errs = append(errs, err4)
|
||||||
|
}
|
||||||
|
if err6 != nil {
|
||||||
|
errs = append(errs, err6)
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, ttl, err := merge(option, rec4, rec6, errs...)
|
||||||
|
var rTTL uint32
|
||||||
|
if ttl > 0 {
|
||||||
|
rTTL = uint32(ttl)
|
||||||
|
} else if ttl == 0 && go_errors.Is(err, errRecordNotFound) {
|
||||||
|
rTTL = 0
|
||||||
|
} else { // edge case: where a fast rep's ttl expires during the rtt of a slower, parallel query
|
||||||
|
rTTL = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Record(&log.DNSLog{Server: s.getCacheController().name, Domain: fqdn, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
|
return result{ips, rTTL, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge(option dns.IPOption, rec4 *IPRecord, rec6 *IPRecord, errs ...error) ([]net.IP, int32, error) {
|
||||||
|
var allIPs []net.IP
|
||||||
|
var rTTL int32 = dns.DefaultTTL
|
||||||
|
|
||||||
|
mergeReq := option.IPv4Enable && option.IPv6Enable
|
||||||
|
|
||||||
|
if option.IPv4Enable {
|
||||||
|
ips, ttl, err := rec4.getIPs() // it's safe
|
||||||
|
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
|
||||||
|
return ips, ttl, err
|
||||||
|
}
|
||||||
|
if ttl < rTTL {
|
||||||
|
rTTL = ttl
|
||||||
|
}
|
||||||
|
if len(ips) > 0 {
|
||||||
|
allIPs = append(allIPs, ips...)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.IPv6Enable {
|
||||||
|
ips, ttl, err := rec6.getIPs() // it's safe
|
||||||
|
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
|
||||||
|
return ips, ttl, err
|
||||||
|
}
|
||||||
|
if ttl < rTTL {
|
||||||
|
rTTL = ttl
|
||||||
|
}
|
||||||
|
if len(ips) > 0 {
|
||||||
|
allIPs = append(allIPs, ips...)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allIPs) > 0 {
|
||||||
|
return allIPs, rTTL, nil
|
||||||
|
}
|
||||||
|
if len(errs) == 2 && go_errors.Is(errs[0], errs[1]) {
|
||||||
|
return nil, rTTL, errs[0]
|
||||||
|
}
|
||||||
|
return nil, rTTL, errors.Combine(errs...)
|
||||||
|
}
|
||||||
239
subproject/Xray-core-main/app/dns/nameserver_doh.go
Normal file
239
subproject/Xray-core-main/app/dns/nameserver_doh.go
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
utls "github.com/refraction-networking/utls"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/crypto"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,
|
||||||
|
// which is compatible with traditional dns over udp(RFC1035),
|
||||||
|
// thus most of the DOH implementation is copied from udpns.go
|
||||||
|
type DoHNameServer struct {
|
||||||
|
cacheController *CacheController
|
||||||
|
httpClient *http.Client
|
||||||
|
dohURL string
|
||||||
|
clientIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
|
||||||
|
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *DoHNameServer {
|
||||||
|
url.Scheme = "https"
|
||||||
|
mode := "DOH"
|
||||||
|
if dispatcher == nil {
|
||||||
|
mode = "DOHL"
|
||||||
|
}
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
|
||||||
|
s := &DoHNameServer{
|
||||||
|
cacheController: NewCacheController(mode+"//"+url.Host, disableCache, serveStale, serveExpiredTTL),
|
||||||
|
dohURL: url.String(),
|
||||||
|
clientIP: clientIP,
|
||||||
|
}
|
||||||
|
s.httpClient = &http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
IdleConnTimeout: net.ConnIdleTimeout,
|
||||||
|
ReadIdleTimeout: net.ChromeH2KeepAlivePeriod,
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
dest, err := net.ParseDestination(network + ":" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var conn net.Conn
|
||||||
|
if dispatcher != nil {
|
||||||
|
dnsCtx := toDnsContext(ctx, s.dohURL)
|
||||||
|
if h2c {
|
||||||
|
dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
|
||||||
|
dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
|
||||||
|
}
|
||||||
|
link, err := dispatcher.Dispatch(dnsCtx, dest)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cc := common.ChainedClosable{}
|
||||||
|
if cw, ok := link.Writer.(common.Closable); ok {
|
||||||
|
cc = append(cc, cw)
|
||||||
|
}
|
||||||
|
if cr, ok := link.Reader.(common.Closable); ok {
|
||||||
|
cc = append(cc, cr)
|
||||||
|
}
|
||||||
|
conn = cnc.NewConnection(
|
||||||
|
cnc.ConnectionInputMulti(link.Writer),
|
||||||
|
cnc.ConnectionOutputMulti(link.Reader),
|
||||||
|
cnc.ConnectionOnClose(cc),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Record(&log.AccessMessage{
|
||||||
|
From: "DNS",
|
||||||
|
To: s.dohURL,
|
||||||
|
Status: log.AccessAccepted,
|
||||||
|
Detour: "local",
|
||||||
|
})
|
||||||
|
conn, err = internet.DialSystem(ctx, dest, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !h2c {
|
||||||
|
conn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)
|
||||||
|
if err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *DoHNameServer) Name() string {
|
||||||
|
return s.cacheController.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDisableCache implements Server.
|
||||||
|
func (s *DoHNameServer) IsDisableCache() bool {
|
||||||
|
return s.cacheController.disableCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) newReqID() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCacheController implements CachedNameserver.
|
||||||
|
func (s *DoHNameServer) getCacheController() *CacheController {
|
||||||
|
return s.cacheController
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendQuery implements CachedNameserver.
|
||||||
|
func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
|
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||||
|
|
||||||
|
if s.Name()+"." == "DOH//"+fqdn {
|
||||||
|
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
|
||||||
|
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
|
||||||
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
deadline = d
|
||||||
|
} else {
|
||||||
|
deadline = time.Now().Add(time.Second * 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, req := range reqs {
|
||||||
|
go func(r *dnsRequest) {
|
||||||
|
// generate new context for each req, using same context
|
||||||
|
// may cause reqs all aborted if any one encounter an error
|
||||||
|
dnsCtx := ctx
|
||||||
|
|
||||||
|
// reserve internal dns server requested Inbound
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
|
Protocol: "https",
|
||||||
|
SkipDNSResolve: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// forced to use mux for DOH
|
||||||
|
// dnsCtx = session.ContextWithMuxPreferred(dnsCtx, true)
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
b, err := dns.PackMessage(r.msg)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to pack dns query for ", fqdn)
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to retrieve response for ", fqdn)
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rec, err := parseResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", fqdn)
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.cacheController.updateRecord(r, rec)
|
||||||
|
}(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
|
||||||
|
body := bytes.NewBuffer(b)
|
||||||
|
req, err := http.NewRequest("POST", s.dohURL, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Accept", "application/dns-message")
|
||||||
|
req.Header.Add("Content-Type", "application/dns-message")
|
||||||
|
utils.TryDefaultHeadersWith(req.Header, "fetch")
|
||||||
|
req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))
|
||||||
|
|
||||||
|
hc := s.httpClient
|
||||||
|
|
||||||
|
resp, err := hc.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
io.Copy(io.Discard, resp.Body) // flush resp.Body so that the conn is reusable
|
||||||
|
return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
return queryIP(ctx, s, domain, option)
|
||||||
|
}
|
||||||
105
subproject/Xray-core-main/app/dns/nameserver_doh_test.go
Normal file
105
subproject/Xray-core-main/app/dns/nameserver_doh_test.go
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDOHNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDOHNameServerWithCache(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDOHNameServerWithIPv4Override(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(ip) != net.IPv4len {
|
||||||
|
t.Error("expect only IPv4 response from DNS query")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDOHNameServerWithIPv6Override(t *testing.T) {
|
||||||
|
url, err := url.Parse("https+local://1.1.1.1/dns-query")
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(ip) != net.IPv6len {
|
||||||
|
t.Error("expect only IPv6 response from DNS query")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
subproject/Xray-core-main/app/dns/nameserver_fakedns.go
Normal file
51
subproject/Xray-core-main/app/dns/nameserver_fakedns.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeDNSServer struct {
|
||||||
|
fakeDNSEngine dns.FakeDNSEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeDNSServer(fd dns.FakeDNSEngine) *FakeDNSServer {
|
||||||
|
return &FakeDNSServer{fakeDNSEngine: fd}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FakeDNSServer) Name() string {
|
||||||
|
return "FakeDNS"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDisableCache implements Server.
|
||||||
|
func (s *FakeDNSServer) IsDisableCache() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
if f.fakeDNSEngine == nil {
|
||||||
|
return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ips []net.Address
|
||||||
|
if fkr0, ok := f.fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||||
|
ips = fkr0.GetFakeIPForDomain3(domain, opt.IPv4Enable, opt.IPv6Enable)
|
||||||
|
} else {
|
||||||
|
ips = f.fakeDNSEngine.GetFakeIPForDomain(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
netIP, err := toNetIP(ips)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError()
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
|
||||||
|
|
||||||
|
if len(netIP) > 0 {
|
||||||
|
return netIP, 1, nil // fakeIP ttl is 1
|
||||||
|
}
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
54
subproject/Xray-core-main/app/dns/nameserver_local.go
Normal file
54
subproject/Xray-core-main/app/dns/nameserver_local.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/dns/localdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LocalNameServer is an wrapper over local DNS feature.
|
||||||
|
type LocalNameServer struct {
|
||||||
|
client *localdns.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
ips, ttl, err = s.client.LookupIP(domain, option)
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
errors.LogInfo(ctx, "Localhost got answer: ", domain, " -> ", ips)
|
||||||
|
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *LocalNameServer) Name() string {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDisableCache implements Server.
|
||||||
|
func (s *LocalNameServer) IsDisableCache() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalNameServer creates localdns server object for directly lookup in system DNS.
|
||||||
|
func NewLocalNameServer() *LocalNameServer {
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created localhost client")
|
||||||
|
return &LocalNameServer{
|
||||||
|
client: localdns.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
|
||||||
|
func NewLocalDNSClient(ipOption dns.IPOption) *Client {
|
||||||
|
return &Client{server: NewLocalNameServer(), ipOption: &ipOption}
|
||||||
|
}
|
||||||
26
subproject/Xray-core-main/app/dns/nameserver_local_test.go
Normal file
26
subproject/Xray-core-main/app/dns/nameserver_local_test.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalNameServer(t *testing.T) {
|
||||||
|
s := NewLocalNameServer()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
FakeEnable: false,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
276
subproject/Xray-core-main/app/dns/nameserver_quic.go
Normal file
276
subproject/Xray-core-main/app/dns/nameserver_quic.go
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/apernet/quic-go"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NextProtoDQ - During connection establishment, DNS/QUIC support is indicated
|
||||||
|
// by selecting the ALPN token "dq" in the crypto handshake.
|
||||||
|
const NextProtoDQ = "doq"
|
||||||
|
|
||||||
|
const handshakeTimeout = time.Second * 8
|
||||||
|
|
||||||
|
// QUICNameServer implemented DNS over QUIC
|
||||||
|
type QUICNameServer struct {
|
||||||
|
sync.RWMutex
|
||||||
|
cacheController *CacheController
|
||||||
|
destination *net.Destination
|
||||||
|
connection *quic.Conn
|
||||||
|
clientIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving
|
||||||
|
func NewQUICNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*QUICNameServer, error) {
|
||||||
|
var err error
|
||||||
|
port := net.Port(853)
|
||||||
|
if url.Port() != "" {
|
||||||
|
port, err = net.PortFromString(url.Port())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
|
||||||
|
|
||||||
|
s := &QUICNameServer{
|
||||||
|
cacheController: NewCacheController(url.String(), disableCache, serveStale, serveExpiredTTL),
|
||||||
|
destination: &dest,
|
||||||
|
clientIP: clientIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *QUICNameServer) Name() string {
|
||||||
|
return s.cacheController.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDisableCache implements Server.
|
||||||
|
func (s *QUICNameServer) IsDisableCache() bool {
|
||||||
|
return s.cacheController.disableCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) newReqID() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCacheController implements CachedNameServer.
|
||||||
|
func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheController }
|
||||||
|
|
||||||
|
// sendQuery implements CachedNameServer.
|
||||||
|
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
|
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||||
|
|
||||||
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
deadline = d
|
||||||
|
} else {
|
||||||
|
deadline = time.Now().Add(time.Second * 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, req := range reqs {
|
||||||
|
go func(r *dnsRequest) {
|
||||||
|
// generate new context for each req, using same context
|
||||||
|
// may cause reqs all aborted if any one encounter an error
|
||||||
|
dnsCtx := ctx
|
||||||
|
|
||||||
|
// reserve internal dns server requested Inbound
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
|
Protocol: "quic",
|
||||||
|
SkipDNSResolve: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
b, err := dns.PackMessage(r.msg)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsReqBuf := buf.New()
|
||||||
|
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "binary write failed")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = dnsReqBuf.Write(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "buffer write failed")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Release()
|
||||||
|
|
||||||
|
conn, err := s.openStream(dnsCtx)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to open quic connection")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to send query")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.Close()
|
||||||
|
|
||||||
|
respBuf := buf.New()
|
||||||
|
defer respBuf.Release()
|
||||||
|
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBuf.Clear()
|
||||||
|
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to handle response")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.cacheController.updateRecord(r, rec)
|
||||||
|
}(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
return queryIP(ctx, s, domain, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isActive(s *quic.Conn) bool {
|
||||||
|
select {
|
||||||
|
case <-s.Context().Done():
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) getConnection() (*quic.Conn, error) {
|
||||||
|
var conn *quic.Conn
|
||||||
|
s.RLock()
|
||||||
|
conn = s.connection
|
||||||
|
if conn != nil && isActive(conn) {
|
||||||
|
s.RUnlock()
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
// we're recreating the connection, let's create a new one
|
||||||
|
_ = conn.CloseWithError(0, "")
|
||||||
|
}
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
conn, err = s.openConnection()
|
||||||
|
if err != nil {
|
||||||
|
// This does not look too nice, but QUIC (or maybe quic-go)
|
||||||
|
// doesn't seem stable enough.
|
||||||
|
// Maybe retransmissions aren't fully implemented in quic-go?
|
||||||
|
// Anyways, the simple solution is to make a second try when
|
||||||
|
// it fails to open the QUIC connection.
|
||||||
|
conn, err = s.openConnection()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.connection = conn
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) openConnection() (*quic.Conn, error) {
|
||||||
|
tlsConfig := tls.Config{}
|
||||||
|
quicConfig := &quic.Config{
|
||||||
|
HandshakeIdleTimeout: handshakeTimeout,
|
||||||
|
}
|
||||||
|
tlsConfig.ServerName = s.destination.Address.String()
|
||||||
|
conn, err := quic.DialAddr(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig)
|
||||||
|
log.Record(&log.AccessMessage{
|
||||||
|
From: "DNS",
|
||||||
|
To: s.destination,
|
||||||
|
Status: log.AccessAccepted,
|
||||||
|
Detour: "local",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QUICNameServer) openStream(ctx context.Context) (*quic.Stream, error) {
|
||||||
|
conn, err := s.getConnection()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// open a new stream
|
||||||
|
return conn.OpenStreamSync(ctx)
|
||||||
|
}
|
||||||
87
subproject/Xray-core-main/app/dns/nameserver_quic_test.go
Normal file
87
subproject/Xray-core-main/app/dns/nameserver_quic_test.go
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQUICNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, _, err := s.QueryIP(ctx2, "google.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQUICNameServerWithIPv4Override(t *testing.T) {
|
||||||
|
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(ip) != net.IPv4len {
|
||||||
|
t.Error("expect only IPv4 response from DNS query")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQUICNameServerWithIPv6Override(t *testing.T) {
|
||||||
|
url, err := url.Parse("quic://dns.adguard-dns.com")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(ip) != net.IPv6len {
|
||||||
|
t.Error("expect only IPv6 response from DNS query")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
235
subproject/Xray-core-main/app/dns/nameserver_tcp.go
Normal file
235
subproject/Xray-core-main/app/dns/nameserver_tcp.go
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"net/url"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TCPNameServer implemented DNS over TCP (RFC7766).
|
||||||
|
type TCPNameServer struct {
|
||||||
|
cacheController *CacheController
|
||||||
|
destination *net.Destination
|
||||||
|
reqID uint32
|
||||||
|
dial func(context.Context) (net.Conn, error)
|
||||||
|
clientIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCPNameServer creates DNS over TCP server object for remote resolving.
|
||||||
|
func NewTCPNameServer(
|
||||||
|
url *url.URL,
|
||||||
|
dispatcher routing.Dispatcher,
|
||||||
|
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||||
|
clientIP net.IP,
|
||||||
|
) (*TCPNameServer, error) {
|
||||||
|
s, err := baseTCPNameServer(url, "TCP", disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dial = func(ctx context.Context) (net.Conn, error) {
|
||||||
|
link, err := dispatcher.Dispatch(toDnsContext(ctx, s.destination.String()), *s.destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnc.NewConnection(
|
||||||
|
cnc.ConnectionInputMulti(link.Writer),
|
||||||
|
cnc.ConnectionOutputMulti(link.Reader),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created TCP client initialized for ", url.String())
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCPLocalNameServer creates DNS over TCP client object for local resolving
|
||||||
|
func NewTCPLocalNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {
|
||||||
|
s, err := baseTCPNameServer(url, "TCPL", disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dial = func(ctx context.Context) (net.Conn, error) {
|
||||||
|
return internet.DialSystem(ctx, *s.destination, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created Local TCP client initialized for ", url.String())
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {
|
||||||
|
port := net.Port(53)
|
||||||
|
if url.Port() != "" {
|
||||||
|
var err error
|
||||||
|
if port, err = net.PortFromString(url.Port()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
|
||||||
|
|
||||||
|
s := &TCPNameServer{
|
||||||
|
cacheController: NewCacheController(prefix+"//"+dest.NetAddr(), disableCache, serveStale, serveExpiredTTL),
|
||||||
|
destination: &dest,
|
||||||
|
clientIP: clientIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *TCPNameServer) Name() string {
|
||||||
|
return s.cacheController.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDisableCache implements Server.
|
||||||
|
func (s *TCPNameServer) IsDisableCache() bool {
|
||||||
|
return s.cacheController.disableCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TCPNameServer) newReqID() uint16 {
|
||||||
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCacheController implements CachedNameserver.
|
||||||
|
func (s *TCPNameServer) getCacheController() *CacheController {
|
||||||
|
return s.cacheController
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendQuery implements CachedNameserver.
|
||||||
|
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
|
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||||
|
|
||||||
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
deadline = d
|
||||||
|
} else {
|
||||||
|
deadline = time.Now().Add(time.Second * 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, req := range reqs {
|
||||||
|
go func(r *dnsRequest) {
|
||||||
|
dnsCtx := ctx
|
||||||
|
|
||||||
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
|
dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
|
||||||
|
Protocol: "dns",
|
||||||
|
SkipDNSResolve: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
b, err := dns.PackMessage(r.msg)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.dial(dnsCtx)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to dial namesever")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
dnsReqBuf := buf.New()
|
||||||
|
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "binary write failed")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = dnsReqBuf.Write(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "buffer write failed")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Release()
|
||||||
|
|
||||||
|
_, err = conn.Write(dnsReqBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to send query")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dnsReqBuf.Release()
|
||||||
|
|
||||||
|
respBuf := buf.New()
|
||||||
|
defer respBuf.Release()
|
||||||
|
n, err := respBuf.ReadFullFrom(conn, 2)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to parse response length")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBuf.Clear()
|
||||||
|
n, err = respBuf.ReadFullFrom(conn, int32(length))
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to read response length")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec, err := parseResponse(respBuf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to parse DNS over TCP response")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cacheController.updateRecord(r, rec)
|
||||||
|
}(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
return queryIP(ctx, s, domain, option)
|
||||||
|
}
|
||||||
107
subproject/Xray-core-main/app/dns/nameserver_tcp_test.go
Normal file
107
subproject/Xray-core-main/app/dns/nameserver_tcp_test.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package dns_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
. "github.com/xtls/xray-core/app/dns"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTCPLocalNameServer(t *testing.T) {
|
||||||
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTCPLocalNameServerWithCache(t *testing.T) {
|
||||||
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
if r := cmp.Diff(ips2, ips); r != "" {
|
||||||
|
t.Fatal(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
|
||||||
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: true,
|
||||||
|
IPv6Enable: false,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(ip) != net.IPv4len {
|
||||||
|
t.Error("expect only IPv4 response from DNS query")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
|
||||||
|
url, err := url.Parse("tcp+local://8.8.8.8")
|
||||||
|
common.Must(err)
|
||||||
|
s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
|
||||||
|
common.Must(err)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
|
||||||
|
IPv4Enable: false,
|
||||||
|
IPv6Enable: true,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
if len(ips) == 0 {
|
||||||
|
t.Error("expect some ips, but got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(ip) != net.IPv6len {
|
||||||
|
t.Error("expect only IPv6 response from DNS query")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
191
subproject/Xray-core-main/app/dns/nameserver_udp.go
Normal file
191
subproject/Xray-core-main/app/dns/nameserver_udp.go
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/dns"
|
||||||
|
udp_proto "github.com/xtls/xray-core/common/protocol/udp"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
dns_feature "github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/udp"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClassicNameServer implemented traditional UDP DNS.
|
||||||
|
type ClassicNameServer struct {
|
||||||
|
sync.RWMutex
|
||||||
|
cacheController *CacheController
|
||||||
|
address *net.Destination
|
||||||
|
requests map[uint16]*udpDnsRequest
|
||||||
|
udpServer *udp.Dispatcher
|
||||||
|
requestsCleanup *task.Periodic
|
||||||
|
reqID uint32
|
||||||
|
clientIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpDnsRequest struct {
|
||||||
|
dnsRequest
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClassicNameServer creates udp server object for remote resolving.
|
||||||
|
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *ClassicNameServer {
|
||||||
|
// default to 53 if unspecific
|
||||||
|
if address.Port == 0 {
|
||||||
|
address.Port = net.Port(53)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &ClassicNameServer{
|
||||||
|
cacheController: NewCacheController(strings.ToUpper(address.String()), disableCache, serveStale, serveExpiredTTL),
|
||||||
|
address: &address,
|
||||||
|
requests: make(map[uint16]*udpDnsRequest),
|
||||||
|
clientIP: clientIP,
|
||||||
|
}
|
||||||
|
s.requestsCleanup = &task.Periodic{
|
||||||
|
Interval: time.Minute,
|
||||||
|
Execute: s.RequestsCleanup,
|
||||||
|
}
|
||||||
|
s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
|
||||||
|
|
||||||
|
errors.LogInfo(context.Background(), "DNS: created UDP client initialized for ", address.NetAddr())
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements Server.
|
||||||
|
func (s *ClassicNameServer) Name() string {
|
||||||
|
return s.cacheController.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDisableCache implements Server.
|
||||||
|
func (s *ClassicNameServer) IsDisableCache() bool {
|
||||||
|
return s.cacheController.disableCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestsCleanup clears expired items from cache
|
||||||
|
func (s *ClassicNameServer) RequestsCleanup() error {
|
||||||
|
now := time.Now()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
if len(s.requests) == 0 {
|
||||||
|
return errors.New(s.Name(), " nothing to do. stopping...")
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, req := range s.requests {
|
||||||
|
if req.expire.Before(now) {
|
||||||
|
delete(s.requests, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.requests) == 0 {
|
||||||
|
s.requests = make(map[uint16]*udpDnsRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleResponse handles udp response packet from remote DNS server.
|
||||||
|
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
||||||
|
payload := packet.Payload
|
||||||
|
ipRec, err := parseResponse(payload.Bytes())
|
||||||
|
payload.Release()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, s.Name(), " fail to parse responded DNS udp")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
id := ipRec.ReqID
|
||||||
|
req, ok := s.requests[id]
|
||||||
|
if ok {
|
||||||
|
// remove the pending request
|
||||||
|
delete(s.requests, id)
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
if !ok {
|
||||||
|
errors.LogErrorInner(ctx, err, s.Name(), " cannot find the pending request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if truncated, retry with EDNS0 option(udp payload size: 1350)
|
||||||
|
if ipRec.RawHeader.Truncated {
|
||||||
|
// if already has EDNS0 option, no need to retry
|
||||||
|
if len(req.msg.Additionals) == 0 {
|
||||||
|
// copy necessary meta data from original request
|
||||||
|
// and add EDNS0 option
|
||||||
|
opt := new(dnsmessage.Resource)
|
||||||
|
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
|
||||||
|
opt.Body = &dnsmessage.OPTResource{}
|
||||||
|
newMsg := *req.msg
|
||||||
|
newReq := *req
|
||||||
|
newMsg.Additionals = append(newMsg.Additionals, *opt)
|
||||||
|
newMsg.ID = s.newReqID()
|
||||||
|
newReq.msg = &newMsg
|
||||||
|
s.addPendingRequest(&newReq)
|
||||||
|
b, _ := dns.PackMessage(newReq.msg)
|
||||||
|
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||||
|
b.UDP = ©Dest
|
||||||
|
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cacheController.updateRecord(&req.dnsRequest, ipRec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClassicNameServer) newReqID() uint16 {
|
||||||
|
return uint16(atomic.AddUint32(&s.reqID, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
|
||||||
|
s.Lock()
|
||||||
|
id := req.msg.ID
|
||||||
|
req.expire = time.Now().Add(time.Second * 8)
|
||||||
|
s.requests[id] = req
|
||||||
|
s.Unlock()
|
||||||
|
common.Must(s.requestsCleanup.Start())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCacheController implements CachedNameserver.
|
||||||
|
func (s *ClassicNameServer) getCacheController() *CacheController {
|
||||||
|
return s.cacheController
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendQuery implements CachedNameserver.
|
||||||
|
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
|
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||||
|
|
||||||
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
|
|
||||||
|
for _, req := range reqs {
|
||||||
|
udpReq := &udpDnsRequest{
|
||||||
|
dnsRequest: *req,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
s.addPendingRequest(udpReq)
|
||||||
|
b, err := dns.PackMessage(req.msg)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogErrorInner(ctx, err, "failed to pack dns query")
|
||||||
|
if noResponseErrCh != nil {
|
||||||
|
noResponseErrCh <- err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||||
|
b.UDP = ©Dest
|
||||||
|
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryIP implements Server.
|
||||||
|
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
|
||||||
|
return queryIP(ctx, s, domain, option)
|
||||||
|
}
|
||||||
55
subproject/Xray-core-main/app/log/command/command.go
Normal file
55
subproject/Xray-core-main/app/log/command/command.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/log"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoggerServer struct {
|
||||||
|
V *core.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestartLogger implements LoggerService.
|
||||||
|
func (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLoggerRequest) (*RestartLoggerResponse, error) {
|
||||||
|
logger := s.V.GetFeature((*log.Instance)(nil))
|
||||||
|
if logger == nil {
|
||||||
|
return nil, errors.New("unable to get logger instance")
|
||||||
|
}
|
||||||
|
if err := logger.Close(); err != nil {
|
||||||
|
return nil, errors.New("failed to close logger").Base(err)
|
||||||
|
}
|
||||||
|
if err := logger.Start(); err != nil {
|
||||||
|
return nil, errors.New("failed to start logger").Base(err)
|
||||||
|
}
|
||||||
|
return &RestartLoggerResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
v *core.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Register(server *grpc.Server) {
|
||||||
|
ls := &LoggerServer{
|
||||||
|
V: s.v,
|
||||||
|
}
|
||||||
|
RegisterLoggerServiceServer(server, ls)
|
||||||
|
|
||||||
|
// For compatibility purposes
|
||||||
|
vCoreDesc := LoggerService_ServiceDesc
|
||||||
|
vCoreDesc.ServiceName = "v2ray.core.app.log.command.LoggerService"
|
||||||
|
server.RegisterService(&vCoreDesc, ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
s := core.MustFromContext(ctx)
|
||||||
|
return &service{v: s}, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
34
subproject/Xray-core-main/app/log/command/command_test.go
Normal file
34
subproject/Xray-core-main/app/log/command/command_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package command_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/dispatcher"
|
||||||
|
"github.com/xtls/xray-core/app/log"
|
||||||
|
. "github.com/xtls/xray-core/app/log/command"
|
||||||
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
|
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||||
|
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoggerRestart(t *testing.T) {
|
||||||
|
v, err := core.New(&core.Config{
|
||||||
|
App: []*serial.TypedMessage{
|
||||||
|
serial.ToTypedMessage(&log.Config{}),
|
||||||
|
serial.ToTypedMessage(&dispatcher.Config{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
||||||
|
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
common.Must(v.Start())
|
||||||
|
|
||||||
|
server := &LoggerServer{
|
||||||
|
V: v,
|
||||||
|
}
|
||||||
|
common.Must2(server.RestartLogger(context.Background(), &RestartLoggerRequest{}))
|
||||||
|
}
|
||||||
194
subproject/Xray-core-main/app/log/command/config.pb.go
Normal file
194
subproject/Xray-core-main/app/log/command/config.pb.go
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/log/command/config.proto
|
||||||
|
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_command_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestartLoggerRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RestartLoggerRequest) Reset() {
|
||||||
|
*x = RestartLoggerRequest{}
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RestartLoggerRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RestartLoggerRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RestartLoggerRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RestartLoggerRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RestartLoggerRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_command_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestartLoggerResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RestartLoggerResponse) Reset() {
|
||||||
|
*x = RestartLoggerResponse{}
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RestartLoggerResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RestartLoggerResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RestartLoggerResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_log_command_config_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RestartLoggerResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RestartLoggerResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_command_config_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_log_command_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_log_command_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1capp/log/command/config.proto\x12\x14xray.app.log.command\"\b\n" +
|
||||||
|
"\x06Config\"\x16\n" +
|
||||||
|
"\x14RestartLoggerRequest\"\x17\n" +
|
||||||
|
"\x15RestartLoggerResponse2{\n" +
|
||||||
|
"\rLoggerService\x12j\n" +
|
||||||
|
"\rRestartLogger\x12*.xray.app.log.command.RestartLoggerRequest\x1a+.xray.app.log.command.RestartLoggerResponse\"\x00B^\n" +
|
||||||
|
"\x18com.xray.app.log.commandP\x01Z)github.com/xtls/xray-core/app/log/command\xaa\x02\x14Xray.App.Log.Commandb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_log_command_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_log_command_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_log_command_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_log_command_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_log_command_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_log_command_config_proto_rawDesc), len(file_app_log_command_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_log_command_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||||
|
var file_app_log_command_config_proto_goTypes = []any{
|
||||||
|
(*Config)(nil), // 0: xray.app.log.command.Config
|
||||||
|
(*RestartLoggerRequest)(nil), // 1: xray.app.log.command.RestartLoggerRequest
|
||||||
|
(*RestartLoggerResponse)(nil), // 2: xray.app.log.command.RestartLoggerResponse
|
||||||
|
}
|
||||||
|
var file_app_log_command_config_proto_depIdxs = []int32{
|
||||||
|
1, // 0: xray.app.log.command.LoggerService.RestartLogger:input_type -> xray.app.log.command.RestartLoggerRequest
|
||||||
|
2, // 1: xray.app.log.command.LoggerService.RestartLogger:output_type -> xray.app.log.command.RestartLoggerResponse
|
||||||
|
1, // [1:2] is the sub-list for method output_type
|
||||||
|
0, // [0:1] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_log_command_config_proto_init() }
|
||||||
|
func file_app_log_command_config_proto_init() {
|
||||||
|
if File_app_log_command_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_log_command_config_proto_rawDesc), len(file_app_log_command_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 3,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_log_command_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_log_command_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_log_command_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_log_command_config_proto = out.File
|
||||||
|
file_app_log_command_config_proto_goTypes = nil
|
||||||
|
file_app_log_command_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
17
subproject/Xray-core-main/app/log/command/config.proto
Normal file
17
subproject/Xray-core-main/app/log/command/config.proto
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.log.command;
|
||||||
|
option csharp_namespace = "Xray.App.Log.Command";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/log/command";
|
||||||
|
option java_package = "com.xray.app.log.command";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Config {}
|
||||||
|
|
||||||
|
message RestartLoggerRequest {}
|
||||||
|
|
||||||
|
message RestartLoggerResponse {}
|
||||||
|
|
||||||
|
service LoggerService {
|
||||||
|
rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {}
|
||||||
|
}
|
||||||
121
subproject/Xray-core-main/app/log/command/config_grpc.pb.go
Normal file
121
subproject/Xray-core-main/app/log/command/config_grpc.pb.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
|
// - protoc v6.33.5
|
||||||
|
// source: app/log/command/config.proto
|
||||||
|
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoggerService_RestartLogger_FullMethodName = "/xray.app.log.command.LoggerService/RestartLogger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggerServiceClient is the client API for LoggerService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type LoggerServiceClient interface {
|
||||||
|
RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoggerServiceClient(cc grpc.ClientConnInterface) LoggerServiceClient {
|
||||||
|
return &loggerServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(RestartLoggerResponse)
|
||||||
|
err := c.cc.Invoke(ctx, LoggerService_RestartLogger_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerServiceServer is the server API for LoggerService service.
|
||||||
|
// All implementations must embed UnimplementedLoggerServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type LoggerServiceServer interface {
|
||||||
|
RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)
|
||||||
|
mustEmbedUnimplementedLoggerServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedLoggerServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedLoggerServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method RestartLogger not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {}
|
||||||
|
func (UnimplementedLoggerServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to LoggerServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeLoggerServiceServer interface {
|
||||||
|
mustEmbedUnimplementedLoggerServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterLoggerServiceServer(s grpc.ServiceRegistrar, srv LoggerServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedLoggerServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&LoggerService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RestartLoggerRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(LoggerServiceServer).RestartLogger(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: LoggerService_RestartLogger_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(LoggerServiceServer).RestartLogger(ctx, req.(*RestartLoggerRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerService_ServiceDesc is the grpc.ServiceDesc for LoggerService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var LoggerService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "xray.app.log.command.LoggerService",
|
||||||
|
HandlerType: (*LoggerServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "RestartLogger",
|
||||||
|
Handler: _LoggerService_RestartLogger_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "app/log/command/config.proto",
|
||||||
|
}
|
||||||
242
subproject/Xray-core-main/app/log/config.pb.go
Normal file
242
subproject/Xray-core-main/app/log/config.pb.go
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/log/config.proto
|
||||||
|
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/xtls/xray-core/common/log"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogType_None LogType = 0
|
||||||
|
LogType_Console LogType = 1
|
||||||
|
LogType_File LogType = 2
|
||||||
|
LogType_Event LogType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for LogType.
|
||||||
|
var (
|
||||||
|
LogType_name = map[int32]string{
|
||||||
|
0: "None",
|
||||||
|
1: "Console",
|
||||||
|
2: "File",
|
||||||
|
3: "Event",
|
||||||
|
}
|
||||||
|
LogType_value = map[string]int32{
|
||||||
|
"None": 0,
|
||||||
|
"Console": 1,
|
||||||
|
"File": 2,
|
||||||
|
"Event": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x LogType) Enum() *LogType {
|
||||||
|
p := new(LogType)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x LogType) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_app_log_config_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogType) Type() protoreflect.EnumType {
|
||||||
|
return &file_app_log_config_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x LogType) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LogType.Descriptor instead.
|
||||||
|
func (LogType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ErrorLogType LogType `protobuf:"varint,1,opt,name=error_log_type,json=errorLogType,proto3,enum=xray.app.log.LogType" json:"error_log_type,omitempty"`
|
||||||
|
ErrorLogLevel log.Severity `protobuf:"varint,2,opt,name=error_log_level,json=errorLogLevel,proto3,enum=xray.common.log.Severity" json:"error_log_level,omitempty"`
|
||||||
|
ErrorLogPath string `protobuf:"bytes,3,opt,name=error_log_path,json=errorLogPath,proto3" json:"error_log_path,omitempty"`
|
||||||
|
AccessLogType LogType `protobuf:"varint,4,opt,name=access_log_type,json=accessLogType,proto3,enum=xray.app.log.LogType" json:"access_log_type,omitempty"`
|
||||||
|
AccessLogPath string `protobuf:"bytes,5,opt,name=access_log_path,json=accessLogPath,proto3" json:"access_log_path,omitempty"`
|
||||||
|
EnableDnsLog bool `protobuf:"varint,6,opt,name=enable_dns_log,json=enableDnsLog,proto3" json:"enable_dns_log,omitempty"`
|
||||||
|
MaskAddress string `protobuf:"bytes,7,opt,name=mask_address,json=maskAddress,proto3" json:"mask_address,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_log_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_log_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_log_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetErrorLogType() LogType {
|
||||||
|
if x != nil {
|
||||||
|
return x.ErrorLogType
|
||||||
|
}
|
||||||
|
return LogType_None
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetErrorLogLevel() log.Severity {
|
||||||
|
if x != nil {
|
||||||
|
return x.ErrorLogLevel
|
||||||
|
}
|
||||||
|
return log.Severity(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetErrorLogPath() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ErrorLogPath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetAccessLogType() LogType {
|
||||||
|
if x != nil {
|
||||||
|
return x.AccessLogType
|
||||||
|
}
|
||||||
|
return LogType_None
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetAccessLogPath() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AccessLogPath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEnableDnsLog() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.EnableDnsLog
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetMaskAddress() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.MaskAddress
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_log_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_log_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x14app/log/config.proto\x12\fxray.app.log\x1a\x14common/log/log.proto\"\xde\x02\n" +
|
||||||
|
"\x06Config\x12;\n" +
|
||||||
|
"\x0eerror_log_type\x18\x01 \x01(\x0e2\x15.xray.app.log.LogTypeR\ferrorLogType\x12A\n" +
|
||||||
|
"\x0ferror_log_level\x18\x02 \x01(\x0e2\x19.xray.common.log.SeverityR\rerrorLogLevel\x12$\n" +
|
||||||
|
"\x0eerror_log_path\x18\x03 \x01(\tR\ferrorLogPath\x12=\n" +
|
||||||
|
"\x0faccess_log_type\x18\x04 \x01(\x0e2\x15.xray.app.log.LogTypeR\raccessLogType\x12&\n" +
|
||||||
|
"\x0faccess_log_path\x18\x05 \x01(\tR\raccessLogPath\x12$\n" +
|
||||||
|
"\x0eenable_dns_log\x18\x06 \x01(\bR\fenableDnsLog\x12!\n" +
|
||||||
|
"\fmask_address\x18\a \x01(\tR\vmaskAddress*5\n" +
|
||||||
|
"\aLogType\x12\b\n" +
|
||||||
|
"\x04None\x10\x00\x12\v\n" +
|
||||||
|
"\aConsole\x10\x01\x12\b\n" +
|
||||||
|
"\x04File\x10\x02\x12\t\n" +
|
||||||
|
"\x05Event\x10\x03BF\n" +
|
||||||
|
"\x10com.xray.app.logP\x01Z!github.com/xtls/xray-core/app/log\xaa\x02\fXray.App.Logb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_log_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_log_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_log_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_log_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_log_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_log_config_proto_rawDesc), len(file_app_log_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_log_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_log_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_app_log_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_app_log_config_proto_goTypes = []any{
|
||||||
|
(LogType)(0), // 0: xray.app.log.LogType
|
||||||
|
(*Config)(nil), // 1: xray.app.log.Config
|
||||||
|
(log.Severity)(0), // 2: xray.common.log.Severity
|
||||||
|
}
|
||||||
|
var file_app_log_config_proto_depIdxs = []int32{
|
||||||
|
0, // 0: xray.app.log.Config.error_log_type:type_name -> xray.app.log.LogType
|
||||||
|
2, // 1: xray.app.log.Config.error_log_level:type_name -> xray.common.log.Severity
|
||||||
|
0, // 2: xray.app.log.Config.access_log_type:type_name -> xray.app.log.LogType
|
||||||
|
3, // [3:3] is the sub-list for method output_type
|
||||||
|
3, // [3:3] is the sub-list for method input_type
|
||||||
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_log_config_proto_init() }
|
||||||
|
func file_app_log_config_proto_init() {
|
||||||
|
if File_app_log_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_log_config_proto_rawDesc), len(file_app_log_config_proto_rawDesc)),
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_log_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_log_config_proto_depIdxs,
|
||||||
|
EnumInfos: file_app_log_config_proto_enumTypes,
|
||||||
|
MessageInfos: file_app_log_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_log_config_proto = out.File
|
||||||
|
file_app_log_config_proto_goTypes = nil
|
||||||
|
file_app_log_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
27
subproject/Xray-core-main/app/log/config.proto
Normal file
27
subproject/Xray-core-main/app/log/config.proto
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.log;
|
||||||
|
option csharp_namespace = "Xray.App.Log";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/log";
|
||||||
|
option java_package = "com.xray.app.log";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "common/log/log.proto";
|
||||||
|
|
||||||
|
enum LogType {
|
||||||
|
None = 0;
|
||||||
|
Console = 1;
|
||||||
|
File = 2;
|
||||||
|
Event = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
LogType error_log_type = 1;
|
||||||
|
xray.common.log.Severity error_log_level = 2;
|
||||||
|
string error_log_path = 3;
|
||||||
|
|
||||||
|
LogType access_log_type = 4;
|
||||||
|
string access_log_path = 5;
|
||||||
|
bool enable_dns_log = 6;
|
||||||
|
string mask_address= 7;
|
||||||
|
}
|
||||||
256
subproject/Xray-core-main/app/log/log.go
Normal file
256
subproject/Xray-core-main/app/log/log.go
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Instance is a log.Handler that handles logs.
|
||||||
|
type Instance struct {
|
||||||
|
sync.RWMutex
|
||||||
|
config *Config
|
||||||
|
accessLogger log.Handler
|
||||||
|
errorLogger log.Handler
|
||||||
|
active bool
|
||||||
|
dns bool
|
||||||
|
mask4 int
|
||||||
|
mask6 int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new log.Instance based on the given config.
|
||||||
|
func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||||
|
m4, m6, err := ParseMaskAddress(config.MaskAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := &Instance{
|
||||||
|
config: config,
|
||||||
|
active: false,
|
||||||
|
dns: config.EnableDnsLog,
|
||||||
|
mask4: m4,
|
||||||
|
mask6: m6,
|
||||||
|
}
|
||||||
|
log.RegisterHandler(g)
|
||||||
|
|
||||||
|
// start logger now,
|
||||||
|
// then other modules will be able to log during initialization
|
||||||
|
if err := g.startInternal(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogDebug(ctx, "Logger started")
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) initAccessLogger() error {
|
||||||
|
handler, err := createHandler(g.config.AccessLogType, HandlerCreatorOptions{
|
||||||
|
Path: g.config.AccessLogPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.accessLogger = handler
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) initErrorLogger() error {
|
||||||
|
handler, err := createHandler(g.config.ErrorLogType, HandlerCreatorOptions{
|
||||||
|
Path: g.config.ErrorLogPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.errorLogger = handler
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (*Instance) Type() interface{} {
|
||||||
|
return (*Instance)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Instance) startInternal() error {
|
||||||
|
g.Lock()
|
||||||
|
defer g.Unlock()
|
||||||
|
|
||||||
|
if g.active {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
g.active = true
|
||||||
|
|
||||||
|
if err := g.initAccessLogger(); err != nil {
|
||||||
|
return errors.New("failed to initialize access logger").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
if err := g.initErrorLogger(); err != nil {
|
||||||
|
return errors.New("failed to initialize error logger").Base(err).AtWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.Start().
|
||||||
|
func (g *Instance) Start() error {
|
||||||
|
return g.startInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle implements log.Handler.
|
||||||
|
func (g *Instance) Handle(msg log.Message) {
|
||||||
|
g.RLock()
|
||||||
|
defer g.RUnlock()
|
||||||
|
|
||||||
|
if !g.active {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var Msg log.Message
|
||||||
|
if g.config.MaskAddress != "" {
|
||||||
|
Msg = &MaskedMsgWrapper{
|
||||||
|
Message: msg,
|
||||||
|
Mask4: g.mask4,
|
||||||
|
Mask6: g.mask6,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Msg = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *log.AccessMessage:
|
||||||
|
if g.accessLogger != nil {
|
||||||
|
g.accessLogger.Handle(Msg)
|
||||||
|
}
|
||||||
|
case *log.DNSLog:
|
||||||
|
if g.dns && g.accessLogger != nil {
|
||||||
|
g.accessLogger.Handle(Msg)
|
||||||
|
}
|
||||||
|
case *log.GeneralMessage:
|
||||||
|
if g.errorLogger != nil && msg.Severity <= g.config.ErrorLogLevel {
|
||||||
|
g.errorLogger.Handle(Msg)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Swallow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.Close().
|
||||||
|
func (g *Instance) Close() error {
|
||||||
|
errors.LogDebug(context.Background(), "Logger closing")
|
||||||
|
|
||||||
|
g.Lock()
|
||||||
|
defer g.Unlock()
|
||||||
|
|
||||||
|
if !g.active {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
g.active = false
|
||||||
|
|
||||||
|
common.Close(g.accessLogger)
|
||||||
|
g.accessLogger = nil
|
||||||
|
|
||||||
|
common.Close(g.errorLogger)
|
||||||
|
g.errorLogger = nil
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseMaskAddress(c string) (int, int, error) {
|
||||||
|
var m4, m6 int
|
||||||
|
switch c {
|
||||||
|
case "half":
|
||||||
|
m4, m6 = 16, 32
|
||||||
|
case "quarter":
|
||||||
|
m4, m6 = 8, 16
|
||||||
|
case "full":
|
||||||
|
m4, m6 = 0, 0
|
||||||
|
case "":
|
||||||
|
// do nothing
|
||||||
|
default:
|
||||||
|
if parts := strings.Split(c, "+"); len(parts) > 0 {
|
||||||
|
if len(parts) >= 1 && parts[0] != "" {
|
||||||
|
i, err := strconv.Atoi(strings.TrimPrefix(parts[0], "/"))
|
||||||
|
if err != nil {
|
||||||
|
return 32, 128, err
|
||||||
|
}
|
||||||
|
m4 = i
|
||||||
|
}
|
||||||
|
if len(parts) >= 2 && parts[1] != "" {
|
||||||
|
i, err := strconv.Atoi(strings.TrimPrefix(parts[1], "/"))
|
||||||
|
if err != nil {
|
||||||
|
return 32, 128, err
|
||||||
|
}
|
||||||
|
m6 = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m4%8 != 0 || m4 > 32 || m4 < 0 {
|
||||||
|
return 32, 128, errors.New("Log Mask: ipv4 mask must be divisible by 8 and between 0-32")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m4, m6, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskedMsgWrapper is to wrap the string() method to mask IP addresses in the log.
|
||||||
|
type MaskedMsgWrapper struct {
|
||||||
|
log.Message
|
||||||
|
Mask4 int
|
||||||
|
Mask6 int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ipv4Regex = regexp.MustCompile(`(\d{1,3}\.){3}\d{1,3}`)
|
||||||
|
ipv6Regex = regexp.MustCompile(`(?:[\da-fA-F]{0,4}:[\da-fA-F]{0,4}){2,7}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *MaskedMsgWrapper) String() string {
|
||||||
|
str := m.Message.String()
|
||||||
|
|
||||||
|
// Process ipv4
|
||||||
|
maskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(s string) string {
|
||||||
|
if m.Mask4 == 32 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if m.Mask4 == 0 {
|
||||||
|
return "[Masked IPv4]"
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(s, ".")
|
||||||
|
for i := m.Mask4 / 8; i < 4; i++ {
|
||||||
|
parts[i] = "*"
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ".")
|
||||||
|
})
|
||||||
|
|
||||||
|
// process ipv6
|
||||||
|
maskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(s string) string {
|
||||||
|
if m.Mask6 == 128 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if m.Mask6 == 0 {
|
||||||
|
return "Masked IPv6"
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(s)
|
||||||
|
if ip == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ip.Mask(net.CIDRMask(m.Mask6, 128)).String() + "/" + strconv.Itoa(m.Mask6)
|
||||||
|
})
|
||||||
|
|
||||||
|
return maskedMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
60
subproject/Xray-core-main/app/log/log_creator.go
Normal file
60
subproject/Xray-core-main/app/log/log_creator.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerCreatorOptions struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerCreator func(LogType, HandlerCreatorOptions) (log.Handler, error)
|
||||||
|
|
||||||
|
var handlerCreatorMap = make(map[LogType]HandlerCreator)
|
||||||
|
|
||||||
|
var handlerCreatorMapLock = &sync.RWMutex{}
|
||||||
|
|
||||||
|
func RegisterHandlerCreator(logType LogType, f HandlerCreator) error {
|
||||||
|
if f == nil {
|
||||||
|
return errors.New("nil HandlerCreator")
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerCreatorMapLock.Lock()
|
||||||
|
defer handlerCreatorMapLock.Unlock()
|
||||||
|
|
||||||
|
handlerCreatorMap[logType] = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createHandler(logType LogType, options HandlerCreatorOptions) (log.Handler, error) {
|
||||||
|
handlerCreatorMapLock.RLock()
|
||||||
|
defer handlerCreatorMapLock.RUnlock()
|
||||||
|
|
||||||
|
creator, found := handlerCreatorMap[logType]
|
||||||
|
if !found {
|
||||||
|
return nil, errors.New("unable to create log handler for ", logType)
|
||||||
|
}
|
||||||
|
return creator(logType, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(RegisterHandlerCreator(LogType_Console, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) {
|
||||||
|
return log.NewLogger(log.CreateStdoutLogWriter()), nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
common.Must(RegisterHandlerCreator(LogType_File, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) {
|
||||||
|
creator, err := log.CreateFileLogWriter(options.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return log.NewLogger(creator), nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
common.Must(RegisterHandlerCreator(LogType_None, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) {
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
89
subproject/Xray-core-main/app/log/log_test.go
Normal file
89
subproject/Xray-core-main/app/log/log_test.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
package log_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/xtls/xray-core/app/log"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
clog "github.com/xtls/xray-core/common/log"
|
||||||
|
"github.com/xtls/xray-core/testing/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCustomLogHandler(t *testing.T) {
|
||||||
|
mockCtl := gomock.NewController(t)
|
||||||
|
defer mockCtl.Finish()
|
||||||
|
|
||||||
|
var loggedValue []string
|
||||||
|
|
||||||
|
mockHandler := mocks.NewLogHandler(mockCtl)
|
||||||
|
mockHandler.EXPECT().Handle(gomock.Any()).AnyTimes().DoAndReturn(func(msg clog.Message) {
|
||||||
|
loggedValue = append(loggedValue, msg.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
log.RegisterHandlerCreator(log.LogType_Console, func(lt log.LogType, options log.HandlerCreatorOptions) (clog.Handler, error) {
|
||||||
|
return mockHandler, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
logger, err := log.New(context.Background(), &log.Config{
|
||||||
|
ErrorLogLevel: clog.Severity_Debug,
|
||||||
|
ErrorLogType: log.LogType_Console,
|
||||||
|
AccessLogType: log.LogType_None,
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
common.Must(logger.Start())
|
||||||
|
|
||||||
|
clog.Record(&clog.GeneralMessage{
|
||||||
|
Severity: clog.Severity_Debug,
|
||||||
|
Content: "test",
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(loggedValue) < 2 {
|
||||||
|
t.Fatal("expected 2 log messages, but actually ", loggedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if loggedValue[1] != "[Debug] test" {
|
||||||
|
t.Fatal("expected '[Debug] test', but actually ", loggedValue[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
common.Must(logger.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaskAddress(t *testing.T) {
|
||||||
|
m4, m6, err := log.ParseMaskAddress("half")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
maskedAddr := log.MaskedMsgWrapper{
|
||||||
|
Mask4: m4,
|
||||||
|
Mask6: m6,
|
||||||
|
}
|
||||||
|
maskedAddr.Message = net.ParseIP("11.45.1.4")
|
||||||
|
if maskedAddr.String() != "11.45.*.*" {
|
||||||
|
t.Fatal("expected '11.45.*.*', but actually ", maskedAddr.String())
|
||||||
|
}
|
||||||
|
maskedAddr.Message = net.ParseIP("11:45:14:19:19:81:0::")
|
||||||
|
if maskedAddr.String() != "11:45::/32" {
|
||||||
|
t.Fatal("expected '11:45::/32', but actually", maskedAddr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
m4, m6, err = log.ParseMaskAddress("/16+/64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
maskedAddr = log.MaskedMsgWrapper{
|
||||||
|
Mask4: m4,
|
||||||
|
Mask6: m6,
|
||||||
|
}
|
||||||
|
maskedAddr.Message = net.ParseIP("11.45.1.4")
|
||||||
|
if maskedAddr.String() != "11.45.*.*" {
|
||||||
|
t.Fatal("expected '11.45.*.*', but actually ", maskedAddr.String())
|
||||||
|
}
|
||||||
|
maskedAddr.Message = net.ParseIP("11:45:14:19:19:81:0::")
|
||||||
|
if maskedAddr.String() != "11:45:14:19::/64" {
|
||||||
|
t.Fatal("expected '11:45:14:19::/64', but actually", maskedAddr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
134
subproject/Xray-core-main/app/metrics/config.pb.go
Normal file
134
subproject/Xray-core-main/app/metrics/config.pb.go
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/metrics/config.proto
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the settings for metrics.
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// Tag of the outbound handler that handles metrics http connections.
|
||||||
|
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||||
|
Listen string `protobuf:"bytes,2,opt,name=listen,proto3" json:"listen,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_metrics_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_metrics_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_metrics_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetTag() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tag
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetListen() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Listen
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_metrics_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_metrics_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x18app/metrics/config.proto\x12\x10xray.app.metrics\"2\n" +
|
||||||
|
"\x06Config\x12\x10\n" +
|
||||||
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x12\x16\n" +
|
||||||
|
"\x06listen\x18\x02 \x01(\tR\x06listenBR\n" +
|
||||||
|
"\x14com.xray.app.metricsP\x01Z%github.com/xtls/xray-core/app/metrics\xaa\x02\x10Xray.App.Metricsb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_metrics_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_metrics_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_metrics_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_metrics_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_metrics_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_metrics_config_proto_rawDesc), len(file_app_metrics_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_metrics_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_metrics_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_app_metrics_config_proto_goTypes = []any{
|
||||||
|
(*Config)(nil), // 0: xray.app.metrics.Config
|
||||||
|
}
|
||||||
|
var file_app_metrics_config_proto_depIdxs = []int32{
|
||||||
|
0, // [0:0] is the sub-list for method output_type
|
||||||
|
0, // [0:0] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_metrics_config_proto_init() }
|
||||||
|
func file_app_metrics_config_proto_init() {
|
||||||
|
if File_app_metrics_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_metrics_config_proto_rawDesc), len(file_app_metrics_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_metrics_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_metrics_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_metrics_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_metrics_config_proto = out.File
|
||||||
|
file_app_metrics_config_proto_goTypes = nil
|
||||||
|
file_app_metrics_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
14
subproject/Xray-core-main/app/metrics/config.proto
Normal file
14
subproject/Xray-core-main/app/metrics/config.proto
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.metrics;
|
||||||
|
option csharp_namespace = "Xray.App.Metrics";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/metrics";
|
||||||
|
option java_package = "com.xray.app.metrics";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
// Config is the settings for metrics.
|
||||||
|
message Config {
|
||||||
|
// Tag of the outbound handler that handles metrics http connections.
|
||||||
|
string tag = 1;
|
||||||
|
string listen = 2;
|
||||||
|
}
|
||||||
139
subproject/Xray-core-main/app/metrics/metrics.go
Normal file
139
subproject/Xray-core-main/app/metrics/metrics.go
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"expvar"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/observatory"
|
||||||
|
"github.com/xtls/xray-core/app/stats"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/extension"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
feature_stats "github.com/xtls/xray-core/features/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetricsHandler struct {
|
||||||
|
ohm outbound.Manager
|
||||||
|
statsManager feature_stats.Manager
|
||||||
|
observatory extension.Observatory
|
||||||
|
tag string
|
||||||
|
listen string
|
||||||
|
tcpListener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
||||||
|
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
||||||
|
c := &MetricsHandler{
|
||||||
|
tag: config.Tag,
|
||||||
|
listen: config.Listen,
|
||||||
|
}
|
||||||
|
common.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) {
|
||||||
|
c.statsManager = sm
|
||||||
|
c.ohm = om
|
||||||
|
}))
|
||||||
|
expvar.Publish("stats", expvar.Func(func() interface{} {
|
||||||
|
manager, ok := c.statsManager.(*stats.Manager)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp := map[string]map[string]map[string]int64{
|
||||||
|
"inbound": {},
|
||||||
|
"outbound": {},
|
||||||
|
"user": {},
|
||||||
|
}
|
||||||
|
manager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
|
||||||
|
nameSplit := strings.Split(name, ">>>")
|
||||||
|
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
|
||||||
|
if item, found := resp[typeName][tagOrUser]; found {
|
||||||
|
item[direction] = counter.Value()
|
||||||
|
} else {
|
||||||
|
resp[typeName][tagOrUser] = map[string]int64{
|
||||||
|
direction: counter.Value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return resp
|
||||||
|
}))
|
||||||
|
expvar.Publish("observatory", expvar.Func(func() interface{} {
|
||||||
|
if c.observatory == nil {
|
||||||
|
common.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {
|
||||||
|
c.observatory = observatory
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
if c.observatory == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp := map[string]*observatory.OutboundStatus{}
|
||||||
|
if o, err := c.observatory.GetObservation(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
|
||||||
|
resp[x.OutboundTag] = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}))
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) Type() interface{} {
|
||||||
|
return (*MetricsHandler)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) Start() error {
|
||||||
|
|
||||||
|
// direct listen a port if listen is set
|
||||||
|
if p.listen != "" {
|
||||||
|
TCPlistener, err := net.Listen("tcp", p.listen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.tcpListener = TCPlistener
|
||||||
|
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := &OutboundListener{
|
||||||
|
buffer: make(chan net.Conn, 4),
|
||||||
|
done: done.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
||||||
|
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.ohm.AddHandler(context.Background(), &Outbound{
|
||||||
|
tag: p.tag,
|
||||||
|
listener: listener,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MetricsHandler) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
return NewMetricsHandler(ctx, cfg.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
121
subproject/Xray-core-main/app/metrics/outbound.go
Normal file
121
subproject/Xray-core-main/app/metrics/outbound.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
"github.com/xtls/xray-core/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutboundListener is a net.Listener for listening metrics http connections.
|
||||||
|
type OutboundListener struct {
|
||||||
|
buffer chan net.Conn
|
||||||
|
done *done.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OutboundListener) add(conn net.Conn) {
|
||||||
|
select {
|
||||||
|
case l.buffer <- conn:
|
||||||
|
case <-l.done.Wait():
|
||||||
|
conn.Close()
|
||||||
|
default:
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept implements net.Listener.
|
||||||
|
func (l *OutboundListener) Accept() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case <-l.done.Wait():
|
||||||
|
return nil, errors.New("listen closed")
|
||||||
|
case c := <-l.buffer:
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implement net.Listener.
|
||||||
|
func (l *OutboundListener) Close() error {
|
||||||
|
common.Must(l.done.Close())
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-l.buffer:
|
||||||
|
c.Close()
|
||||||
|
default:
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr implements net.Listener.
|
||||||
|
func (l *OutboundListener) Addr() net.Addr {
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: net.IP{0, 0, 0, 0},
|
||||||
|
Port: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outbound is an outbound.Handler that handles metrics http connections.
|
||||||
|
type Outbound struct {
|
||||||
|
tag string
|
||||||
|
listener *OutboundListener
|
||||||
|
access sync.RWMutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch implements outbound.Handler.
|
||||||
|
func (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) {
|
||||||
|
co.access.RLock()
|
||||||
|
|
||||||
|
if co.closed {
|
||||||
|
common.Interrupt(link.Reader)
|
||||||
|
common.Interrupt(link.Writer)
|
||||||
|
co.access.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSignal := done.New()
|
||||||
|
c := cnc.NewConnection(cnc.ConnectionInputMulti(link.Writer), cnc.ConnectionOutputMulti(link.Reader), cnc.ConnectionOnClose(closeSignal))
|
||||||
|
co.listener.add(c)
|
||||||
|
co.access.RUnlock()
|
||||||
|
<-closeSignal.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag implements outbound.Handler.
|
||||||
|
func (co *Outbound) Tag() string {
|
||||||
|
return co.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.
|
||||||
|
func (co *Outbound) Start() error {
|
||||||
|
co.access.Lock()
|
||||||
|
co.closed = false
|
||||||
|
co.access.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.
|
||||||
|
func (co *Outbound) Close() error {
|
||||||
|
co.access.Lock()
|
||||||
|
defer co.access.Unlock()
|
||||||
|
|
||||||
|
co.closed = true
|
||||||
|
return co.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SenderSettings implements outbound.Handler.
|
||||||
|
func (co *Outbound) SenderSettings() *serial.TypedMessage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxySettings implements outbound.Handler.
|
||||||
|
func (co *Outbound) ProxySettings() *serial.TypedMessage {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
12
subproject/Xray-core-main/app/observatory/burst/burst.go
Normal file
12
subproject/Xray-core-main/app/observatory/burst/burst.go
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package burst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rttFailed = time.Duration(math.MaxInt64 - iota)
|
||||||
|
rttUntested
|
||||||
|
rttUnqualified
|
||||||
|
)
|
||||||
117
subproject/Xray-core-main/app/observatory/burst/burstobserver.go
Normal file
117
subproject/Xray-core-main/app/observatory/burst/burstobserver.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package burst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/observatory"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/extension"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Observer struct {
|
||||||
|
config *Config
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
statusLock sync.Mutex
|
||||||
|
hp *HealthPing
|
||||||
|
|
||||||
|
finished *done.Instance
|
||||||
|
|
||||||
|
ohm outbound.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) {
|
||||||
|
return &observatory.ObservationResult{Status: o.createResult()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Check(tag []string) {
|
||||||
|
o.hp.Check(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) createResult() []*observatory.OutboundStatus {
|
||||||
|
var result []*observatory.OutboundStatus
|
||||||
|
o.hp.access.Lock()
|
||||||
|
defer o.hp.access.Unlock()
|
||||||
|
for name, value := range o.hp.Results {
|
||||||
|
status := observatory.OutboundStatus{
|
||||||
|
Alive: value.getStatistics().All != value.getStatistics().Fail,
|
||||||
|
Delay: value.getStatistics().Average.Milliseconds(),
|
||||||
|
LastErrorReason: "",
|
||||||
|
OutboundTag: name,
|
||||||
|
LastSeenTime: 0,
|
||||||
|
LastTryTime: 0,
|
||||||
|
HealthPing: &observatory.HealthPingMeasurementResult{
|
||||||
|
All: int64(value.getStatistics().All),
|
||||||
|
Fail: int64(value.getStatistics().Fail),
|
||||||
|
Deviation: int64(value.getStatistics().Deviation),
|
||||||
|
Average: int64(value.getStatistics().Average),
|
||||||
|
Max: int64(value.getStatistics().Max),
|
||||||
|
Min: int64(value.getStatistics().Min),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = append(result, &status)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Type() interface{} {
|
||||||
|
return extension.ObservatoryType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Start() error {
|
||||||
|
if o.config != nil && len(o.config.SubjectSelector) != 0 {
|
||||||
|
o.finished = done.New()
|
||||||
|
o.hp.StartScheduler(func() ([]string, error) {
|
||||||
|
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||||
|
if !ok {
|
||||||
|
|
||||||
|
return nil, errors.New("outbound.Manager is not a HandlerSelector")
|
||||||
|
}
|
||||||
|
|
||||||
|
outbounds := hs.Select(o.config.SubjectSelector)
|
||||||
|
return outbounds, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Close() error {
|
||||||
|
if o.finished != nil {
|
||||||
|
o.hp.StopScheduler()
|
||||||
|
return o.finished.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, config *Config) (*Observer, error) {
|
||||||
|
var outboundManager outbound.Manager
|
||||||
|
var dispatcher routing.Dispatcher
|
||||||
|
err := core.RequireFeatures(ctx, func(om outbound.Manager, rd routing.Dispatcher) {
|
||||||
|
outboundManager = om
|
||||||
|
dispatcher = rd
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Cannot get depended features").Base(err)
|
||||||
|
}
|
||||||
|
hp := NewHealthPing(ctx, dispatcher, config.PingConfig)
|
||||||
|
return &Observer{
|
||||||
|
config: config,
|
||||||
|
ctx: ctx,
|
||||||
|
ohm: outboundManager,
|
||||||
|
hp: hp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
236
subproject/Xray-core-main/app/observatory/burst/config.pb.go
Normal file
236
subproject/Xray-core-main/app/observatory/burst/config.pb.go
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/observatory/burst/config.proto
|
||||||
|
|
||||||
|
package burst
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// @Document The selectors for outbound under observation
|
||||||
|
SubjectSelector []string `protobuf:"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3" json:"subject_selector,omitempty"`
|
||||||
|
PingConfig *HealthPingConfig `protobuf:"bytes,3,opt,name=ping_config,json=pingConfig,proto3" json:"ping_config,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_observatory_burst_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_burst_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_burst_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetSubjectSelector() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SubjectSelector
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetPingConfig() *HealthPingConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.PingConfig
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthPingConfig struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// destination url, need 204 for success return
|
||||||
|
// default https://connectivitycheck.gstatic.com/generate_204
|
||||||
|
Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"`
|
||||||
|
// connectivity check url
|
||||||
|
Connectivity string `protobuf:"bytes,2,opt,name=connectivity,proto3" json:"connectivity,omitempty"`
|
||||||
|
// health check interval, int64 values of time.Duration
|
||||||
|
Interval int64 `protobuf:"varint,3,opt,name=interval,proto3" json:"interval,omitempty"`
|
||||||
|
// sampling count is the amount of recent ping results which are kept for calculation
|
||||||
|
SamplingCount int32 `protobuf:"varint,4,opt,name=samplingCount,proto3" json:"samplingCount,omitempty"`
|
||||||
|
// ping timeout, int64 values of time.Duration
|
||||||
|
Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
|
||||||
|
// http method to make request
|
||||||
|
HttpMethod string `protobuf:"bytes,6,opt,name=httpMethod,proto3" json:"httpMethod,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) Reset() {
|
||||||
|
*x = HealthPingConfig{}
|
||||||
|
mi := &file_app_observatory_burst_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HealthPingConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_burst_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use HealthPingConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*HealthPingConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_burst_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) GetDestination() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Destination
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) GetConnectivity() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Connectivity
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) GetInterval() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Interval
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) GetSamplingCount() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.SamplingCount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) GetTimeout() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Timeout
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingConfig) GetHttpMethod() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.HttpMethod
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_observatory_burst_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_observatory_burst_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\"app/observatory/burst/config.proto\x12\x1fxray.core.app.observatory.burst\"\x87\x01\n" +
|
||||||
|
"\x06Config\x12)\n" +
|
||||||
|
"\x10subject_selector\x18\x02 \x03(\tR\x0fsubjectSelector\x12R\n" +
|
||||||
|
"\vping_config\x18\x03 \x01(\v21.xray.core.app.observatory.burst.HealthPingConfigR\n" +
|
||||||
|
"pingConfig\"\xd4\x01\n" +
|
||||||
|
"\x10HealthPingConfig\x12 \n" +
|
||||||
|
"\vdestination\x18\x01 \x01(\tR\vdestination\x12\"\n" +
|
||||||
|
"\fconnectivity\x18\x02 \x01(\tR\fconnectivity\x12\x1a\n" +
|
||||||
|
"\binterval\x18\x03 \x01(\x03R\binterval\x12$\n" +
|
||||||
|
"\rsamplingCount\x18\x04 \x01(\x05R\rsamplingCount\x12\x18\n" +
|
||||||
|
"\atimeout\x18\x05 \x01(\x03R\atimeout\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"httpMethod\x18\x06 \x01(\tR\n" +
|
||||||
|
"httpMethodBp\n" +
|
||||||
|
"\x1ecom.xray.app.observatory.burstP\x01Z/github.com/xtls/xray-core/app/observatory/burst\xaa\x02\x1aXray.App.Observatory.Burstb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_observatory_burst_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_observatory_burst_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_observatory_burst_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_observatory_burst_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_observatory_burst_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_observatory_burst_config_proto_rawDesc), len(file_app_observatory_burst_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_observatory_burst_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_observatory_burst_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_app_observatory_burst_config_proto_goTypes = []any{
|
||||||
|
(*Config)(nil), // 0: xray.core.app.observatory.burst.Config
|
||||||
|
(*HealthPingConfig)(nil), // 1: xray.core.app.observatory.burst.HealthPingConfig
|
||||||
|
}
|
||||||
|
var file_app_observatory_burst_config_proto_depIdxs = []int32{
|
||||||
|
1, // 0: xray.core.app.observatory.burst.Config.ping_config:type_name -> xray.core.app.observatory.burst.HealthPingConfig
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_observatory_burst_config_proto_init() }
|
||||||
|
func file_app_observatory_burst_config_proto_init() {
|
||||||
|
if File_app_observatory_burst_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_observatory_burst_config_proto_rawDesc), len(file_app_observatory_burst_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_observatory_burst_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_observatory_burst_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_observatory_burst_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_observatory_burst_config_proto = out.File
|
||||||
|
file_app_observatory_burst_config_proto_goTypes = nil
|
||||||
|
file_app_observatory_burst_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
32
subproject/Xray-core-main/app/observatory/burst/config.proto
Normal file
32
subproject/Xray-core-main/app/observatory/burst/config.proto
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.core.app.observatory.burst;
|
||||||
|
option csharp_namespace = "Xray.App.Observatory.Burst";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/observatory/burst";
|
||||||
|
option java_package = "com.xray.app.observatory.burst";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
/* @Document The selectors for outbound under observation
|
||||||
|
*/
|
||||||
|
repeated string subject_selector = 2;
|
||||||
|
|
||||||
|
HealthPingConfig ping_config = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HealthPingConfig {
|
||||||
|
// destination url, need 204 for success return
|
||||||
|
// default https://connectivitycheck.gstatic.com/generate_204
|
||||||
|
string destination = 1;
|
||||||
|
// connectivity check url
|
||||||
|
string connectivity = 2;
|
||||||
|
// health check interval, int64 values of time.Duration
|
||||||
|
int64 interval = 3;
|
||||||
|
// sampling count is the amount of recent ping results which are kept for calculation
|
||||||
|
int32 samplingCount = 4;
|
||||||
|
// ping timeout, int64 values of time.Duration
|
||||||
|
int64 timeout = 5;
|
||||||
|
// http method to make request
|
||||||
|
string httpMethod = 6;
|
||||||
|
|
||||||
|
}
|
||||||
268
subproject/Xray-core-main/app/observatory/burst/healthping.go
Normal file
268
subproject/Xray-core-main/app/observatory/burst/healthping.go
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
package burst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/dice"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HealthPingSettings holds settings for health Checker
|
||||||
|
type HealthPingSettings struct {
|
||||||
|
Destination string `json:"destination"`
|
||||||
|
Connectivity string `json:"connectivity"`
|
||||||
|
Interval time.Duration `json:"interval"`
|
||||||
|
SamplingCount int `json:"sampling"`
|
||||||
|
Timeout time.Duration `json:"timeout"`
|
||||||
|
HttpMethod string `json:"httpMethod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthPing is the health checker for balancers
|
||||||
|
type HealthPing struct {
|
||||||
|
ctx context.Context
|
||||||
|
dispatcher routing.Dispatcher
|
||||||
|
access sync.Mutex
|
||||||
|
ticker *time.Ticker
|
||||||
|
tickerClose chan struct{}
|
||||||
|
|
||||||
|
Settings *HealthPingSettings
|
||||||
|
Results map[string]*HealthPingRTTS
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHealthPing creates a new HealthPing with settings
|
||||||
|
func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *HealthPingConfig) *HealthPing {
|
||||||
|
settings := &HealthPingSettings{}
|
||||||
|
if config != nil {
|
||||||
|
|
||||||
|
var httpMethod string
|
||||||
|
if config.HttpMethod == "" {
|
||||||
|
httpMethod = "HEAD"
|
||||||
|
} else {
|
||||||
|
httpMethod = strings.TrimSpace(config.HttpMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = &HealthPingSettings{
|
||||||
|
Connectivity: strings.TrimSpace(config.Connectivity),
|
||||||
|
Destination: strings.TrimSpace(config.Destination),
|
||||||
|
Interval: time.Duration(config.Interval),
|
||||||
|
SamplingCount: int(config.SamplingCount),
|
||||||
|
Timeout: time.Duration(config.Timeout),
|
||||||
|
HttpMethod: httpMethod,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if settings.Destination == "" {
|
||||||
|
// Destination URL, need 204 for success return default to chromium
|
||||||
|
// https://github.com/chromium/chromium/blob/main/components/safety_check/url_constants.cc#L10
|
||||||
|
// https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/safety_check/url_constants.cc#10
|
||||||
|
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
|
||||||
|
}
|
||||||
|
if settings.Interval == 0 {
|
||||||
|
settings.Interval = time.Duration(1) * time.Minute
|
||||||
|
} else if settings.Interval < 10 {
|
||||||
|
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
|
||||||
|
settings.Interval = time.Duration(10) * time.Second
|
||||||
|
}
|
||||||
|
if settings.SamplingCount <= 0 {
|
||||||
|
settings.SamplingCount = 10
|
||||||
|
}
|
||||||
|
if settings.Timeout <= 0 {
|
||||||
|
// results are saved after all health pings finish,
|
||||||
|
// a larger timeout could possibly makes checks run longer
|
||||||
|
settings.Timeout = time.Duration(5) * time.Second
|
||||||
|
}
|
||||||
|
return &HealthPing{
|
||||||
|
ctx: ctx,
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
Settings: settings,
|
||||||
|
Results: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartScheduler implements the HealthChecker
|
||||||
|
func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||||
|
if h.ticker != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
tickerClose := make(chan struct{})
|
||||||
|
h.ticker = ticker
|
||||||
|
h.tickerClose = tickerClose
|
||||||
|
go func() {
|
||||||
|
tags, err := selector()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogWarning(h.ctx, "error select outbounds for initial health check: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Check(tags)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
go func() {
|
||||||
|
tags, err := selector()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.doCheck(tags, interval, h.Settings.SamplingCount)
|
||||||
|
h.Cleanup(tags)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
continue
|
||||||
|
case <-tickerClose:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopScheduler implements the HealthChecker
|
||||||
|
func (h *HealthPing) StopScheduler() {
|
||||||
|
if h.ticker == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ticker.Stop()
|
||||||
|
h.ticker = nil
|
||||||
|
close(h.tickerClose)
|
||||||
|
h.tickerClose = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check implements the HealthChecker
|
||||||
|
func (h *HealthPing) Check(tags []string) error {
|
||||||
|
if len(tags) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
|
||||||
|
h.doCheck(tags, 0, 1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rtt struct {
|
||||||
|
handler string
|
||||||
|
value time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
|
||||||
|
// sure all tags are valid for current balancer
|
||||||
|
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
|
||||||
|
count := len(tags) * rounds
|
||||||
|
if count == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch := make(chan *rtt, count)
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
handler := tag
|
||||||
|
client := newPingClient(
|
||||||
|
h.ctx,
|
||||||
|
h.dispatcher,
|
||||||
|
h.Settings.Destination,
|
||||||
|
h.Settings.Timeout,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
|
for i := 0; i < rounds; i++ {
|
||||||
|
delay := time.Duration(0)
|
||||||
|
if duration > 0 {
|
||||||
|
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
||||||
|
}
|
||||||
|
time.AfterFunc(delay, func() {
|
||||||
|
errors.LogDebug(h.ctx, "checking ", handler)
|
||||||
|
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
||||||
|
if err == nil {
|
||||||
|
ch <- &rtt{
|
||||||
|
handler: handler,
|
||||||
|
value: delay,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !h.checkConnectivity() {
|
||||||
|
errors.LogWarning(h.ctx, "network is down")
|
||||||
|
ch <- &rtt{
|
||||||
|
handler: handler,
|
||||||
|
value: 0,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errors.LogWarning(h.ctx, fmt.Sprintf(
|
||||||
|
"error ping %s with %s: %s",
|
||||||
|
h.Settings.Destination,
|
||||||
|
handler,
|
||||||
|
err,
|
||||||
|
))
|
||||||
|
ch <- &rtt{
|
||||||
|
handler: handler,
|
||||||
|
value: rttFailed,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
rtt := <-ch
|
||||||
|
if rtt.value > 0 {
|
||||||
|
// should not put results when network is down
|
||||||
|
h.PutResult(rtt.handler, rtt.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutResult put a ping rtt to results
|
||||||
|
func (h *HealthPing) PutResult(tag string, rtt time.Duration) {
|
||||||
|
h.access.Lock()
|
||||||
|
defer h.access.Unlock()
|
||||||
|
if h.Results == nil {
|
||||||
|
h.Results = make(map[string]*HealthPingRTTS)
|
||||||
|
}
|
||||||
|
r, ok := h.Results[tag]
|
||||||
|
if !ok {
|
||||||
|
// validity is 2 times to sampling period, since the check are
|
||||||
|
// distributed in the time line randomly, in extreme cases,
|
||||||
|
// Previous checks are distributed on the left, and later ones
|
||||||
|
// on the right
|
||||||
|
validity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2
|
||||||
|
r = NewHealthPingResult(h.Settings.SamplingCount, validity)
|
||||||
|
h.Results[tag] = r
|
||||||
|
}
|
||||||
|
r.Put(rtt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup removes results of removed handlers,
|
||||||
|
// tags should be all valid tags of the Balancer now
|
||||||
|
func (h *HealthPing) Cleanup(tags []string) {
|
||||||
|
h.access.Lock()
|
||||||
|
defer h.access.Unlock()
|
||||||
|
for tag := range h.Results {
|
||||||
|
found := false
|
||||||
|
for _, v := range tags {
|
||||||
|
if tag == v {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
delete(h.Results, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConnectivity checks the network connectivity, it returns
|
||||||
|
// true if network is good or "connectivity check url" not set
|
||||||
|
func (h *HealthPing) checkConnectivity() bool {
|
||||||
|
if h.Settings.Connectivity == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
tester := newDirectPingClient(
|
||||||
|
h.Settings.Connectivity,
|
||||||
|
h.Settings.Timeout,
|
||||||
|
)
|
||||||
|
if _, err := tester.MeasureDelay(h.Settings.HttpMethod); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
package burst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HealthPingStats is the statistics of HealthPingRTTS
|
||||||
|
type HealthPingStats struct {
|
||||||
|
All int
|
||||||
|
Fail int
|
||||||
|
Deviation time.Duration
|
||||||
|
Average time.Duration
|
||||||
|
Max time.Duration
|
||||||
|
Min time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthPingRTTS holds ping rtts for health Checker
|
||||||
|
type HealthPingRTTS struct {
|
||||||
|
idx int
|
||||||
|
cap int
|
||||||
|
validity time.Duration
|
||||||
|
rtts []*pingRTT
|
||||||
|
|
||||||
|
lastUpdateAt time.Time
|
||||||
|
stats *HealthPingStats
|
||||||
|
}
|
||||||
|
|
||||||
|
type pingRTT struct {
|
||||||
|
time time.Time
|
||||||
|
value time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHealthPingResult returns a *HealthPingResult with specified capacity
|
||||||
|
func NewHealthPingResult(cap int, validity time.Duration) *HealthPingRTTS {
|
||||||
|
return &HealthPingRTTS{cap: cap, validity: validity}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets statistics of the HealthPingRTTS
|
||||||
|
func (h *HealthPingRTTS) Get() *HealthPingStats {
|
||||||
|
return h.getStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWithCache get statistics and write cache for next call
|
||||||
|
// Make sure use Mutex.Lock() before calling it, RWMutex.RLock()
|
||||||
|
// is not an option since it writes cache
|
||||||
|
func (h *HealthPingRTTS) GetWithCache() *HealthPingStats {
|
||||||
|
lastPutAt := h.rtts[h.idx].time
|
||||||
|
now := time.Now()
|
||||||
|
if h.stats == nil || h.lastUpdateAt.Before(lastPutAt) || h.findOutdated(now) >= 0 {
|
||||||
|
h.stats = h.getStatistics()
|
||||||
|
h.lastUpdateAt = now
|
||||||
|
}
|
||||||
|
return h.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put puts a new rtt to the HealthPingResult
|
||||||
|
func (h *HealthPingRTTS) Put(d time.Duration) {
|
||||||
|
if h.rtts == nil {
|
||||||
|
h.rtts = make([]*pingRTT, h.cap)
|
||||||
|
for i := 0; i < h.cap; i++ {
|
||||||
|
h.rtts[i] = &pingRTT{}
|
||||||
|
}
|
||||||
|
h.idx = -1
|
||||||
|
}
|
||||||
|
h.idx = h.calcIndex(1)
|
||||||
|
now := time.Now()
|
||||||
|
h.rtts[h.idx].time = now
|
||||||
|
h.rtts[h.idx].value = d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthPingRTTS) calcIndex(step int) int {
|
||||||
|
idx := h.idx
|
||||||
|
idx += step
|
||||||
|
if idx >= h.cap {
|
||||||
|
idx %= h.cap
|
||||||
|
}
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
|
||||||
|
stats := &HealthPingStats{}
|
||||||
|
stats.Fail = 0
|
||||||
|
stats.Max = 0
|
||||||
|
stats.Min = rttFailed
|
||||||
|
sum := time.Duration(0)
|
||||||
|
cnt := 0
|
||||||
|
validRTTs := make([]time.Duration, 0)
|
||||||
|
for _, rtt := range h.rtts {
|
||||||
|
switch {
|
||||||
|
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
|
||||||
|
continue
|
||||||
|
case rtt.value == rttFailed:
|
||||||
|
stats.Fail++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
|
sum += rtt.value
|
||||||
|
validRTTs = append(validRTTs, rtt.value)
|
||||||
|
if stats.Max < rtt.value {
|
||||||
|
stats.Max = rtt.value
|
||||||
|
}
|
||||||
|
if stats.Min > rtt.value {
|
||||||
|
stats.Min = rtt.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.All = cnt + stats.Fail
|
||||||
|
if cnt == 0 {
|
||||||
|
stats.Min = 0
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
stats.Average = time.Duration(int(sum) / cnt)
|
||||||
|
var std float64
|
||||||
|
if cnt < 2 {
|
||||||
|
// no enough data for standard deviation, we assume it's half of the average rtt
|
||||||
|
// if we don't do this, standard deviation of 1 round tested nodes is 0, will always
|
||||||
|
// selected before 2 or more rounds tested nodes
|
||||||
|
std = float64(stats.Average / 2)
|
||||||
|
} else {
|
||||||
|
variance := float64(0)
|
||||||
|
for _, rtt := range validRTTs {
|
||||||
|
variance += math.Pow(float64(rtt-stats.Average), 2)
|
||||||
|
}
|
||||||
|
std = math.Sqrt(variance / float64(cnt))
|
||||||
|
}
|
||||||
|
stats.Deviation = time.Duration(std)
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthPingRTTS) findOutdated(now time.Time) int {
|
||||||
|
for i := h.cap - 1; i < 2*h.cap; i++ {
|
||||||
|
// from oldest to latest
|
||||||
|
idx := h.calcIndex(i)
|
||||||
|
validity := h.rtts[idx].time.Add(h.validity)
|
||||||
|
if h.lastUpdateAt.After(validity) {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
if validity.Before(now) {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package burst_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
reflect "reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/observatory/burst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHealthPingResults(t *testing.T) {
|
||||||
|
rtts := []int64{60, 140, 60, 140, 60, 60, 140, 60, 140}
|
||||||
|
hr := burst.NewHealthPingResult(4, time.Hour)
|
||||||
|
for _, rtt := range rtts {
|
||||||
|
hr.Put(time.Duration(rtt))
|
||||||
|
}
|
||||||
|
rttFailed := time.Duration(math.MaxInt64)
|
||||||
|
expected := &burst.HealthPingStats{
|
||||||
|
All: 4,
|
||||||
|
Fail: 0,
|
||||||
|
Deviation: 40,
|
||||||
|
Average: 100,
|
||||||
|
Max: 140,
|
||||||
|
Min: 60,
|
||||||
|
}
|
||||||
|
actual := hr.Get()
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("expected: %v, actual: %v", expected, actual)
|
||||||
|
}
|
||||||
|
hr.Put(rttFailed)
|
||||||
|
hr.Put(rttFailed)
|
||||||
|
expected.Fail = 2
|
||||||
|
actual = hr.Get()
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("failed half-failures test, expected: %v, actual: %v", expected, actual)
|
||||||
|
}
|
||||||
|
hr.Put(rttFailed)
|
||||||
|
hr.Put(rttFailed)
|
||||||
|
expected = &burst.HealthPingStats{
|
||||||
|
All: 4,
|
||||||
|
Fail: 4,
|
||||||
|
Deviation: 0,
|
||||||
|
Average: 0,
|
||||||
|
Max: 0,
|
||||||
|
Min: 0,
|
||||||
|
}
|
||||||
|
actual = hr.Get()
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("failed all-failures test, expected: %v, actual: %v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthPingResultsIgnoreOutdated(t *testing.T) {
|
||||||
|
rtts := []int64{60, 140, 60, 140}
|
||||||
|
hr := burst.NewHealthPingResult(4, time.Duration(10)*time.Millisecond)
|
||||||
|
for i, rtt := range rtts {
|
||||||
|
if i == 2 {
|
||||||
|
// wait for previous 2 outdated
|
||||||
|
time.Sleep(time.Duration(10) * time.Millisecond)
|
||||||
|
}
|
||||||
|
hr.Put(time.Duration(rtt))
|
||||||
|
}
|
||||||
|
hr.Get()
|
||||||
|
expected := &burst.HealthPingStats{
|
||||||
|
All: 2,
|
||||||
|
Fail: 0,
|
||||||
|
Deviation: 40,
|
||||||
|
Average: 100,
|
||||||
|
Max: 140,
|
||||||
|
Min: 60,
|
||||||
|
}
|
||||||
|
actual := hr.Get()
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("failed 'half-outdated' test, expected: %v, actual: %v", expected, actual)
|
||||||
|
}
|
||||||
|
// wait for all outdated
|
||||||
|
time.Sleep(time.Duration(10) * time.Millisecond)
|
||||||
|
expected = &burst.HealthPingStats{
|
||||||
|
All: 0,
|
||||||
|
Fail: 0,
|
||||||
|
Deviation: 0,
|
||||||
|
Average: 0,
|
||||||
|
Max: 0,
|
||||||
|
Min: 0,
|
||||||
|
}
|
||||||
|
actual = hr.Get()
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("failed 'outdated / not-tested' test, expected: %v, actual: %v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
hr.Put(time.Duration(60))
|
||||||
|
expected = &burst.HealthPingStats{
|
||||||
|
All: 1,
|
||||||
|
Fail: 0,
|
||||||
|
// 1 sample, std=0.5rtt
|
||||||
|
Deviation: 30,
|
||||||
|
Average: 60,
|
||||||
|
Max: 60,
|
||||||
|
Min: 60,
|
||||||
|
}
|
||||||
|
actual = hr.Get()
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("expected: %v, actual: %v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
subproject/Xray-core-main/app/observatory/burst/ping.go
Normal file
81
subproject/Xray-core-main/app/observatory/burst/ping.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package burst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pingClient struct {
|
||||||
|
destination string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPingClient(ctx context.Context, dispatcher routing.Dispatcher, destination string, timeout time.Duration, handler string) *pingClient {
|
||||||
|
return &pingClient{
|
||||||
|
destination: destination,
|
||||||
|
httpClient: newHTTPClient(ctx, dispatcher, handler, timeout),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDirectPingClient(destination string, timeout time.Duration) *pingClient {
|
||||||
|
return &pingClient{
|
||||||
|
destination: destination,
|
||||||
|
httpClient: &http.Client{Timeout: timeout},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHTTPClient(ctxv context.Context, dispatcher routing.Dispatcher, handler string, timeout time.Duration) *http.Client {
|
||||||
|
tr := &http.Transport{
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
dest, err := net.ParseDestination(network + ":" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tagged.Dialer(ctxv, dispatcher, dest, handler)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
Timeout: timeout,
|
||||||
|
// don't follow redirect
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureDelay returns the delay time of the request to dest
|
||||||
|
func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {
|
||||||
|
if s.httpClient == nil {
|
||||||
|
panic("pingClient not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(httpMethod, s.destination, nil)
|
||||||
|
if err != nil {
|
||||||
|
return rttFailed, err
|
||||||
|
}
|
||||||
|
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return rttFailed, err
|
||||||
|
}
|
||||||
|
if httpMethod == http.MethodGet {
|
||||||
|
_, err = io.Copy(io.Discard, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return rttFailed, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
return time.Since(start), nil
|
||||||
|
}
|
||||||
47
subproject/Xray-core-main/app/observatory/command/command.go
Normal file
47
subproject/Xray-core-main/app/observatory/command/command.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/observatory"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
core "github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/extension"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
UnimplementedObservatoryServiceServer
|
||||||
|
v *core.Instance
|
||||||
|
|
||||||
|
observatory extension.Observatory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetOutboundStatus(ctx context.Context, request *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) {
|
||||||
|
resp, err := s.observatory.GetObservation(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
retdata := resp.(*observatory.ObservationResult)
|
||||||
|
return &GetOutboundStatusResponse{
|
||||||
|
Status: retdata,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Register(server *grpc.Server) {
|
||||||
|
RegisterObservatoryServiceServer(server, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
s := core.MustFromContext(ctx)
|
||||||
|
sv := &service{v: s}
|
||||||
|
err := s.RequireFeatures(func(Observatory extension.Observatory) {
|
||||||
|
sv.observatory = Observatory
|
||||||
|
}, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sv, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
206
subproject/Xray-core-main/app/observatory/command/command.pb.go
Normal file
206
subproject/Xray-core-main/app/observatory/command/command.pb.go
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/observatory/command/command.proto
|
||||||
|
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
observatory "github.com/xtls/xray-core/app/observatory"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetOutboundStatusRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusRequest) Reset() {
|
||||||
|
*x = GetOutboundStatusRequest{}
|
||||||
|
mi := &file_app_observatory_command_command_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetOutboundStatusRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_command_command_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetOutboundStatusRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetOutboundStatusRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_command_command_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetOutboundStatusResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Status *observatory.ObservationResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusResponse) Reset() {
|
||||||
|
*x = GetOutboundStatusResponse{}
|
||||||
|
mi := &file_app_observatory_command_command_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetOutboundStatusResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_command_command_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetOutboundStatusResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetOutboundStatusResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_command_command_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetOutboundStatusResponse) GetStatus() *observatory.ObservationResult {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_observatory_command_command_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_command_command_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_command_command_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_observatory_command_command_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_observatory_command_command_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"%app/observatory/command/command.proto\x12!xray.core.app.observatory.command\x1a\x1capp/observatory/config.proto\"\x1a\n" +
|
||||||
|
"\x18GetOutboundStatusRequest\"a\n" +
|
||||||
|
"\x19GetOutboundStatusResponse\x12D\n" +
|
||||||
|
"\x06status\x18\x01 \x01(\v2,.xray.core.app.observatory.ObservationResultR\x06status\"\b\n" +
|
||||||
|
"\x06Config2\xa7\x01\n" +
|
||||||
|
"\x12ObservatoryService\x12\x90\x01\n" +
|
||||||
|
"\x11GetOutboundStatus\x12;.xray.core.app.observatory.command.GetOutboundStatusRequest\x1a<.xray.core.app.observatory.command.GetOutboundStatusResponse\"\x00B\x80\x01\n" +
|
||||||
|
"%com.xray.core.app.observatory.commandP\x01Z1github.com/xtls/xray-core/app/observatory/command\xaa\x02!Xray.Core.App.Observatory.Commandb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_observatory_command_command_proto_rawDescOnce sync.Once
|
||||||
|
file_app_observatory_command_command_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_observatory_command_command_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_observatory_command_command_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_observatory_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_observatory_command_command_proto_rawDesc), len(file_app_observatory_command_command_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_observatory_command_command_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_observatory_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||||
|
var file_app_observatory_command_command_proto_goTypes = []any{
|
||||||
|
(*GetOutboundStatusRequest)(nil), // 0: xray.core.app.observatory.command.GetOutboundStatusRequest
|
||||||
|
(*GetOutboundStatusResponse)(nil), // 1: xray.core.app.observatory.command.GetOutboundStatusResponse
|
||||||
|
(*Config)(nil), // 2: xray.core.app.observatory.command.Config
|
||||||
|
(*observatory.ObservationResult)(nil), // 3: xray.core.app.observatory.ObservationResult
|
||||||
|
}
|
||||||
|
var file_app_observatory_command_command_proto_depIdxs = []int32{
|
||||||
|
3, // 0: xray.core.app.observatory.command.GetOutboundStatusResponse.status:type_name -> xray.core.app.observatory.ObservationResult
|
||||||
|
0, // 1: xray.core.app.observatory.command.ObservatoryService.GetOutboundStatus:input_type -> xray.core.app.observatory.command.GetOutboundStatusRequest
|
||||||
|
1, // 2: xray.core.app.observatory.command.ObservatoryService.GetOutboundStatus:output_type -> xray.core.app.observatory.command.GetOutboundStatusResponse
|
||||||
|
2, // [2:3] is the sub-list for method output_type
|
||||||
|
1, // [1:2] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_observatory_command_command_proto_init() }
|
||||||
|
func file_app_observatory_command_command_proto_init() {
|
||||||
|
if File_app_observatory_command_command_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_observatory_command_command_proto_rawDesc), len(file_app_observatory_command_command_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 3,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_observatory_command_command_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_observatory_command_command_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_observatory_command_command_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_observatory_command_command_proto = out.File
|
||||||
|
file_app_observatory_command_command_proto_goTypes = nil
|
||||||
|
file_app_observatory_command_command_proto_depIdxs = nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.core.app.observatory.command;
|
||||||
|
option csharp_namespace = "Xray.Core.App.Observatory.Command";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/observatory/command";
|
||||||
|
option java_package = "com.xray.core.app.observatory.command";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "app/observatory/config.proto";
|
||||||
|
|
||||||
|
message GetOutboundStatusRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetOutboundStatusResponse {
|
||||||
|
xray.core.app.observatory.ObservationResult status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service ObservatoryService {
|
||||||
|
rpc GetOutboundStatus(GetOutboundStatusRequest)
|
||||||
|
returns (GetOutboundStatusResponse) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message Config {}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
|
// - protoc v6.33.5
|
||||||
|
// source: app/observatory/command/command.proto
|
||||||
|
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
ObservatoryService_GetOutboundStatus_FullMethodName = "/xray.core.app.observatory.command.ObservatoryService/GetOutboundStatus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObservatoryServiceClient is the client API for ObservatoryService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type ObservatoryServiceClient interface {
|
||||||
|
GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type observatoryServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewObservatoryServiceClient(cc grpc.ClientConnInterface) ObservatoryServiceClient {
|
||||||
|
return &observatoryServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *observatoryServiceClient) GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(GetOutboundStatusResponse)
|
||||||
|
err := c.cc.Invoke(ctx, ObservatoryService_GetOutboundStatus_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObservatoryServiceServer is the server API for ObservatoryService service.
|
||||||
|
// All implementations must embed UnimplementedObservatoryServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type ObservatoryServiceServer interface {
|
||||||
|
GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error)
|
||||||
|
mustEmbedUnimplementedObservatoryServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedObservatoryServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedObservatoryServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedObservatoryServiceServer) GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetOutboundStatus not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedObservatoryServiceServer) mustEmbedUnimplementedObservatoryServiceServer() {}
|
||||||
|
func (UnimplementedObservatoryServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeObservatoryServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to ObservatoryServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeObservatoryServiceServer interface {
|
||||||
|
mustEmbedUnimplementedObservatoryServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterObservatoryServiceServer(s grpc.ServiceRegistrar, srv ObservatoryServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedObservatoryServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&ObservatoryService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ObservatoryService_GetOutboundStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetOutboundStatusRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ObservatoryService_GetOutboundStatus_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, req.(*GetOutboundStatusRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObservatoryService_ServiceDesc is the grpc.ServiceDesc for ObservatoryService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var ObservatoryService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "xray.core.app.observatory.command.ObservatoryService",
|
||||||
|
HandlerType: (*ObservatoryServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "GetOutboundStatus",
|
||||||
|
Handler: _ObservatoryService_GetOutboundStatus_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "app/observatory/command/command.proto",
|
||||||
|
}
|
||||||
528
subproject/Xray-core-main/app/observatory/config.pb.go
Normal file
528
subproject/Xray-core-main/app/observatory/config.pb.go
Normal file
|
|
@ -0,0 +1,528 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/observatory/config.proto
|
||||||
|
|
||||||
|
package observatory
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ObservationResult struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Status []*OutboundStatus `protobuf:"bytes,1,rep,name=status,proto3" json:"status,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObservationResult) Reset() {
|
||||||
|
*x = ObservationResult{}
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObservationResult) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ObservationResult) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ObservationResult) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ObservationResult.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ObservationResult) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObservationResult) GetStatus() []*OutboundStatus {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthPingMeasurementResult struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
All int64 `protobuf:"varint,1,opt,name=all,proto3" json:"all,omitempty"`
|
||||||
|
Fail int64 `protobuf:"varint,2,opt,name=fail,proto3" json:"fail,omitempty"`
|
||||||
|
Deviation int64 `protobuf:"varint,3,opt,name=deviation,proto3" json:"deviation,omitempty"`
|
||||||
|
Average int64 `protobuf:"varint,4,opt,name=average,proto3" json:"average,omitempty"`
|
||||||
|
Max int64 `protobuf:"varint,5,opt,name=max,proto3" json:"max,omitempty"`
|
||||||
|
Min int64 `protobuf:"varint,6,opt,name=min,proto3" json:"min,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) Reset() {
|
||||||
|
*x = HealthPingMeasurementResult{}
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HealthPingMeasurementResult) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use HealthPingMeasurementResult.ProtoReflect.Descriptor instead.
|
||||||
|
func (*HealthPingMeasurementResult) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) GetAll() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.All
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) GetFail() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Fail
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) GetDeviation() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Deviation
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) GetAverage() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Average
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) GetMax() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Max
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HealthPingMeasurementResult) GetMin() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Min
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundStatus struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// @Document Whether this outbound is usable
|
||||||
|
// @Restriction ReadOnlyForUser
|
||||||
|
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
|
||||||
|
// @Document The time for probe request to finish.
|
||||||
|
// @Type time.ms
|
||||||
|
// @Restriction ReadOnlyForUser
|
||||||
|
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
|
||||||
|
// @Document The last error caused this outbound failed to relay probe request
|
||||||
|
// @Restriction NotMachineReadable
|
||||||
|
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
|
||||||
|
// @Document The outbound tag for this Server
|
||||||
|
// @Type id.outboundTag
|
||||||
|
OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"`
|
||||||
|
// @Document The time this outbound is known to be alive
|
||||||
|
// @Type id.outboundTag
|
||||||
|
LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"`
|
||||||
|
// @Document The time this outbound is tried
|
||||||
|
// @Type id.outboundTag
|
||||||
|
LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"`
|
||||||
|
HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) Reset() {
|
||||||
|
*x = OutboundStatus{}
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*OutboundStatus) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use OutboundStatus.ProtoReflect.Descriptor instead.
|
||||||
|
func (*OutboundStatus) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_config_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetAlive() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Alive
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetDelay() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Delay
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetLastErrorReason() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastErrorReason
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetOutboundTag() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.OutboundTag
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetLastSeenTime() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastSeenTime
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetLastTryTime() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastTryTime
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult {
|
||||||
|
if x != nil {
|
||||||
|
return x.HealthPing
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProbeResult struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// @Document Whether this outbound is usable
|
||||||
|
// @Restriction ReadOnlyForUser
|
||||||
|
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
|
||||||
|
// @Document The time for probe request to finish.
|
||||||
|
// @Type time.ms
|
||||||
|
// @Restriction ReadOnlyForUser
|
||||||
|
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
|
||||||
|
// @Document The error caused this outbound failed to relay probe request
|
||||||
|
// @Restriction NotMachineReadable
|
||||||
|
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProbeResult) Reset() {
|
||||||
|
*x = ProbeResult{}
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProbeResult) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ProbeResult) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ProbeResult) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ProbeResult.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ProbeResult) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_config_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProbeResult) GetAlive() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Alive
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProbeResult) GetDelay() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Delay
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ProbeResult) GetLastErrorReason() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastErrorReason
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Intensity struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// @Document The time interval for a probe request in ms.
|
||||||
|
// @Type time.ms
|
||||||
|
ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Intensity) Reset() {
|
||||||
|
*x = Intensity{}
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Intensity) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Intensity) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Intensity) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Intensity.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Intensity) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_config_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Intensity) GetProbeInterval() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProbeInterval
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// @Document The selectors for outbound under observation
|
||||||
|
SubjectSelector []string `protobuf:"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3" json:"subject_selector,omitempty"`
|
||||||
|
ProbeUrl string `protobuf:"bytes,3,opt,name=probe_url,json=probeUrl,proto3" json:"probe_url,omitempty"`
|
||||||
|
ProbeInterval int64 `protobuf:"varint,4,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"`
|
||||||
|
EnableConcurrency bool `protobuf:"varint,5,opt,name=enable_concurrency,json=enableConcurrency,proto3" json:"enable_concurrency,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_observatory_config_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_observatory_config_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetSubjectSelector() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SubjectSelector
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetProbeUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProbeUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetProbeInterval() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProbeInterval
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEnableConcurrency() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.EnableConcurrency
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_observatory_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_observatory_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1capp/observatory/config.proto\x12\x19xray.core.app.observatory\"V\n" +
|
||||||
|
"\x11ObservationResult\x12A\n" +
|
||||||
|
"\x06status\x18\x01 \x03(\v2).xray.core.app.observatory.OutboundStatusR\x06status\"\x9f\x01\n" +
|
||||||
|
"\x1bHealthPingMeasurementResult\x12\x10\n" +
|
||||||
|
"\x03all\x18\x01 \x01(\x03R\x03all\x12\x12\n" +
|
||||||
|
"\x04fail\x18\x02 \x01(\x03R\x04fail\x12\x1c\n" +
|
||||||
|
"\tdeviation\x18\x03 \x01(\x03R\tdeviation\x12\x18\n" +
|
||||||
|
"\aaverage\x18\x04 \x01(\x03R\aaverage\x12\x10\n" +
|
||||||
|
"\x03max\x18\x05 \x01(\x03R\x03max\x12\x10\n" +
|
||||||
|
"\x03min\x18\x06 \x01(\x03R\x03min\"\xae\x02\n" +
|
||||||
|
"\x0eOutboundStatus\x12\x14\n" +
|
||||||
|
"\x05alive\x18\x01 \x01(\bR\x05alive\x12\x14\n" +
|
||||||
|
"\x05delay\x18\x02 \x01(\x03R\x05delay\x12*\n" +
|
||||||
|
"\x11last_error_reason\x18\x03 \x01(\tR\x0flastErrorReason\x12!\n" +
|
||||||
|
"\foutbound_tag\x18\x04 \x01(\tR\voutboundTag\x12$\n" +
|
||||||
|
"\x0elast_seen_time\x18\x05 \x01(\x03R\flastSeenTime\x12\"\n" +
|
||||||
|
"\rlast_try_time\x18\x06 \x01(\x03R\vlastTryTime\x12W\n" +
|
||||||
|
"\vhealth_ping\x18\a \x01(\v26.xray.core.app.observatory.HealthPingMeasurementResultR\n" +
|
||||||
|
"healthPing\"e\n" +
|
||||||
|
"\vProbeResult\x12\x14\n" +
|
||||||
|
"\x05alive\x18\x01 \x01(\bR\x05alive\x12\x14\n" +
|
||||||
|
"\x05delay\x18\x02 \x01(\x03R\x05delay\x12*\n" +
|
||||||
|
"\x11last_error_reason\x18\x03 \x01(\tR\x0flastErrorReason\"2\n" +
|
||||||
|
"\tIntensity\x12%\n" +
|
||||||
|
"\x0eprobe_interval\x18\x01 \x01(\rR\rprobeInterval\"\xa6\x01\n" +
|
||||||
|
"\x06Config\x12)\n" +
|
||||||
|
"\x10subject_selector\x18\x02 \x03(\tR\x0fsubjectSelector\x12\x1b\n" +
|
||||||
|
"\tprobe_url\x18\x03 \x01(\tR\bprobeUrl\x12%\n" +
|
||||||
|
"\x0eprobe_interval\x18\x04 \x01(\x03R\rprobeInterval\x12-\n" +
|
||||||
|
"\x12enable_concurrency\x18\x05 \x01(\bR\x11enableConcurrencyB^\n" +
|
||||||
|
"\x18com.xray.app.observatoryP\x01Z)github.com/xtls/xray-core/app/observatory\xaa\x02\x14Xray.App.Observatoryb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_observatory_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_observatory_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_observatory_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_observatory_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_observatory_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_observatory_config_proto_rawDesc), len(file_app_observatory_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_observatory_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_observatory_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||||
|
var file_app_observatory_config_proto_goTypes = []any{
|
||||||
|
(*ObservationResult)(nil), // 0: xray.core.app.observatory.ObservationResult
|
||||||
|
(*HealthPingMeasurementResult)(nil), // 1: xray.core.app.observatory.HealthPingMeasurementResult
|
||||||
|
(*OutboundStatus)(nil), // 2: xray.core.app.observatory.OutboundStatus
|
||||||
|
(*ProbeResult)(nil), // 3: xray.core.app.observatory.ProbeResult
|
||||||
|
(*Intensity)(nil), // 4: xray.core.app.observatory.Intensity
|
||||||
|
(*Config)(nil), // 5: xray.core.app.observatory.Config
|
||||||
|
}
|
||||||
|
var file_app_observatory_config_proto_depIdxs = []int32{
|
||||||
|
2, // 0: xray.core.app.observatory.ObservationResult.status:type_name -> xray.core.app.observatory.OutboundStatus
|
||||||
|
1, // 1: xray.core.app.observatory.OutboundStatus.health_ping:type_name -> xray.core.app.observatory.HealthPingMeasurementResult
|
||||||
|
2, // [2:2] is the sub-list for method output_type
|
||||||
|
2, // [2:2] is the sub-list for method input_type
|
||||||
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
|
0, // [0:2] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_observatory_config_proto_init() }
|
||||||
|
func file_app_observatory_config_proto_init() {
|
||||||
|
if File_app_observatory_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_observatory_config_proto_rawDesc), len(file_app_observatory_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 6,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_observatory_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_observatory_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_observatory_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_observatory_config_proto = out.File
|
||||||
|
file_app_observatory_config_proto_goTypes = nil
|
||||||
|
file_app_observatory_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
84
subproject/Xray-core-main/app/observatory/config.proto
Normal file
84
subproject/Xray-core-main/app/observatory/config.proto
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.core.app.observatory;
|
||||||
|
option csharp_namespace = "Xray.App.Observatory";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/observatory";
|
||||||
|
option java_package = "com.xray.app.observatory";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message ObservationResult {
|
||||||
|
repeated OutboundStatus status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HealthPingMeasurementResult {
|
||||||
|
int64 all = 1;
|
||||||
|
int64 fail = 2;
|
||||||
|
int64 deviation = 3;
|
||||||
|
int64 average = 4;
|
||||||
|
int64 max = 5;
|
||||||
|
int64 min = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OutboundStatus{
|
||||||
|
/* @Document Whether this outbound is usable
|
||||||
|
@Restriction ReadOnlyForUser
|
||||||
|
*/
|
||||||
|
bool alive = 1;
|
||||||
|
/* @Document The time for probe request to finish.
|
||||||
|
@Type time.ms
|
||||||
|
@Restriction ReadOnlyForUser
|
||||||
|
*/
|
||||||
|
int64 delay = 2;
|
||||||
|
/* @Document The last error caused this outbound failed to relay probe request
|
||||||
|
@Restriction NotMachineReadable
|
||||||
|
*/
|
||||||
|
string last_error_reason = 3;
|
||||||
|
/* @Document The outbound tag for this Server
|
||||||
|
@Type id.outboundTag
|
||||||
|
*/
|
||||||
|
string outbound_tag = 4;
|
||||||
|
/* @Document The time this outbound is known to be alive
|
||||||
|
@Type id.outboundTag
|
||||||
|
*/
|
||||||
|
int64 last_seen_time = 5;
|
||||||
|
/* @Document The time this outbound is tried
|
||||||
|
@Type id.outboundTag
|
||||||
|
*/
|
||||||
|
int64 last_try_time = 6;
|
||||||
|
|
||||||
|
HealthPingMeasurementResult health_ping = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProbeResult{
|
||||||
|
/* @Document Whether this outbound is usable
|
||||||
|
@Restriction ReadOnlyForUser
|
||||||
|
*/
|
||||||
|
bool alive = 1;
|
||||||
|
/* @Document The time for probe request to finish.
|
||||||
|
@Type time.ms
|
||||||
|
@Restriction ReadOnlyForUser
|
||||||
|
*/
|
||||||
|
int64 delay = 2;
|
||||||
|
/* @Document The error caused this outbound failed to relay probe request
|
||||||
|
@Restriction NotMachineReadable
|
||||||
|
*/
|
||||||
|
string last_error_reason = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Intensity{
|
||||||
|
/* @Document The time interval for a probe request in ms.
|
||||||
|
@Type time.ms
|
||||||
|
*/
|
||||||
|
uint32 probe_interval = 1;
|
||||||
|
}
|
||||||
|
message Config {
|
||||||
|
/* @Document The selectors for outbound under observation
|
||||||
|
*/
|
||||||
|
repeated string subject_selector = 2;
|
||||||
|
|
||||||
|
string probe_url = 3;
|
||||||
|
|
||||||
|
int64 probe_interval = 4;
|
||||||
|
|
||||||
|
bool enable_concurrency = 5;
|
||||||
|
}
|
||||||
26
subproject/Xray-core-main/app/observatory/explainErrors.go
Normal file
26
subproject/Xray-core-main/app/observatory/explainErrors.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package observatory
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
type errorCollector struct {
|
||||||
|
errors *errors.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorCollector) SubmitError(err error) {
|
||||||
|
if e.errors == nil {
|
||||||
|
e.errors = errors.New("underlying connection error").Base(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.errors = e.errors.Base(errors.New("underlying connection error").Base(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrorCollector() *errorCollector {
|
||||||
|
return &errorCollector{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorCollector) UnderlyingError() error {
|
||||||
|
if e.errors == nil {
|
||||||
|
return errors.New("failed to produce report")
|
||||||
|
}
|
||||||
|
return e.errors
|
||||||
|
}
|
||||||
1
subproject/Xray-core-main/app/observatory/observatory.go
Normal file
1
subproject/Xray-core-main/app/observatory/observatory.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package observatory
|
||||||
252
subproject/Xray-core-main/app/observatory/observer.go
Normal file
252
subproject/Xray-core-main/app/observatory/observer.go
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
package observatory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
v2net "github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/session"
|
||||||
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/extension"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Observer struct {
|
||||||
|
config *Config
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
statusLock sync.Mutex
|
||||||
|
status []*OutboundStatus
|
||||||
|
|
||||||
|
finished *done.Instance
|
||||||
|
|
||||||
|
ohm outbound.Manager
|
||||||
|
dispatcher routing.Dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) {
|
||||||
|
return &ObservationResult{Status: o.status}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Type() interface{} {
|
||||||
|
return extension.ObservatoryType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Start() error {
|
||||||
|
if o.config != nil && len(o.config.SubjectSelector) != 0 {
|
||||||
|
o.finished = done.New()
|
||||||
|
go o.background()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) Close() error {
|
||||||
|
if o.finished != nil {
|
||||||
|
return o.finished.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) background() {
|
||||||
|
for !o.finished.Done() {
|
||||||
|
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||||
|
if !ok {
|
||||||
|
errors.LogInfo(o.ctx, "outbound.Manager is not a HandlerSelector")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outbounds := hs.Select(o.config.SubjectSelector)
|
||||||
|
|
||||||
|
o.clearRemovedOutbounds(outbounds)
|
||||||
|
|
||||||
|
sleepTime := time.Second * 10
|
||||||
|
if o.config.ProbeInterval != 0 {
|
||||||
|
sleepTime = time.Duration(o.config.ProbeInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !o.config.EnableConcurrency {
|
||||||
|
sort.Strings(outbounds)
|
||||||
|
for _, v := range outbounds {
|
||||||
|
result := o.probe(v)
|
||||||
|
o.updateStatusForResult(v, &result)
|
||||||
|
if o.finished.Done() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan struct{}, len(outbounds))
|
||||||
|
|
||||||
|
for _, v := range outbounds {
|
||||||
|
go func(v string) {
|
||||||
|
result := o.probe(v)
|
||||||
|
o.updateStatusForResult(v, &result)
|
||||||
|
ch <- struct{}{}
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for range outbounds {
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-o.finished.Wait():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) clearRemovedOutbounds(outbounds []string) {
|
||||||
|
o.statusLock.Lock()
|
||||||
|
defer o.statusLock.Unlock()
|
||||||
|
if len(o.status) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pruned []*OutboundStatus
|
||||||
|
for _, status := range o.status {
|
||||||
|
if slices.Contains(outbounds, status.OutboundTag) {
|
||||||
|
pruned = append(pruned, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.status = pruned
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) probe(outbound string) ProbeResult {
|
||||||
|
errorCollectorForRequest := newErrorCollector()
|
||||||
|
|
||||||
|
httpTransport := http.Transport{
|
||||||
|
Proxy: func(*http.Request) (*url.URL, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||||
|
var connection net.Conn
|
||||||
|
taskErr := task.Run(ctx, func() error {
|
||||||
|
// MUST use Xray's built in context system
|
||||||
|
dest, err := v2net.ParseDestination(network + ":" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot understand address").Base(err)
|
||||||
|
}
|
||||||
|
trackedCtx := session.TrackedConnectionError(o.ctx, errorCollectorForRequest)
|
||||||
|
conn, err := tagged.Dialer(trackedCtx, o.dispatcher, dest, outbound)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("cannot dial remote address ", dest).Base(err)
|
||||||
|
}
|
||||||
|
connection = conn
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if taskErr != nil {
|
||||||
|
return nil, errors.New("cannot finish connection").Base(taskErr)
|
||||||
|
}
|
||||||
|
return connection, nil
|
||||||
|
},
|
||||||
|
TLSHandshakeTimeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &httpTransport,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
Jar: nil,
|
||||||
|
Timeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
var GETTime time.Duration
|
||||||
|
err := task.Run(o.ctx, func() error {
|
||||||
|
startTime := time.Now()
|
||||||
|
probeURL := "https://www.google.com/generate_204"
|
||||||
|
if o.config.ProbeUrl != "" {
|
||||||
|
probeURL = o.config.ProbeUrl
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest(http.MethodGet, probeURL, nil)
|
||||||
|
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||||
|
response, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("outbound failed to relay connection").Base(err)
|
||||||
|
}
|
||||||
|
if response.Body != nil {
|
||||||
|
response.Body.Close()
|
||||||
|
}
|
||||||
|
endTime := time.Now()
|
||||||
|
GETTime = endTime.Sub(startTime)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
||||||
|
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
|
||||||
|
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
|
||||||
|
}
|
||||||
|
errors.LogInfo(o.ctx, "the outbound ", outbound, " is alive:", GETTime.Seconds())
|
||||||
|
return ProbeResult{Alive: true, Delay: GETTime.Milliseconds()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) updateStatusForResult(outbound string, result *ProbeResult) {
|
||||||
|
o.statusLock.Lock()
|
||||||
|
defer o.statusLock.Unlock()
|
||||||
|
var status *OutboundStatus
|
||||||
|
if location := o.findStatusLocationLockHolderOnly(outbound); location != -1 {
|
||||||
|
status = o.status[location]
|
||||||
|
} else {
|
||||||
|
status = &OutboundStatus{}
|
||||||
|
o.status = append(o.status, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
status.LastTryTime = time.Now().Unix()
|
||||||
|
status.OutboundTag = outbound
|
||||||
|
status.Alive = result.Alive
|
||||||
|
if result.Alive {
|
||||||
|
status.Delay = result.Delay
|
||||||
|
status.LastSeenTime = status.LastTryTime
|
||||||
|
status.LastErrorReason = ""
|
||||||
|
} else {
|
||||||
|
status.LastErrorReason = result.LastErrorReason
|
||||||
|
status.Delay = 99999999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Observer) findStatusLocationLockHolderOnly(outbound string) int {
|
||||||
|
for i, v := range o.status {
|
||||||
|
if v.OutboundTag == outbound {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, config *Config) (*Observer, error) {
|
||||||
|
var outboundManager outbound.Manager
|
||||||
|
var dispatcher routing.Dispatcher
|
||||||
|
err := core.RequireFeatures(ctx, func(om outbound.Manager, rd routing.Dispatcher) {
|
||||||
|
outboundManager = om
|
||||||
|
dispatcher = rd
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Cannot get depended features").Base(err)
|
||||||
|
}
|
||||||
|
return &Observer{
|
||||||
|
config: config,
|
||||||
|
ctx: ctx,
|
||||||
|
ohm: outboundManager,
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
64
subproject/Xray-core-main/app/observatory/observer_test.go
Normal file
64
subproject/Xray-core-main/app/observatory/observer_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package observatory
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestObserverUpdateStatusPrunesStaleOutbounds(t *testing.T) {
|
||||||
|
observer := &Observer{
|
||||||
|
status: []*OutboundStatus{
|
||||||
|
{
|
||||||
|
OutboundTag: "keep",
|
||||||
|
Alive: true,
|
||||||
|
Delay: 42,
|
||||||
|
LastErrorReason: "",
|
||||||
|
LastSeenTime: 111,
|
||||||
|
LastTryTime: 222,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OutboundTag: "drop",
|
||||||
|
Alive: false,
|
||||||
|
Delay: 99999999,
|
||||||
|
LastErrorReason: "probe failed",
|
||||||
|
LastSeenTime: 333,
|
||||||
|
LastTryTime: 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.updateStatus([]string{"keep"})
|
||||||
|
|
||||||
|
if len(observer.status) != 1 {
|
||||||
|
t.Fatalf("expected 1 status after pruning, got %d", len(observer.status))
|
||||||
|
}
|
||||||
|
|
||||||
|
got := observer.status[0]
|
||||||
|
if got.OutboundTag != "keep" {
|
||||||
|
t.Fatalf("expected remaining status for keep, got %q", got.OutboundTag)
|
||||||
|
}
|
||||||
|
if !got.Alive {
|
||||||
|
t.Fatal("expected remaining status to preserve Alive field")
|
||||||
|
}
|
||||||
|
if got.Delay != 42 {
|
||||||
|
t.Fatalf("expected remaining status to preserve Delay, got %d", got.Delay)
|
||||||
|
}
|
||||||
|
if got.LastSeenTime != 111 {
|
||||||
|
t.Fatalf("expected remaining status to preserve LastSeenTime, got %d", got.LastSeenTime)
|
||||||
|
}
|
||||||
|
if got.LastTryTime != 222 {
|
||||||
|
t.Fatalf("expected remaining status to preserve LastTryTime, got %d", got.LastTryTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObserverUpdateStatusClearsWhenNoOutboundsRemain(t *testing.T) {
|
||||||
|
observer := &Observer{
|
||||||
|
status: []*OutboundStatus{
|
||||||
|
{OutboundTag: "drop-1"},
|
||||||
|
{OutboundTag: "drop-2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.updateStatus(nil)
|
||||||
|
|
||||||
|
if len(observer.status) != 0 {
|
||||||
|
t.Fatalf("expected all statuses to be removed, got %d", len(observer.status))
|
||||||
|
}
|
||||||
|
}
|
||||||
94
subproject/Xray-core-main/app/policy/config.go
Normal file
94
subproject/Xray-core-main/app/policy/config.go
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/features/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Duration converts Second to time.Duration.
|
||||||
|
func (s *Second) Duration() time.Duration {
|
||||||
|
if s == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Second * time.Duration(s.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPolicy() *Policy {
|
||||||
|
p := policy.SessionDefault()
|
||||||
|
|
||||||
|
return &Policy{
|
||||||
|
Timeout: &Policy_Timeout{
|
||||||
|
Handshake: &Second{Value: uint32(p.Timeouts.Handshake / time.Second)},
|
||||||
|
ConnectionIdle: &Second{Value: uint32(p.Timeouts.ConnectionIdle / time.Second)},
|
||||||
|
UplinkOnly: &Second{Value: uint32(p.Timeouts.UplinkOnly / time.Second)},
|
||||||
|
DownlinkOnly: &Second{Value: uint32(p.Timeouts.DownlinkOnly / time.Second)},
|
||||||
|
},
|
||||||
|
Buffer: &Policy_Buffer{
|
||||||
|
Connection: p.Buffer.PerConnection,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy_Timeout) overrideWith(another *Policy_Timeout) {
|
||||||
|
if another.Handshake != nil {
|
||||||
|
p.Handshake = &Second{Value: another.Handshake.Value}
|
||||||
|
}
|
||||||
|
if another.ConnectionIdle != nil {
|
||||||
|
p.ConnectionIdle = &Second{Value: another.ConnectionIdle.Value}
|
||||||
|
}
|
||||||
|
if another.UplinkOnly != nil {
|
||||||
|
p.UplinkOnly = &Second{Value: another.UplinkOnly.Value}
|
||||||
|
}
|
||||||
|
if another.DownlinkOnly != nil {
|
||||||
|
p.DownlinkOnly = &Second{Value: another.DownlinkOnly.Value}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) overrideWith(another *Policy) {
|
||||||
|
if another.Timeout != nil {
|
||||||
|
p.Timeout.overrideWith(another.Timeout)
|
||||||
|
}
|
||||||
|
if another.Stats != nil && p.Stats == nil {
|
||||||
|
p.Stats = &Policy_Stats{}
|
||||||
|
p.Stats = another.Stats
|
||||||
|
}
|
||||||
|
if another.Buffer != nil {
|
||||||
|
p.Buffer = &Policy_Buffer{
|
||||||
|
Connection: another.Buffer.Connection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCorePolicy converts this Policy to policy.Session.
|
||||||
|
func (p *Policy) ToCorePolicy() policy.Session {
|
||||||
|
cp := policy.SessionDefault()
|
||||||
|
|
||||||
|
if p.Timeout != nil {
|
||||||
|
cp.Timeouts.ConnectionIdle = p.Timeout.ConnectionIdle.Duration()
|
||||||
|
cp.Timeouts.Handshake = p.Timeout.Handshake.Duration()
|
||||||
|
cp.Timeouts.DownlinkOnly = p.Timeout.DownlinkOnly.Duration()
|
||||||
|
cp.Timeouts.UplinkOnly = p.Timeout.UplinkOnly.Duration()
|
||||||
|
}
|
||||||
|
if p.Stats != nil {
|
||||||
|
cp.Stats.UserUplink = p.Stats.UserUplink
|
||||||
|
cp.Stats.UserDownlink = p.Stats.UserDownlink
|
||||||
|
cp.Stats.UserOnline = p.Stats.UserOnline
|
||||||
|
}
|
||||||
|
if p.Buffer != nil {
|
||||||
|
cp.Buffer.PerConnection = p.Buffer.Connection
|
||||||
|
}
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCorePolicy converts this SystemPolicy to policy.System.
|
||||||
|
func (p *SystemPolicy) ToCorePolicy() policy.System {
|
||||||
|
return policy.System{
|
||||||
|
Stats: policy.SystemStats{
|
||||||
|
InboundUplink: p.Stats.InboundUplink,
|
||||||
|
InboundDownlink: p.Stats.InboundDownlink,
|
||||||
|
OutboundUplink: p.Stats.OutboundUplink,
|
||||||
|
OutboundDownlink: p.Stats.OutboundDownlink,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
574
subproject/Xray-core-main/app/policy/config.pb.go
Normal file
574
subproject/Xray-core-main/app/policy/config.pb.go
Normal file
|
|
@ -0,0 +1,574 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.5
|
||||||
|
// source: app/policy/config.proto
|
||||||
|
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Second struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Second) Reset() {
|
||||||
|
*x = Second{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Second) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Second) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Second) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Second.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Second) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Second) GetValue() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Policy struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Timeout *Policy_Timeout `protobuf:"bytes,1,opt,name=timeout,proto3" json:"timeout,omitempty"`
|
||||||
|
Stats *Policy_Stats `protobuf:"bytes,2,opt,name=stats,proto3" json:"stats,omitempty"`
|
||||||
|
Buffer *Policy_Buffer `protobuf:"bytes,3,opt,name=buffer,proto3" json:"buffer,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy) Reset() {
|
||||||
|
*x = Policy{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Policy) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Policy) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Policy.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Policy) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy) GetTimeout() *Policy_Timeout {
|
||||||
|
if x != nil {
|
||||||
|
return x.Timeout
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy) GetStats() *Policy_Stats {
|
||||||
|
if x != nil {
|
||||||
|
return x.Stats
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy) GetBuffer() *Policy_Buffer {
|
||||||
|
if x != nil {
|
||||||
|
return x.Buffer
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemPolicy struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Stats *SystemPolicy_Stats `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy) Reset() {
|
||||||
|
*x = SystemPolicy{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SystemPolicy) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SystemPolicy) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SystemPolicy.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SystemPolicy) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy) GetStats() *SystemPolicy_Stats {
|
||||||
|
if x != nil {
|
||||||
|
return x.Stats
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Level map[uint32]*Policy `protobuf:"bytes,1,rep,name=level,proto3" json:"level,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
System *SystemPolicy `protobuf:"bytes,2,opt,name=system,proto3" json:"system,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) Reset() {
|
||||||
|
*x = Config{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetLevel() map[uint32]*Policy {
|
||||||
|
if x != nil {
|
||||||
|
return x.Level
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetSystem() *SystemPolicy {
|
||||||
|
if x != nil {
|
||||||
|
return x.System
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout is a message for timeout settings in various stages, in seconds.
|
||||||
|
type Policy_Timeout struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Handshake *Second `protobuf:"bytes,1,opt,name=handshake,proto3" json:"handshake,omitempty"`
|
||||||
|
ConnectionIdle *Second `protobuf:"bytes,2,opt,name=connection_idle,json=connectionIdle,proto3" json:"connection_idle,omitempty"`
|
||||||
|
UplinkOnly *Second `protobuf:"bytes,3,opt,name=uplink_only,json=uplinkOnly,proto3" json:"uplink_only,omitempty"`
|
||||||
|
DownlinkOnly *Second `protobuf:"bytes,4,opt,name=downlink_only,json=downlinkOnly,proto3" json:"downlink_only,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) Reset() {
|
||||||
|
*x = Policy_Timeout{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Policy_Timeout) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Policy_Timeout.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Policy_Timeout) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) GetHandshake() *Second {
|
||||||
|
if x != nil {
|
||||||
|
return x.Handshake
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) GetConnectionIdle() *Second {
|
||||||
|
if x != nil {
|
||||||
|
return x.ConnectionIdle
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) GetUplinkOnly() *Second {
|
||||||
|
if x != nil {
|
||||||
|
return x.UplinkOnly
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Timeout) GetDownlinkOnly() *Second {
|
||||||
|
if x != nil {
|
||||||
|
return x.DownlinkOnly
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Policy_Stats struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserUplink bool `protobuf:"varint,1,opt,name=user_uplink,json=userUplink,proto3" json:"user_uplink,omitempty"`
|
||||||
|
UserDownlink bool `protobuf:"varint,2,opt,name=user_downlink,json=userDownlink,proto3" json:"user_downlink,omitempty"`
|
||||||
|
UserOnline bool `protobuf:"varint,3,opt,name=user_online,json=userOnline,proto3" json:"user_online,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Stats) Reset() {
|
||||||
|
*x = Policy_Stats{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Stats) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Policy_Stats) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Policy_Stats) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Policy_Stats.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Policy_Stats) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{1, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Stats) GetUserUplink() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserUplink
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Stats) GetUserDownlink() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserDownlink
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Stats) GetUserOnline() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserOnline
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Policy_Buffer struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// Buffer size per connection, in bytes. -1 for unlimited buffer.
|
||||||
|
Connection int32 `protobuf:"varint,1,opt,name=connection,proto3" json:"connection,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Buffer) Reset() {
|
||||||
|
*x = Policy_Buffer{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Buffer) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Policy_Buffer) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Policy_Buffer) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Policy_Buffer.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Policy_Buffer) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{1, 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Policy_Buffer) GetConnection() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Connection
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemPolicy_Stats struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
InboundUplink bool `protobuf:"varint,1,opt,name=inbound_uplink,json=inboundUplink,proto3" json:"inbound_uplink,omitempty"`
|
||||||
|
InboundDownlink bool `protobuf:"varint,2,opt,name=inbound_downlink,json=inboundDownlink,proto3" json:"inbound_downlink,omitempty"`
|
||||||
|
OutboundUplink bool `protobuf:"varint,3,opt,name=outbound_uplink,json=outboundUplink,proto3" json:"outbound_uplink,omitempty"`
|
||||||
|
OutboundDownlink bool `protobuf:"varint,4,opt,name=outbound_downlink,json=outboundDownlink,proto3" json:"outbound_downlink,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) Reset() {
|
||||||
|
*x = SystemPolicy_Stats{}
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SystemPolicy_Stats) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_app_policy_config_proto_msgTypes[7]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SystemPolicy_Stats.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SystemPolicy_Stats) Descriptor() ([]byte, []int) {
|
||||||
|
return file_app_policy_config_proto_rawDescGZIP(), []int{2, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) GetInboundUplink() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.InboundUplink
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) GetInboundDownlink() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.InboundDownlink
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) GetOutboundUplink() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.OutboundUplink
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SystemPolicy_Stats) GetOutboundDownlink() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.OutboundDownlink
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_app_policy_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_app_policy_config_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x17app/policy/config.proto\x12\x0fxray.app.policy\"\x1e\n" +
|
||||||
|
"\x06Second\x12\x14\n" +
|
||||||
|
"\x05value\x18\x01 \x01(\rR\x05value\"\xc7\x04\n" +
|
||||||
|
"\x06Policy\x129\n" +
|
||||||
|
"\atimeout\x18\x01 \x01(\v2\x1f.xray.app.policy.Policy.TimeoutR\atimeout\x123\n" +
|
||||||
|
"\x05stats\x18\x02 \x01(\v2\x1d.xray.app.policy.Policy.StatsR\x05stats\x126\n" +
|
||||||
|
"\x06buffer\x18\x03 \x01(\v2\x1e.xray.app.policy.Policy.BufferR\x06buffer\x1a\xfa\x01\n" +
|
||||||
|
"\aTimeout\x125\n" +
|
||||||
|
"\thandshake\x18\x01 \x01(\v2\x17.xray.app.policy.SecondR\thandshake\x12@\n" +
|
||||||
|
"\x0fconnection_idle\x18\x02 \x01(\v2\x17.xray.app.policy.SecondR\x0econnectionIdle\x128\n" +
|
||||||
|
"\vuplink_only\x18\x03 \x01(\v2\x17.xray.app.policy.SecondR\n" +
|
||||||
|
"uplinkOnly\x12<\n" +
|
||||||
|
"\rdownlink_only\x18\x04 \x01(\v2\x17.xray.app.policy.SecondR\fdownlinkOnly\x1an\n" +
|
||||||
|
"\x05Stats\x12\x1f\n" +
|
||||||
|
"\vuser_uplink\x18\x01 \x01(\bR\n" +
|
||||||
|
"userUplink\x12#\n" +
|
||||||
|
"\ruser_downlink\x18\x02 \x01(\bR\fuserDownlink\x12\x1f\n" +
|
||||||
|
"\vuser_online\x18\x03 \x01(\bR\n" +
|
||||||
|
"userOnline\x1a(\n" +
|
||||||
|
"\x06Buffer\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"connection\x18\x01 \x01(\x05R\n" +
|
||||||
|
"connection\"\xfb\x01\n" +
|
||||||
|
"\fSystemPolicy\x129\n" +
|
||||||
|
"\x05stats\x18\x01 \x01(\v2#.xray.app.policy.SystemPolicy.StatsR\x05stats\x1a\xaf\x01\n" +
|
||||||
|
"\x05Stats\x12%\n" +
|
||||||
|
"\x0einbound_uplink\x18\x01 \x01(\bR\rinboundUplink\x12)\n" +
|
||||||
|
"\x10inbound_downlink\x18\x02 \x01(\bR\x0finboundDownlink\x12'\n" +
|
||||||
|
"\x0foutbound_uplink\x18\x03 \x01(\bR\x0eoutboundUplink\x12+\n" +
|
||||||
|
"\x11outbound_downlink\x18\x04 \x01(\bR\x10outboundDownlink\"\xcc\x01\n" +
|
||||||
|
"\x06Config\x128\n" +
|
||||||
|
"\x05level\x18\x01 \x03(\v2\".xray.app.policy.Config.LevelEntryR\x05level\x125\n" +
|
||||||
|
"\x06system\x18\x02 \x01(\v2\x1d.xray.app.policy.SystemPolicyR\x06system\x1aQ\n" +
|
||||||
|
"\n" +
|
||||||
|
"LevelEntry\x12\x10\n" +
|
||||||
|
"\x03key\x18\x01 \x01(\rR\x03key\x12-\n" +
|
||||||
|
"\x05value\x18\x02 \x01(\v2\x17.xray.app.policy.PolicyR\x05value:\x028\x01BO\n" +
|
||||||
|
"\x13com.xray.app.policyP\x01Z$github.com/xtls/xray-core/app/policy\xaa\x02\x0fXray.App.Policyb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_app_policy_config_proto_rawDescOnce sync.Once
|
||||||
|
file_app_policy_config_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_app_policy_config_proto_rawDescGZIP() []byte {
|
||||||
|
file_app_policy_config_proto_rawDescOnce.Do(func() {
|
||||||
|
file_app_policy_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_policy_config_proto_rawDesc), len(file_app_policy_config_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_app_policy_config_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_app_policy_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||||
|
var file_app_policy_config_proto_goTypes = []any{
|
||||||
|
(*Second)(nil), // 0: xray.app.policy.Second
|
||||||
|
(*Policy)(nil), // 1: xray.app.policy.Policy
|
||||||
|
(*SystemPolicy)(nil), // 2: xray.app.policy.SystemPolicy
|
||||||
|
(*Config)(nil), // 3: xray.app.policy.Config
|
||||||
|
(*Policy_Timeout)(nil), // 4: xray.app.policy.Policy.Timeout
|
||||||
|
(*Policy_Stats)(nil), // 5: xray.app.policy.Policy.Stats
|
||||||
|
(*Policy_Buffer)(nil), // 6: xray.app.policy.Policy.Buffer
|
||||||
|
(*SystemPolicy_Stats)(nil), // 7: xray.app.policy.SystemPolicy.Stats
|
||||||
|
nil, // 8: xray.app.policy.Config.LevelEntry
|
||||||
|
}
|
||||||
|
var file_app_policy_config_proto_depIdxs = []int32{
|
||||||
|
4, // 0: xray.app.policy.Policy.timeout:type_name -> xray.app.policy.Policy.Timeout
|
||||||
|
5, // 1: xray.app.policy.Policy.stats:type_name -> xray.app.policy.Policy.Stats
|
||||||
|
6, // 2: xray.app.policy.Policy.buffer:type_name -> xray.app.policy.Policy.Buffer
|
||||||
|
7, // 3: xray.app.policy.SystemPolicy.stats:type_name -> xray.app.policy.SystemPolicy.Stats
|
||||||
|
8, // 4: xray.app.policy.Config.level:type_name -> xray.app.policy.Config.LevelEntry
|
||||||
|
2, // 5: xray.app.policy.Config.system:type_name -> xray.app.policy.SystemPolicy
|
||||||
|
0, // 6: xray.app.policy.Policy.Timeout.handshake:type_name -> xray.app.policy.Second
|
||||||
|
0, // 7: xray.app.policy.Policy.Timeout.connection_idle:type_name -> xray.app.policy.Second
|
||||||
|
0, // 8: xray.app.policy.Policy.Timeout.uplink_only:type_name -> xray.app.policy.Second
|
||||||
|
0, // 9: xray.app.policy.Policy.Timeout.downlink_only:type_name -> xray.app.policy.Second
|
||||||
|
1, // 10: xray.app.policy.Config.LevelEntry.value:type_name -> xray.app.policy.Policy
|
||||||
|
11, // [11:11] is the sub-list for method output_type
|
||||||
|
11, // [11:11] is the sub-list for method input_type
|
||||||
|
11, // [11:11] is the sub-list for extension type_name
|
||||||
|
11, // [11:11] is the sub-list for extension extendee
|
||||||
|
0, // [0:11] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_app_policy_config_proto_init() }
|
||||||
|
func file_app_policy_config_proto_init() {
|
||||||
|
if File_app_policy_config_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_policy_config_proto_rawDesc), len(file_app_policy_config_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 9,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_app_policy_config_proto_goTypes,
|
||||||
|
DependencyIndexes: file_app_policy_config_proto_depIdxs,
|
||||||
|
MessageInfos: file_app_policy_config_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_app_policy_config_proto = out.File
|
||||||
|
file_app_policy_config_proto_goTypes = nil
|
||||||
|
file_app_policy_config_proto_depIdxs = nil
|
||||||
|
}
|
||||||
52
subproject/Xray-core-main/app/policy/config.proto
Normal file
52
subproject/Xray-core-main/app/policy/config.proto
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.policy;
|
||||||
|
option csharp_namespace = "Xray.App.Policy";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/policy";
|
||||||
|
option java_package = "com.xray.app.policy";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Second {
|
||||||
|
uint32 value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Policy {
|
||||||
|
// Timeout is a message for timeout settings in various stages, in seconds.
|
||||||
|
message Timeout {
|
||||||
|
Second handshake = 1;
|
||||||
|
Second connection_idle = 2;
|
||||||
|
Second uplink_only = 3;
|
||||||
|
Second downlink_only = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Stats {
|
||||||
|
bool user_uplink = 1;
|
||||||
|
bool user_downlink = 2;
|
||||||
|
bool user_online = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Buffer {
|
||||||
|
// Buffer size per connection, in bytes. -1 for unlimited buffer.
|
||||||
|
int32 connection = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeout timeout = 1;
|
||||||
|
Stats stats = 2;
|
||||||
|
Buffer buffer = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SystemPolicy {
|
||||||
|
message Stats {
|
||||||
|
bool inbound_uplink = 1;
|
||||||
|
bool inbound_downlink = 2;
|
||||||
|
bool outbound_uplink = 3;
|
||||||
|
bool outbound_downlink = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats stats = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
map<uint32, Policy> level = 1;
|
||||||
|
SystemPolicy system = 2;
|
||||||
|
}
|
||||||
68
subproject/Xray-core-main/app/policy/manager.go
Normal file
68
subproject/Xray-core-main/app/policy/manager.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/features/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Instance is an instance of Policy manager.
|
||||||
|
type Instance struct {
|
||||||
|
levels map[uint32]*Policy
|
||||||
|
system *SystemPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates new Policy manager instance.
|
||||||
|
func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||||
|
m := &Instance{
|
||||||
|
levels: make(map[uint32]*Policy),
|
||||||
|
system: config.System,
|
||||||
|
}
|
||||||
|
if len(config.Level) > 0 {
|
||||||
|
for lv, p := range config.Level {
|
||||||
|
pp := defaultPolicy()
|
||||||
|
pp.overrideWith(p)
|
||||||
|
m.levels[lv] = pp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements common.HasType.
|
||||||
|
func (*Instance) Type() interface{} {
|
||||||
|
return policy.ManagerType()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForLevel implements policy.Manager.
|
||||||
|
func (m *Instance) ForLevel(level uint32) policy.Session {
|
||||||
|
if p, ok := m.levels[level]; ok {
|
||||||
|
return p.ToCorePolicy()
|
||||||
|
}
|
||||||
|
return policy.SessionDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForSystem implements policy.Manager.
|
||||||
|
func (m *Instance) ForSystem() policy.System {
|
||||||
|
if m.system == nil {
|
||||||
|
return policy.System{}
|
||||||
|
}
|
||||||
|
return m.system.ToCorePolicy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements common.Runnable.Start().
|
||||||
|
func (m *Instance) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements common.Closable.Close().
|
||||||
|
func (m *Instance) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
|
return New(ctx, config.(*Config))
|
||||||
|
}))
|
||||||
|
}
|
||||||
45
subproject/Xray-core-main/app/policy/manager_test.go
Normal file
45
subproject/Xray-core-main/app/policy/manager_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package policy_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/xtls/xray-core/app/policy"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/features/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPolicy(t *testing.T) {
|
||||||
|
manager, err := New(context.Background(), &Config{
|
||||||
|
Level: map[uint32]*Policy{
|
||||||
|
0: {
|
||||||
|
Timeout: &Policy_Timeout{
|
||||||
|
Handshake: &Second{
|
||||||
|
Value: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
pDefault := policy.SessionDefault()
|
||||||
|
|
||||||
|
{
|
||||||
|
p := manager.ForLevel(0)
|
||||||
|
if p.Timeouts.Handshake != 2*time.Second {
|
||||||
|
t.Error("expect 2 sec timeout, but got ", p.Timeouts.Handshake)
|
||||||
|
}
|
||||||
|
if p.Timeouts.ConnectionIdle != pDefault.Timeouts.ConnectionIdle {
|
||||||
|
t.Error("expect ", pDefault.Timeouts.ConnectionIdle, " sec timeout, but got ", p.Timeouts.ConnectionIdle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
p := manager.ForLevel(1)
|
||||||
|
if p.Timeouts.Handshake != pDefault.Timeouts.Handshake {
|
||||||
|
t.Error("expect ", pDefault.Timeouts.Handshake, " sec timeout, but got ", p.Timeouts.Handshake)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
subproject/Xray-core-main/app/policy/policy.go
Normal file
2
subproject/Xray-core-main/app/policy/policy.go
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package policy is an implementation of policy.Manager feature.
|
||||||
|
package policy
|
||||||
234
subproject/Xray-core-main/app/proxyman/command/command.go
Normal file
234
subproject/Xray-core-main/app/proxyman/command/command.go
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/app/commander"
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/inbound"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
"github.com/xtls/xray-core/proxy"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InboundOperation is the interface for operations that applies to inbound handlers.
|
||||||
|
type InboundOperation interface {
|
||||||
|
// ApplyInbound applies this operation to the given inbound handler.
|
||||||
|
ApplyInbound(context.Context, inbound.Handler) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutboundOperation is the interface for operations that applies to outbound handlers.
|
||||||
|
type OutboundOperation interface {
|
||||||
|
// ApplyOutbound applies this operation to the given outbound handler.
|
||||||
|
ApplyOutbound(context.Context, outbound.Handler) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInbound(handler inbound.Handler) (proxy.Inbound, error) {
|
||||||
|
gi, ok := handler.(proxy.GetInbound)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("can't get inbound proxy from handler.")
|
||||||
|
}
|
||||||
|
return gi.GetInbound(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyInbound implements InboundOperation.
|
||||||
|
func (op *AddUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error {
|
||||||
|
p, err := getInbound(handler)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
um, ok := p.(proxy.UserManager)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("proxy is not a UserManager")
|
||||||
|
}
|
||||||
|
mUser, err := op.User.ToMemoryUser()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse user").Base(err)
|
||||||
|
}
|
||||||
|
return um.AddUser(ctx, mUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyInbound implements InboundOperation.
|
||||||
|
func (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error {
|
||||||
|
p, err := getInbound(handler)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
um, ok := p.(proxy.UserManager)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("proxy is not a UserManager")
|
||||||
|
}
|
||||||
|
return um.RemoveUser(ctx, op.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
type handlerServer struct {
|
||||||
|
s *core.Instance
|
||||||
|
ihm inbound.Manager
|
||||||
|
ohm outbound.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) AddInbound(ctx context.Context, request *AddInboundRequest) (*AddInboundResponse, error) {
|
||||||
|
if err := core.AddInboundHandler(s.s, request.Inbound); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AddInboundResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboundRequest) (*RemoveInboundResponse, error) {
|
||||||
|
return &RemoveInboundResponse{}, s.ihm.RemoveHandler(ctx, request.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) {
|
||||||
|
rawOperation, err := request.Operation.GetInstance()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("unknown operation").Base(err)
|
||||||
|
}
|
||||||
|
operation, ok := rawOperation.(InboundOperation)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an inbound operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to get handler: ", request.Tag).Base(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) ListInbounds(ctx context.Context, request *ListInboundsRequest) (*ListInboundsResponse, error) {
|
||||||
|
handlers := s.ihm.ListHandlers(ctx)
|
||||||
|
response := &ListInboundsResponse{}
|
||||||
|
if request.GetIsOnlyTags() {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{
|
||||||
|
Tag: handler.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{
|
||||||
|
Tag: handler.Tag(),
|
||||||
|
ReceiverSettings: handler.ReceiverSettings(),
|
||||||
|
ProxySettings: handler.ProxySettings(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUserResponse, error) {
|
||||||
|
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to get handler: ", request.Tag).Base(err)
|
||||||
|
}
|
||||||
|
p, err := getInbound(handler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
um, ok := p.(proxy.UserManager)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("proxy is not a UserManager")
|
||||||
|
}
|
||||||
|
if len(request.Email) > 0 {
|
||||||
|
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
|
||||||
|
}
|
||||||
|
var result = make([]*protocol.User, 0, 100)
|
||||||
|
users := um.GetUsers(ctx)
|
||||||
|
for _, u := range users {
|
||||||
|
result = append(result, protocol.ToProtoUser(u))
|
||||||
|
}
|
||||||
|
return &GetInboundUserResponse{Users: result}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) GetInboundUsersCount(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUsersCountResponse, error) {
|
||||||
|
handler, err := s.ihm.GetHandler(ctx, request.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to get handler: ", request.Tag).Base(err)
|
||||||
|
}
|
||||||
|
p, err := getInbound(handler)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
um, ok := p.(proxy.UserManager)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("proxy is not a UserManager")
|
||||||
|
}
|
||||||
|
return &GetInboundUsersCountResponse{Count: um.GetUsersCount(ctx)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) {
|
||||||
|
if err := core.AddOutboundHandler(s.s, request.Outbound); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &AddOutboundResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutboundRequest) (*RemoveOutboundResponse, error) {
|
||||||
|
return &RemoveOutboundResponse{}, s.ohm.RemoveHandler(ctx, request.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) {
|
||||||
|
rawOperation, err := request.Operation.GetInstance()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("unknown operation").Base(err)
|
||||||
|
}
|
||||||
|
operation, ok := rawOperation.(OutboundOperation)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an outbound operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := s.ohm.GetHandler(request.Tag)
|
||||||
|
return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) ListOutbounds(ctx context.Context, request *ListOutboundsRequest) (*ListOutboundsResponse, error) {
|
||||||
|
handlers := s.ohm.ListHandlers(ctx)
|
||||||
|
response := &ListOutboundsResponse{}
|
||||||
|
for _, handler := range handlers {
|
||||||
|
// Ignore gRPC outbound
|
||||||
|
if _, ok := handler.(*commander.Outbound); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
response.Outbounds = append(response.Outbounds, &core.OutboundHandlerConfig{
|
||||||
|
Tag: handler.Tag(),
|
||||||
|
SenderSettings: handler.SenderSettings(),
|
||||||
|
ProxySettings: handler.ProxySettings(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
v *core.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Register(server *grpc.Server) {
|
||||||
|
hs := &handlerServer{
|
||||||
|
s: s.v,
|
||||||
|
}
|
||||||
|
common.Must(s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) {
|
||||||
|
hs.ihm = im
|
||||||
|
hs.ohm = om
|
||||||
|
}, false))
|
||||||
|
RegisterHandlerServiceServer(server, hs)
|
||||||
|
|
||||||
|
// For compatibility purposes
|
||||||
|
vCoreDesc := HandlerService_ServiceDesc
|
||||||
|
vCoreDesc.ServiceName = "v2ray.core.app.proxyman.command.HandlerService"
|
||||||
|
server.RegisterService(&vCoreDesc, hs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||||
|
s := core.MustFromContext(ctx)
|
||||||
|
return &service{v: s}, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
1114
subproject/Xray-core-main/app/proxyman/command/command.pb.go
Normal file
1114
subproject/Xray-core-main/app/proxyman/command/command.pb.go
Normal file
File diff suppressed because it is too large
Load diff
108
subproject/Xray-core-main/app/proxyman/command/command.proto
Normal file
108
subproject/Xray-core-main/app/proxyman/command/command.proto
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package xray.app.proxyman.command;
|
||||||
|
option csharp_namespace = "Xray.App.Proxyman.Command";
|
||||||
|
option go_package = "github.com/xtls/xray-core/app/proxyman/command";
|
||||||
|
option java_package = "com.xray.app.proxyman.command";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
import "common/protocol/user.proto";
|
||||||
|
import "common/serial/typed_message.proto";
|
||||||
|
import "core/config.proto";
|
||||||
|
|
||||||
|
message AddUserOperation {
|
||||||
|
xray.common.protocol.User user = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveUserOperation {
|
||||||
|
string email = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddInboundRequest {
|
||||||
|
core.InboundHandlerConfig inbound = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddInboundResponse {}
|
||||||
|
|
||||||
|
message RemoveInboundRequest {
|
||||||
|
string tag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveInboundResponse {}
|
||||||
|
|
||||||
|
message AlterInboundRequest {
|
||||||
|
string tag = 1;
|
||||||
|
xray.common.serial.TypedMessage operation = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AlterInboundResponse {}
|
||||||
|
|
||||||
|
message ListInboundsRequest {
|
||||||
|
bool isOnlyTags = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListInboundsResponse {
|
||||||
|
repeated core.InboundHandlerConfig inbounds = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInboundUserRequest {
|
||||||
|
string tag = 1;
|
||||||
|
string email = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInboundUserResponse {
|
||||||
|
repeated xray.common.protocol.User users = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInboundUsersCountResponse {
|
||||||
|
int64 count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddOutboundRequest {
|
||||||
|
core.OutboundHandlerConfig outbound = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddOutboundResponse {}
|
||||||
|
|
||||||
|
message RemoveOutboundRequest {
|
||||||
|
string tag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveOutboundResponse {}
|
||||||
|
|
||||||
|
message AlterOutboundRequest {
|
||||||
|
string tag = 1;
|
||||||
|
xray.common.serial.TypedMessage operation = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AlterOutboundResponse {}
|
||||||
|
|
||||||
|
message ListOutboundsRequest {}
|
||||||
|
|
||||||
|
message ListOutboundsResponse {
|
||||||
|
repeated core.OutboundHandlerConfig outbounds = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service HandlerService {
|
||||||
|
rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {}
|
||||||
|
|
||||||
|
rpc RemoveInbound(RemoveInboundRequest) returns (RemoveInboundResponse) {}
|
||||||
|
|
||||||
|
rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {}
|
||||||
|
|
||||||
|
rpc ListInbounds(ListInboundsRequest) returns (ListInboundsResponse) {}
|
||||||
|
|
||||||
|
rpc GetInboundUsers(GetInboundUserRequest) returns (GetInboundUserResponse) {}
|
||||||
|
|
||||||
|
rpc GetInboundUsersCount(GetInboundUserRequest) returns (GetInboundUsersCountResponse) {}
|
||||||
|
|
||||||
|
rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {}
|
||||||
|
|
||||||
|
rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {}
|
||||||
|
|
||||||
|
rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {}
|
||||||
|
|
||||||
|
rpc ListOutbounds(ListOutboundsRequest) returns (ListOutboundsResponse) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Config {}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue