Upload 256 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- .github/FUNDING.yml +1 -0
- .github/ISSUE_TEMPLATE/bug_report.md +26 -0
- .github/ISSUE_TEMPLATE/bug_report.zh.md +26 -0
- .github/ISSUE_TEMPLATE/feature_request.md +20 -0
- .github/ISSUE_TEMPLATE/feature_request.zh.md +20 -0
- .github/dependabot.yml +10 -0
- .github/workflows/autotag.yaml +104 -0
- .github/workflows/docker.yml +44 -0
- .github/workflows/master.yml +52 -0
- .github/workflows/release.yml +71 -0
- .github/workflows/scripts.yml +29 -0
- .gitignore +470 -0
- CHANGELOG.md +3 -0
- Dockerfile +39 -0
- LICENSE.md +7 -0
- PROTOCOL.md +153 -0
- app/cmd/client.go +1031 -0
- app/cmd/client_test.go +204 -0
- app/cmd/client_test.yaml +85 -0
- app/cmd/errors.go +18 -0
- app/cmd/ping.go +63 -0
- app/cmd/root.go +176 -0
- app/cmd/server.go +1051 -0
- app/cmd/server_test.go +189 -0
- app/cmd/server_test.yaml +144 -0
- app/cmd/share.go +55 -0
- app/cmd/speedtest.go +178 -0
- app/cmd/update.go +88 -0
- app/cmd/version.go +23 -0
- app/go.mod +92 -0
- app/go.sum +661 -0
- app/internal/forwarding/tcp.go +62 -0
- app/internal/forwarding/tcp_test.go +39 -0
- app/internal/forwarding/udp.go +180 -0
- app/internal/forwarding/udp_test.go +39 -0
- app/internal/http/server.go +301 -0
- app/internal/http/server_test.go +59 -0
- app/internal/http/server_test.py +24 -0
- app/internal/http/test.crt +23 -0
- app/internal/http/test.key +27 -0
- app/internal/proxymux/.mockery.yaml +12 -0
- app/internal/proxymux/internal/mocks/mock_Conn.go +427 -0
- app/internal/proxymux/internal/mocks/mock_Listener.go +185 -0
- app/internal/proxymux/manager.go +72 -0
- app/internal/proxymux/manager_test.go +110 -0
- app/internal/proxymux/mux.go +320 -0
- app/internal/proxymux/mux_test.go +154 -0
- app/internal/redirect/getsockopt_linux.go +17 -0
- app/internal/redirect/getsockopt_linux_386.go +23 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
extras/outbounds/acl/v2geo/geoip.dat filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
extras/outbounds/acl/v2geo/geosite.dat filter=lfs diff=lfs merge=lfs -text
|
.github/FUNDING.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
custom: [ 'https://v2.hysteria.network/docs/Donation/' ]
|
.github/ISSUE_TEMPLATE/bug_report.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Bug report
|
| 3 |
+
about: Report anything you think is a bug and needs to be fixed.
|
| 4 |
+
title: ''
|
| 5 |
+
labels: bug
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**Describe the bug**
|
| 11 |
+
A clear and concise description of what the bug is.
|
| 12 |
+
|
| 13 |
+
**To Reproduce**
|
| 14 |
+
Steps to reproduce the behavior.
|
| 15 |
+
|
| 16 |
+
**Expected behavior**
|
| 17 |
+
A clear and concise description of what you expected to happen.
|
| 18 |
+
|
| 19 |
+
**Logs**
|
| 20 |
+
Attach logs from the client/server when the error occurs.
|
| 21 |
+
|
| 22 |
+
**Device and Operating System**
|
| 23 |
+
What are you using it on.
|
| 24 |
+
|
| 25 |
+
**Additional context**
|
| 26 |
+
Add any other context about the problem here.
|
.github/ISSUE_TEMPLATE/bug_report.zh.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Bug 反馈
|
| 3 |
+
about: 反馈任何你认为是 bug 需要修复的问题。
|
| 4 |
+
title: ''
|
| 5 |
+
labels: bug
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**描述问题**
|
| 11 |
+
请尽量清晰精准地描述你遇到的问题。
|
| 12 |
+
|
| 13 |
+
**如何复现**
|
| 14 |
+
复现问题的步骤。
|
| 15 |
+
|
| 16 |
+
**预期行为**
|
| 17 |
+
你认为修复后的行为应该是怎样的。
|
| 18 |
+
|
| 19 |
+
**日志**
|
| 20 |
+
附上客户端/服务器端在错误发生前后的日志。
|
| 21 |
+
|
| 22 |
+
**设备和操作系统**
|
| 23 |
+
你在用什么设备和操作系统。
|
| 24 |
+
|
| 25 |
+
**额外信息**
|
| 26 |
+
其他你认为有助于解决问题的信息。
|
.github/ISSUE_TEMPLATE/feature_request.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Feature request
|
| 3 |
+
about: Suggest an idea for this project.
|
| 4 |
+
title: ''
|
| 5 |
+
labels: enhancement
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**Is your feature request related to a problem? Please describe.**
|
| 11 |
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
| 12 |
+
|
| 13 |
+
**Describe the solution you'd like**
|
| 14 |
+
A clear and concise description of what you want to happen.
|
| 15 |
+
|
| 16 |
+
**Describe alternatives you've considered**
|
| 17 |
+
A clear and concise description of any alternative solutions or features you've considered.
|
| 18 |
+
|
| 19 |
+
**Additional context**
|
| 20 |
+
Add any other context or screenshots about the feature request here.
|
.github/ISSUE_TEMPLATE/feature_request.zh.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: 功能请求
|
| 3 |
+
about: 为这个项目提出改进意见。
|
| 4 |
+
title: ''
|
| 5 |
+
labels: enhancement
|
| 6 |
+
assignees: ''
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
**你的功能请求是否与某个问题有关?**
|
| 11 |
+
请尽量清晰精准地描述你遇到的问题。例如:我家运营商限制 UDP 协议速度,导致 Hysteria 很慢,希望增加 FakeTCP 支持。
|
| 12 |
+
|
| 13 |
+
**描述你希望的解决方案**
|
| 14 |
+
请尽量清晰精准地描述你希望的解决方案。
|
| 15 |
+
|
| 16 |
+
**有没有其他替代方案**
|
| 17 |
+
请尽量清晰精准地描述你认为可能的替代方案。
|
| 18 |
+
|
| 19 |
+
**额外信息**
|
| 20 |
+
其他你认为有助于开发者了解你需求的信息。
|
.github/dependabot.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: 2
|
| 2 |
+
updates:
|
| 3 |
+
- package-ecosystem: "gomod"
|
| 4 |
+
directory: "/"
|
| 5 |
+
schedule:
|
| 6 |
+
interval: "daily"
|
| 7 |
+
- package-ecosystem: "github-actions"
|
| 8 |
+
directory: "/"
|
| 9 |
+
schedule:
|
| 10 |
+
interval: "daily"
|
.github/workflows/autotag.yaml
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: "Create release tags for nested modules"
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
tags:
|
| 6 |
+
- app/v*.*.*
|
| 7 |
+
|
| 8 |
+
permissions:
|
| 9 |
+
contents: write
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
tag:
|
| 13 |
+
name: "Create tags"
|
| 14 |
+
runs-on: ubuntu-latest
|
| 15 |
+
steps:
|
| 16 |
+
- name: "Extract tagbase"
|
| 17 |
+
id: extract_tagbase
|
| 18 |
+
uses: actions/github-script@v7
|
| 19 |
+
with:
|
| 20 |
+
script: |
|
| 21 |
+
const ref = context.ref;
|
| 22 |
+
core.info(`context.ref: ${ref}`);
|
| 23 |
+
const refPrefix = 'refs/tags/app/';
|
| 24 |
+
if (!ref.startsWith(refPrefix)) {
|
| 25 |
+
core.setFailed(`context.ref does not start with ${refPrefix}: ${ref}`);
|
| 26 |
+
return;
|
| 27 |
+
}
|
| 28 |
+
const tagbase = ref.slice(refPrefix.length);
|
| 29 |
+
core.info(`tagbase: ${tagbase}`);
|
| 30 |
+
core.setOutput('tagbase', tagbase);
|
| 31 |
+
|
| 32 |
+
- name: "Tagging core/*"
|
| 33 |
+
uses: actions/github-script@v7
|
| 34 |
+
env:
|
| 35 |
+
INPUT_TAGPREFIX: "core/"
|
| 36 |
+
INPUT_TAGBASE: ${{ steps.extract_tagbase.outputs.tagbase }}
|
| 37 |
+
with:
|
| 38 |
+
script: |
|
| 39 |
+
const tagbase = core.getInput('tagbase', { required: true });
|
| 40 |
+
const tagprefix = core.getInput('tagprefix', { required: true });
|
| 41 |
+
const refname = `tags/${tagprefix}${tagbase}`;
|
| 42 |
+
core.info(`creating ref ${refname}`);
|
| 43 |
+
try {
|
| 44 |
+
await github.rest.git.createRef({
|
| 45 |
+
owner: context.repo.owner,
|
| 46 |
+
repo: context.repo.repo,
|
| 47 |
+
ref: `refs/${refname}`,
|
| 48 |
+
sha: context.sha
|
| 49 |
+
});
|
| 50 |
+
core.info(`created ref ${refname}`);
|
| 51 |
+
return;
|
| 52 |
+
} catch (error) {
|
| 53 |
+
core.info(`failed to create ref ${refname}: ${error}`);
|
| 54 |
+
}
|
| 55 |
+
core.info(`updating ref ${refname}`)
|
| 56 |
+
try {
|
| 57 |
+
await github.rest.git.updateRef({
|
| 58 |
+
owner: context.repo.owner,
|
| 59 |
+
repo: context.repo.repo,
|
| 60 |
+
ref: refname,
|
| 61 |
+
sha: context.sha
|
| 62 |
+
});
|
| 63 |
+
core.info(`updated ref ${refname}`);
|
| 64 |
+
return;
|
| 65 |
+
} catch (error) {
|
| 66 |
+
core.setFailed(`failed to update ref ${refname}: ${error}`);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
- name: "Tagging extras/*"
|
| 70 |
+
uses: actions/github-script@v7
|
| 71 |
+
env:
|
| 72 |
+
INPUT_TAGPREFIX: "extras/"
|
| 73 |
+
INPUT_TAGBASE: ${{ steps.extract_tagbase.outputs.tagbase }}
|
| 74 |
+
with:
|
| 75 |
+
script: |
|
| 76 |
+
const tagbase = core.getInput('tagbase', { required: true });
|
| 77 |
+
const tagprefix = core.getInput('tagprefix', { required: true });
|
| 78 |
+
const refname = `tags/${tagprefix}${tagbase}`;
|
| 79 |
+
core.info(`creating ref ${refname}`);
|
| 80 |
+
try {
|
| 81 |
+
await github.rest.git.createRef({
|
| 82 |
+
owner: context.repo.owner,
|
| 83 |
+
repo: context.repo.repo,
|
| 84 |
+
ref: `refs/${refname}`,
|
| 85 |
+
sha: context.sha
|
| 86 |
+
});
|
| 87 |
+
core.info(`created ref ${refname}`);
|
| 88 |
+
return;
|
| 89 |
+
} catch (error) {
|
| 90 |
+
core.info(`failed to create ref ${refname}: ${error}`);
|
| 91 |
+
}
|
| 92 |
+
core.info(`updating ref ${refname}`)
|
| 93 |
+
try {
|
| 94 |
+
await github.rest.git.updateRef({
|
| 95 |
+
owner: context.repo.owner,
|
| 96 |
+
repo: context.repo.repo,
|
| 97 |
+
ref: refname,
|
| 98 |
+
sha: context.sha
|
| 99 |
+
});
|
| 100 |
+
core.info(`updated ref ${refname}`);
|
| 101 |
+
return;
|
| 102 |
+
} catch (error) {
|
| 103 |
+
core.setFailed(`failed to update ref ${refname}: ${error}`);
|
| 104 |
+
}
|
.github/workflows/docker.yml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: "Build Docker Image"
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
tags:
|
| 6 |
+
- app/v*.*.*
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
docker:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
env:
|
| 12 |
+
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
| 13 |
+
|
| 14 |
+
steps:
|
| 15 |
+
- name: Check out
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
|
| 18 |
+
- name: Get version
|
| 19 |
+
id: get_version
|
| 20 |
+
run: echo "version=$(git describe --tags --always --match 'app/v*' | sed -n 's|app/\([^/-]*\)\(-.*\)\{0,1\}|\1|p')" >> $GITHUB_OUTPUT
|
| 21 |
+
|
| 22 |
+
- name: Set up QEMU
|
| 23 |
+
uses: docker/setup-qemu-action@v3
|
| 24 |
+
|
| 25 |
+
- name: Set up Docker Buildx
|
| 26 |
+
uses: docker/setup-buildx-action@v3
|
| 27 |
+
|
| 28 |
+
- name: Login to DockerHub
|
| 29 |
+
uses: docker/login-action@v3
|
| 30 |
+
with:
|
| 31 |
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
| 32 |
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
| 33 |
+
|
| 34 |
+
- name: Build and push
|
| 35 |
+
id: docker_build
|
| 36 |
+
uses: docker/build-push-action@v5
|
| 37 |
+
with:
|
| 38 |
+
context: .
|
| 39 |
+
push: true
|
| 40 |
+
platforms: linux/amd64,linux/arm64
|
| 41 |
+
tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:v2,tobyxdd/hysteria:${{ steps.get_version.outputs.version }}
|
| 42 |
+
|
| 43 |
+
- name: Image digest
|
| 44 |
+
run: echo ${{ steps.docker_build.outputs.digest }}
|
.github/workflows/master.yml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: "Build master branch"
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- master
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
build:
|
| 10 |
+
name: Build
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
env:
|
| 13 |
+
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
| 14 |
+
|
| 15 |
+
steps:
|
| 16 |
+
- name: Check out
|
| 17 |
+
uses: actions/checkout@v4
|
| 18 |
+
|
| 19 |
+
- name: Setup Go
|
| 20 |
+
uses: actions/setup-go@v5
|
| 21 |
+
with:
|
| 22 |
+
go-version: "1.23"
|
| 23 |
+
|
| 24 |
+
- name: Setup Python # This is for the build script
|
| 25 |
+
uses: actions/setup-python@v5
|
| 26 |
+
with:
|
| 27 |
+
python-version: "3.11"
|
| 28 |
+
|
| 29 |
+
- uses: nttld/setup-ndk@v1
|
| 30 |
+
id: setup-ndk
|
| 31 |
+
with:
|
| 32 |
+
ndk-version: r26b
|
| 33 |
+
add-to-path: false
|
| 34 |
+
|
| 35 |
+
- name: Run build script
|
| 36 |
+
env:
|
| 37 |
+
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
| 38 |
+
run: |
|
| 39 |
+
export HY_APP_PLATFORMS=$(sed 's/\r$//' platforms.txt | awk '!/^#/ && !/^$/' | paste -sd ",")
|
| 40 |
+
python hyperbole.py build -r
|
| 41 |
+
|
| 42 |
+
- name: Generate hashes
|
| 43 |
+
run: |
|
| 44 |
+
for file in build/*; do
|
| 45 |
+
sha256sum $file >> build/hashes.txt
|
| 46 |
+
done
|
| 47 |
+
|
| 48 |
+
- name: Archive
|
| 49 |
+
uses: actions/upload-artifact@v4
|
| 50 |
+
with:
|
| 51 |
+
name: hysteria-master-${{ github.sha }}
|
| 52 |
+
path: build
|
.github/workflows/release.yml
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: "Build release"
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
tags:
|
| 6 |
+
- app/v*.*.*
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
build:
|
| 10 |
+
name: Build
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
env:
|
| 13 |
+
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
| 14 |
+
|
| 15 |
+
steps:
|
| 16 |
+
- name: Check out
|
| 17 |
+
uses: actions/checkout@v4
|
| 18 |
+
|
| 19 |
+
- name: Get version
|
| 20 |
+
id: get_version
|
| 21 |
+
run: echo "version=$(git describe --tags --always --match 'app/v*' | sed -n 's|app/\([^/-]*\)\(-.*\)\{0,1\}|\1|p')" >> $GITHUB_OUTPUT
|
| 22 |
+
|
| 23 |
+
- name: Setup Go
|
| 24 |
+
uses: actions/setup-go@v5
|
| 25 |
+
with:
|
| 26 |
+
go-version: "1.23"
|
| 27 |
+
|
| 28 |
+
- name: Setup Python # This is for the build script
|
| 29 |
+
uses: actions/setup-python@v5
|
| 30 |
+
with:
|
| 31 |
+
python-version: "3.11"
|
| 32 |
+
|
| 33 |
+
- uses: nttld/setup-ndk@v1
|
| 34 |
+
id: setup-ndk
|
| 35 |
+
with:
|
| 36 |
+
ndk-version: r26b
|
| 37 |
+
add-to-path: false
|
| 38 |
+
|
| 39 |
+
- name: Run build script
|
| 40 |
+
env:
|
| 41 |
+
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
| 42 |
+
run: |
|
| 43 |
+
export HY_APP_PLATFORMS=$(sed 's/\r$//' platforms.txt | awk '!/^#/ && !/^$/' | paste -sd ",")
|
| 44 |
+
python hyperbole.py build -r
|
| 45 |
+
|
| 46 |
+
- name: Generate hashes
|
| 47 |
+
run: |
|
| 48 |
+
for file in build/*; do
|
| 49 |
+
sha256sum $file >> build/hashes.txt
|
| 50 |
+
done
|
| 51 |
+
|
| 52 |
+
- name: Upload GitHub
|
| 53 |
+
uses: softprops/action-gh-release@v2
|
| 54 |
+
with:
|
| 55 |
+
files: build/*
|
| 56 |
+
|
| 57 |
+
- name: Upload CF bucket
|
| 58 |
+
uses: shallwefootball/upload-s3-action@v1.3.3
|
| 59 |
+
with:
|
| 60 |
+
aws_key_id: ${{ secrets.CF_KEY_ID }}
|
| 61 |
+
aws_secret_access_key: ${{ secrets.CF_KEY }}
|
| 62 |
+
aws_bucket: "hydownload"
|
| 63 |
+
endpoint: "https://bea223c61d5a41250d127bd67f51dfec.r2.cloudflarestorage.com/"
|
| 64 |
+
source_dir: "build"
|
| 65 |
+
destination_dir: "app/${{ steps.get_version.outputs.version }}"
|
| 66 |
+
|
| 67 |
+
- name: Publish to API
|
| 68 |
+
run: |
|
| 69 |
+
export HY_API_POST_KEY=${{ secrets.HY2_API_POST_KEY }}
|
| 70 |
+
pip install requests
|
| 71 |
+
python hyperbole.py publish
|
.github/workflows/scripts.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: "Publish scripts"
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- master
|
| 7 |
+
paths:
|
| 8 |
+
- scripts/**
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
publish:
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
permissions:
|
| 14 |
+
contents: read
|
| 15 |
+
deployments: write
|
| 16 |
+
name: Publish scripts to Cloudflare Pages
|
| 17 |
+
steps:
|
| 18 |
+
- name: Check out
|
| 19 |
+
uses: actions/checkout@v4
|
| 20 |
+
|
| 21 |
+
- name: Publish to Cloudflare Pages
|
| 22 |
+
uses: cloudflare/pages-action@v1
|
| 23 |
+
with:
|
| 24 |
+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
| 25 |
+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
| 26 |
+
projectName: hy2scripts
|
| 27 |
+
directory: scripts
|
| 28 |
+
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
| 29 |
+
branch: main
|
.gitignore
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Created by https://www.toptal.com/developers/gitignore/api/goland+all,intellij+all,go,windows,linux,macos,python,pycharm+all
|
| 2 |
+
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,intellij+all,go,windows,linux,macos,python,pycharm+all
|
| 3 |
+
|
| 4 |
+
### Go ###
|
| 5 |
+
# If you prefer the allow list template instead of the deny list, see community template:
|
| 6 |
+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
| 7 |
+
#
|
| 8 |
+
# Binaries for programs and plugins
|
| 9 |
+
*.exe
|
| 10 |
+
*.exe~
|
| 11 |
+
*.dll
|
| 12 |
+
*.so
|
| 13 |
+
*.dylib
|
| 14 |
+
|
| 15 |
+
# Test binary, built with `go test -c`
|
| 16 |
+
*.test
|
| 17 |
+
|
| 18 |
+
# Output of the go coverage tool, specifically when used with LiteIDE
|
| 19 |
+
*.out
|
| 20 |
+
|
| 21 |
+
# Dependency directories (remove the comment below to include it)
|
| 22 |
+
# vendor/
|
| 23 |
+
|
| 24 |
+
# Go workspace file
|
| 25 |
+
go.work
|
| 26 |
+
|
| 27 |
+
### GoLand+all ###
|
| 28 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
| 29 |
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
| 30 |
+
|
| 31 |
+
# User-specific stuff
|
| 32 |
+
.idea/**/workspace.xml
|
| 33 |
+
.idea/**/tasks.xml
|
| 34 |
+
.idea/**/usage.statistics.xml
|
| 35 |
+
.idea/**/dictionaries
|
| 36 |
+
.idea/**/shelf
|
| 37 |
+
|
| 38 |
+
# AWS User-specific
|
| 39 |
+
.idea/**/aws.xml
|
| 40 |
+
|
| 41 |
+
# Generated files
|
| 42 |
+
.idea/**/contentModel.xml
|
| 43 |
+
|
| 44 |
+
# Sensitive or high-churn files
|
| 45 |
+
.idea/**/dataSources/
|
| 46 |
+
.idea/**/dataSources.ids
|
| 47 |
+
.idea/**/dataSources.local.xml
|
| 48 |
+
.idea/**/sqlDataSources.xml
|
| 49 |
+
.idea/**/dynamic.xml
|
| 50 |
+
.idea/**/uiDesigner.xml
|
| 51 |
+
.idea/**/dbnavigator.xml
|
| 52 |
+
|
| 53 |
+
# Gradle
|
| 54 |
+
.idea/**/gradle.xml
|
| 55 |
+
.idea/**/libraries
|
| 56 |
+
|
| 57 |
+
# Gradle and Maven with auto-import
|
| 58 |
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
| 59 |
+
# since they will be recreated, and may cause churn. Uncomment if using
|
| 60 |
+
# auto-import.
|
| 61 |
+
# .idea/artifacts
|
| 62 |
+
# .idea/compiler.xml
|
| 63 |
+
# .idea/jarRepositories.xml
|
| 64 |
+
# .idea/modules.xml
|
| 65 |
+
# .idea/*.iml
|
| 66 |
+
# .idea/modules
|
| 67 |
+
# *.iml
|
| 68 |
+
# *.ipr
|
| 69 |
+
|
| 70 |
+
# CMake
|
| 71 |
+
cmake-build-*/
|
| 72 |
+
|
| 73 |
+
# Mongo Explorer plugin
|
| 74 |
+
.idea/**/mongoSettings.xml
|
| 75 |
+
|
| 76 |
+
# File-based project format
|
| 77 |
+
*.iws
|
| 78 |
+
|
| 79 |
+
# IntelliJ
|
| 80 |
+
out/
|
| 81 |
+
|
| 82 |
+
# mpeltonen/sbt-idea plugin
|
| 83 |
+
.idea_modules/
|
| 84 |
+
|
| 85 |
+
# JIRA plugin
|
| 86 |
+
atlassian-ide-plugin.xml
|
| 87 |
+
|
| 88 |
+
# Cursive Clojure plugin
|
| 89 |
+
.idea/replstate.xml
|
| 90 |
+
|
| 91 |
+
# SonarLint plugin
|
| 92 |
+
.idea/sonarlint/
|
| 93 |
+
|
| 94 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
| 95 |
+
com_crashlytics_export_strings.xml
|
| 96 |
+
crashlytics.properties
|
| 97 |
+
crashlytics-build.properties
|
| 98 |
+
fabric.properties
|
| 99 |
+
|
| 100 |
+
# Editor-based Rest Client
|
| 101 |
+
.idea/httpRequests
|
| 102 |
+
|
| 103 |
+
# Android studio 3.1+ serialized cache file
|
| 104 |
+
.idea/caches/build_file_checksums.ser
|
| 105 |
+
|
| 106 |
+
### GoLand+all Patch ###
|
| 107 |
+
# Ignore everything but code style settings and run configurations
|
| 108 |
+
# that are supposed to be shared within teams.
|
| 109 |
+
|
| 110 |
+
.idea/*
|
| 111 |
+
|
| 112 |
+
!.idea/codeStyles
|
| 113 |
+
!.idea/runConfigurations
|
| 114 |
+
|
| 115 |
+
### Intellij+all ###
|
| 116 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
| 117 |
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
| 118 |
+
|
| 119 |
+
# User-specific stuff
|
| 120 |
+
|
| 121 |
+
# AWS User-specific
|
| 122 |
+
|
| 123 |
+
# Generated files
|
| 124 |
+
|
| 125 |
+
# Sensitive or high-churn files
|
| 126 |
+
|
| 127 |
+
# Gradle
|
| 128 |
+
|
| 129 |
+
# Gradle and Maven with auto-import
|
| 130 |
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
| 131 |
+
# since they will be recreated, and may cause churn. Uncomment if using
|
| 132 |
+
# auto-import.
|
| 133 |
+
# .idea/artifacts
|
| 134 |
+
# .idea/compiler.xml
|
| 135 |
+
# .idea/jarRepositories.xml
|
| 136 |
+
# .idea/modules.xml
|
| 137 |
+
# .idea/*.iml
|
| 138 |
+
# .idea/modules
|
| 139 |
+
# *.iml
|
| 140 |
+
# *.ipr
|
| 141 |
+
|
| 142 |
+
# CMake
|
| 143 |
+
|
| 144 |
+
# Mongo Explorer plugin
|
| 145 |
+
|
| 146 |
+
# File-based project format
|
| 147 |
+
|
| 148 |
+
# IntelliJ
|
| 149 |
+
|
| 150 |
+
# mpeltonen/sbt-idea plugin
|
| 151 |
+
|
| 152 |
+
# JIRA plugin
|
| 153 |
+
|
| 154 |
+
# Cursive Clojure plugin
|
| 155 |
+
|
| 156 |
+
# SonarLint plugin
|
| 157 |
+
|
| 158 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
| 159 |
+
|
| 160 |
+
# Editor-based Rest Client
|
| 161 |
+
|
| 162 |
+
# Android studio 3.1+ serialized cache file
|
| 163 |
+
|
| 164 |
+
### Intellij+all Patch ###
|
| 165 |
+
# Ignore everything but code style settings and run configurations
|
| 166 |
+
# that are supposed to be shared within teams.
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
### Linux ###
|
| 171 |
+
*~
|
| 172 |
+
|
| 173 |
+
# temporary files which can be created if a process still has a handle open of a deleted file
|
| 174 |
+
.fuse_hidden*
|
| 175 |
+
|
| 176 |
+
# KDE directory preferences
|
| 177 |
+
.directory
|
| 178 |
+
|
| 179 |
+
# Linux trash folder which might appear on any partition or disk
|
| 180 |
+
.Trash-*
|
| 181 |
+
|
| 182 |
+
# .nfs files are created when an open file is removed but is still being accessed
|
| 183 |
+
.nfs*
|
| 184 |
+
|
| 185 |
+
### macOS ###
|
| 186 |
+
# General
|
| 187 |
+
.DS_Store
|
| 188 |
+
.AppleDouble
|
| 189 |
+
.LSOverride
|
| 190 |
+
|
| 191 |
+
# Icon must end with two \r
|
| 192 |
+
Icon
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# Thumbnails
|
| 196 |
+
._*
|
| 197 |
+
|
| 198 |
+
# Files that might appear in the root of a volume
|
| 199 |
+
.DocumentRevisions-V100
|
| 200 |
+
.fseventsd
|
| 201 |
+
.Spotlight-V100
|
| 202 |
+
.TemporaryItems
|
| 203 |
+
.Trashes
|
| 204 |
+
.VolumeIcon.icns
|
| 205 |
+
.com.apple.timemachine.donotpresent
|
| 206 |
+
|
| 207 |
+
# Directories potentially created on remote AFP share
|
| 208 |
+
.AppleDB
|
| 209 |
+
.AppleDesktop
|
| 210 |
+
Network Trash Folder
|
| 211 |
+
Temporary Items
|
| 212 |
+
.apdisk
|
| 213 |
+
|
| 214 |
+
### macOS Patch ###
|
| 215 |
+
# iCloud generated files
|
| 216 |
+
*.icloud
|
| 217 |
+
|
| 218 |
+
### PyCharm+all ###
|
| 219 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
| 220 |
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
| 221 |
+
|
| 222 |
+
# User-specific stuff
|
| 223 |
+
|
| 224 |
+
# AWS User-specific
|
| 225 |
+
|
| 226 |
+
# Generated files
|
| 227 |
+
|
| 228 |
+
# Sensitive or high-churn files
|
| 229 |
+
|
| 230 |
+
# Gradle
|
| 231 |
+
|
| 232 |
+
# Gradle and Maven with auto-import
|
| 233 |
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
| 234 |
+
# since they will be recreated, and may cause churn. Uncomment if using
|
| 235 |
+
# auto-import.
|
| 236 |
+
# .idea/artifacts
|
| 237 |
+
# .idea/compiler.xml
|
| 238 |
+
# .idea/jarRepositories.xml
|
| 239 |
+
# .idea/modules.xml
|
| 240 |
+
# .idea/*.iml
|
| 241 |
+
# .idea/modules
|
| 242 |
+
# *.iml
|
| 243 |
+
# *.ipr
|
| 244 |
+
|
| 245 |
+
# CMake
|
| 246 |
+
|
| 247 |
+
# Mongo Explorer plugin
|
| 248 |
+
|
| 249 |
+
# File-based project format
|
| 250 |
+
|
| 251 |
+
# IntelliJ
|
| 252 |
+
|
| 253 |
+
# mpeltonen/sbt-idea plugin
|
| 254 |
+
|
| 255 |
+
# JIRA plugin
|
| 256 |
+
|
| 257 |
+
# Cursive Clojure plugin
|
| 258 |
+
|
| 259 |
+
# SonarLint plugin
|
| 260 |
+
|
| 261 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
| 262 |
+
|
| 263 |
+
# Editor-based Rest Client
|
| 264 |
+
|
| 265 |
+
# Android studio 3.1+ serialized cache file
|
| 266 |
+
|
| 267 |
+
### PyCharm+all Patch ###
|
| 268 |
+
# Ignore everything but code style settings and run configurations
|
| 269 |
+
# that are supposed to be shared within teams.
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
### Python ###
|
| 274 |
+
# Byte-compiled / optimized / DLL files
|
| 275 |
+
__pycache__/
|
| 276 |
+
*.py[cod]
|
| 277 |
+
*$py.class
|
| 278 |
+
|
| 279 |
+
# C extensions
|
| 280 |
+
|
| 281 |
+
# Distribution / packaging
|
| 282 |
+
.Python
|
| 283 |
+
build/
|
| 284 |
+
develop-eggs/
|
| 285 |
+
dist/
|
| 286 |
+
downloads/
|
| 287 |
+
eggs/
|
| 288 |
+
.eggs/
|
| 289 |
+
lib/
|
| 290 |
+
lib64/
|
| 291 |
+
parts/
|
| 292 |
+
sdist/
|
| 293 |
+
var/
|
| 294 |
+
wheels/
|
| 295 |
+
share/python-wheels/
|
| 296 |
+
*.egg-info/
|
| 297 |
+
.installed.cfg
|
| 298 |
+
*.egg
|
| 299 |
+
MANIFEST
|
| 300 |
+
|
| 301 |
+
# PyInstaller
|
| 302 |
+
# Usually these files are written by a python script from a template
|
| 303 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 304 |
+
*.manifest
|
| 305 |
+
*.spec
|
| 306 |
+
|
| 307 |
+
# Installer logs
|
| 308 |
+
pip-log.txt
|
| 309 |
+
pip-delete-this-directory.txt
|
| 310 |
+
|
| 311 |
+
# Unit test / coverage reports
|
| 312 |
+
htmlcov/
|
| 313 |
+
.tox/
|
| 314 |
+
.nox/
|
| 315 |
+
.coverage
|
| 316 |
+
.coverage.*
|
| 317 |
+
.cache
|
| 318 |
+
nosetests.xml
|
| 319 |
+
coverage.xml
|
| 320 |
+
*.cover
|
| 321 |
+
*.py,cover
|
| 322 |
+
.hypothesis/
|
| 323 |
+
.pytest_cache/
|
| 324 |
+
cover/
|
| 325 |
+
|
| 326 |
+
# Translations
|
| 327 |
+
*.mo
|
| 328 |
+
*.pot
|
| 329 |
+
|
| 330 |
+
# Django stuff:
|
| 331 |
+
*.log
|
| 332 |
+
local_settings.py
|
| 333 |
+
db.sqlite3
|
| 334 |
+
db.sqlite3-journal
|
| 335 |
+
|
| 336 |
+
# Flask stuff:
|
| 337 |
+
instance/
|
| 338 |
+
.webassets-cache
|
| 339 |
+
|
| 340 |
+
# Scrapy stuff:
|
| 341 |
+
.scrapy
|
| 342 |
+
|
| 343 |
+
# Sphinx documentation
|
| 344 |
+
docs/_build/
|
| 345 |
+
|
| 346 |
+
# PyBuilder
|
| 347 |
+
.pybuilder/
|
| 348 |
+
target/
|
| 349 |
+
|
| 350 |
+
# Jupyter Notebook
|
| 351 |
+
.ipynb_checkpoints
|
| 352 |
+
|
| 353 |
+
# IPython
|
| 354 |
+
profile_default/
|
| 355 |
+
ipython_config.py
|
| 356 |
+
|
| 357 |
+
# pyenv
|
| 358 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 359 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 360 |
+
# .python-version
|
| 361 |
+
|
| 362 |
+
# pipenv
|
| 363 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 364 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 365 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 366 |
+
# install all needed dependencies.
|
| 367 |
+
#Pipfile.lock
|
| 368 |
+
|
| 369 |
+
# poetry
|
| 370 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 371 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 372 |
+
# commonly ignored for libraries.
|
| 373 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 374 |
+
#poetry.lock
|
| 375 |
+
|
| 376 |
+
# pdm
|
| 377 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 378 |
+
#pdm.lock
|
| 379 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 380 |
+
# in version control.
|
| 381 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 382 |
+
.pdm.toml
|
| 383 |
+
|
| 384 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 385 |
+
__pypackages__/
|
| 386 |
+
|
| 387 |
+
# Celery stuff
|
| 388 |
+
celerybeat-schedule
|
| 389 |
+
celerybeat.pid
|
| 390 |
+
|
| 391 |
+
# SageMath parsed files
|
| 392 |
+
*.sage.py
|
| 393 |
+
|
| 394 |
+
# Environments
|
| 395 |
+
.env
|
| 396 |
+
.venv
|
| 397 |
+
env/
|
| 398 |
+
venv/
|
| 399 |
+
ENV/
|
| 400 |
+
env.bak/
|
| 401 |
+
venv.bak/
|
| 402 |
+
|
| 403 |
+
# Spyder project settings
|
| 404 |
+
.spyderproject
|
| 405 |
+
.spyproject
|
| 406 |
+
|
| 407 |
+
# Rope project settings
|
| 408 |
+
.ropeproject
|
| 409 |
+
|
| 410 |
+
# mkdocs documentation
|
| 411 |
+
/site
|
| 412 |
+
|
| 413 |
+
# mypy
|
| 414 |
+
.mypy_cache/
|
| 415 |
+
.dmypy.json
|
| 416 |
+
dmypy.json
|
| 417 |
+
|
| 418 |
+
# Pyre type checker
|
| 419 |
+
.pyre/
|
| 420 |
+
|
| 421 |
+
# pytype static type analyzer
|
| 422 |
+
.pytype/
|
| 423 |
+
|
| 424 |
+
# Cython debug symbols
|
| 425 |
+
cython_debug/
|
| 426 |
+
|
| 427 |
+
# PyCharm
|
| 428 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 429 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 430 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 431 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 432 |
+
#.idea/
|
| 433 |
+
|
| 434 |
+
### Python Patch ###
|
| 435 |
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
| 436 |
+
poetry.toml
|
| 437 |
+
|
| 438 |
+
# ruff
|
| 439 |
+
.ruff_cache/
|
| 440 |
+
|
| 441 |
+
# LSP config files
|
| 442 |
+
pyrightconfig.json
|
| 443 |
+
|
| 444 |
+
### Windows ###
|
| 445 |
+
# Windows thumbnail cache files
|
| 446 |
+
Thumbs.db
|
| 447 |
+
Thumbs.db:encryptable
|
| 448 |
+
ehthumbs.db
|
| 449 |
+
ehthumbs_vista.db
|
| 450 |
+
|
| 451 |
+
# Dump file
|
| 452 |
+
*.stackdump
|
| 453 |
+
|
| 454 |
+
# Folder config file
|
| 455 |
+
[Dd]esktop.ini
|
| 456 |
+
|
| 457 |
+
# Recycle Bin used on file shares
|
| 458 |
+
$RECYCLE.BIN/
|
| 459 |
+
|
| 460 |
+
# Windows Installer files
|
| 461 |
+
*.cab
|
| 462 |
+
*.msi
|
| 463 |
+
*.msix
|
| 464 |
+
*.msm
|
| 465 |
+
*.msp
|
| 466 |
+
|
| 467 |
+
# Windows shortcuts
|
| 468 |
+
*.lnk
|
| 469 |
+
|
| 470 |
+
# End of https://www.toptal.com/developers/gitignore/api/goland+all,intellij+all,go,windows,linux,macos,python,pycharm+all
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
https://v2.hysteria.network/docs/Changelog/
|
Dockerfile
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM golang:1-alpine AS builder
|
| 2 |
+
|
| 3 |
+
# GOPROXY is disabled by default, use:
|
| 4 |
+
# docker build --build-arg GOPROXY="https://goproxy.io" ...
|
| 5 |
+
# to enable GOPROXY.
|
| 6 |
+
ARG GOPROXY=""
|
| 7 |
+
|
| 8 |
+
ENV GOPROXY ${GOPROXY}
|
| 9 |
+
|
| 10 |
+
COPY . /go/src/github.com/apernet/hysteria
|
| 11 |
+
|
| 12 |
+
WORKDIR /go/src/github.com/apernet/hysteria
|
| 13 |
+
|
| 14 |
+
RUN set -ex \
|
| 15 |
+
&& apk add git build-base bash python3 \
|
| 16 |
+
&& python hyperbole.py build -r \
|
| 17 |
+
&& mv ./build/hysteria-* /go/bin/hysteria
|
| 18 |
+
|
| 19 |
+
# multi-stage builds to create the final image
|
| 20 |
+
FROM alpine AS dist
|
| 21 |
+
|
| 22 |
+
# set up nsswitch.conf for Go's "netgo" implementation
|
| 23 |
+
# - https://github.com/golang/go/blob/go1.9.1/src/net/conf.go#L194-L275
|
| 24 |
+
# - docker run --rm debian:stretch grep '^hosts:' /etc/nsswitch.conf
|
| 25 |
+
RUN if [ ! -e /etc/nsswitch.conf ]; then echo 'hosts: files dns' > /etc/nsswitch.conf; fi
|
| 26 |
+
|
| 27 |
+
# bash is used for debugging, tzdata is used to add timezone information.
|
| 28 |
+
# Install ca-certificates to ensure no CA certificate errors.
|
| 29 |
+
#
|
| 30 |
+
# Do not try to add the "--no-cache" option when there are multiple "apk"
|
| 31 |
+
# commands, this will cause the build process to become very slow.
|
| 32 |
+
RUN set -ex \
|
| 33 |
+
&& apk upgrade \
|
| 34 |
+
&& apk add bash tzdata ca-certificates \
|
| 35 |
+
&& rm -rf /var/cache/apk/*
|
| 36 |
+
|
| 37 |
+
COPY --from=builder /go/bin/hysteria /usr/local/bin/hysteria
|
| 38 |
+
|
| 39 |
+
ENTRYPOINT ["hysteria"]
|
LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright 2023 Toby
|
| 2 |
+
|
| 3 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
| 4 |
+
|
| 5 |
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 6 |
+
|
| 7 |
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
PROTOCOL.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hysteria 2 Protocol Specification
|
| 2 |
+
|
| 3 |
+
Hysteria is a TCP & UDP proxy based on QUIC, designed for speed, security and censorship resistance. This document describes the protocol used by Hysteria starting with version 2.0.0, sometimes internally referred to as the "v4" protocol. From here on, we will call it "the protocol" or "the Hysteria protocol".
|
| 4 |
+
|
| 5 |
+
## Requirements Language
|
| 6 |
+
|
| 7 |
+
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
|
| 8 |
+
|
| 9 |
+
## Underlying Protocol & Wire Format
|
| 10 |
+
|
| 11 |
+
The Hysteria protocol MUST be implemented on top of the standard QUIC transport protocol [RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000) with [Unreliable Datagram Extension](https://datatracker.ietf.org/doc/rfc9221/).
|
| 12 |
+
|
| 13 |
+
All multibyte numbers use Big Endian format.
|
| 14 |
+
|
| 15 |
+
All variable-length integers ("varints") are encoded/decoded as defined in QUIC (RFC 9000).
|
| 16 |
+
|
| 17 |
+
## Authentication & HTTP/3 masquerading
|
| 18 |
+
|
| 19 |
+
One of the key features of the Hysteria protocol is that to a third party without proper authentication credentials (whether it's a middleman or an active prober), a Hysteria proxy server behaves just like a standard HTTP/3 web server. Additionally, the encrypted traffic between the client and the server appears indistinguishable from normal HTTP/3 traffic.
|
| 20 |
+
|
| 21 |
+
Therefore, a Hysteria server MUST implement an HTTP/3 server (as defined by [RFC 9114](https://datatracker.ietf.org/doc/rfc9114/)) and handle HTTP requests as any standard web server would. To prevent active probers from detecting common response patterns in Hysteria servers, implementations SHOULD advise users to either host actual content or set it up as a reverse proxy for other sites.
|
| 22 |
+
|
| 23 |
+
An actual Hysteria client, upon connection, MUST send the following HTTP/3 request to the server:
|
| 24 |
+
|
| 25 |
+
```
|
| 26 |
+
:method: POST
|
| 27 |
+
:path: /auth
|
| 28 |
+
:host: hysteria
|
| 29 |
+
Hysteria-Auth: [string]
|
| 30 |
+
Hysteria-CC-RX: [uint]
|
| 31 |
+
Hysteria-Padding: [string]
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
`Hysteria-Auth`: Authentication credentials.
|
| 35 |
+
|
| 36 |
+
`Hysteria-CC-RX`: Client's maximum receive rate in bytes per second. A value of 0 indicates unknown.
|
| 37 |
+
|
| 38 |
+
`Hysteria-Padding`: A random padding string of variable length.
|
| 39 |
+
|
| 40 |
+
The Hysteria server MUST identify this special request, and, instead of attempting to serve content or forwarding it to an upstream site, it MUST authenticate the client using the provided information. If authentication is successful, the server MUST send the following response (HTTP status code 233):
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
:status: 233 HyOK
|
| 44 |
+
Hysteria-UDP: [true/false]
|
| 45 |
+
Hysteria-CC-RX: [uint/"auto"]
|
| 46 |
+
Hysteria-Padding: [string]
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
`Hysteria-UDP`: Whether the server supports UDP relay.
|
| 50 |
+
|
| 51 |
+
`Hysteria-CC-RX`: Server's maximum receive rate in bytes per second. A value of 0 indicates unlimited; "auto" indicates the server refuses to provide a value and ask the client to use congestion control to determine the rate on its own.
|
| 52 |
+
|
| 53 |
+
`Hysteria-Padding`: A random padding string of variable length.
|
| 54 |
+
|
| 55 |
+
See the Congestion Control section for more information on how to use the `Hysteria-CC-RX` values.
|
| 56 |
+
|
| 57 |
+
`Hysteria-Padding` is optional and is only intended to obfuscate the request/response pattern. It SHOULD be ignored by both sides.
|
| 58 |
+
|
| 59 |
+
If authentication fails, the server MUST either act like a standard web server that does not understand the request, or in the case of being a reverse proxy, forward the request to the upstream site and return the response to the client.
|
| 60 |
+
|
| 61 |
+
The client MUST check the status code to determine if the authentication was successful. If the status code is anything other than 233, the client MUST consider authentication to have failed and disconnect from the server.
|
| 62 |
+
|
| 63 |
+
After (and only after) a client passes authentication, the server MUST consider this QUIC connection to be a Hysteria proxy connection. It MUST then start processing proxy requests from the client as described in the next section.
|
| 64 |
+
|
| 65 |
+
## Proxy Requests
|
| 66 |
+
|
| 67 |
+
### TCP
|
| 68 |
+
|
| 69 |
+
For each TCP connection, the client MUST create a new QUIC bidirectional stream and send the following TCPRequest message:
|
| 70 |
+
|
| 71 |
+
```
|
| 72 |
+
[varint] 0x401 (TCPRequest ID)
|
| 73 |
+
[varint] Address length
|
| 74 |
+
[bytes] Address string (host:port)
|
| 75 |
+
[varint] Padding length
|
| 76 |
+
[bytes] Random padding
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
The server MUST respond with a TCPResponse message:
|
| 80 |
+
|
| 81 |
+
```
|
| 82 |
+
[uint8] Status (0x00 = OK, 0x01 = Error)
|
| 83 |
+
[varint] Message length
|
| 84 |
+
[bytes] Message string
|
| 85 |
+
[varint] Padding length
|
| 86 |
+
[bytes] Random padding
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
If the status is OK, the server MUST then begin forwarding data between the client and the specified TCP address until either side closes the connection. If the status is Error, the server MUST close the QUIC stream.
|
| 90 |
+
|
| 91 |
+
### UDP
|
| 92 |
+
|
| 93 |
+
UDP packets MUST be encapsulated in the following UDPMessage format and sent over QUIC's unreliable datagram (for both client-to-server and server-to-client):
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
[uint32] Session ID
|
| 97 |
+
[uint16] Packet ID
|
| 98 |
+
[uint8] Fragment ID
|
| 99 |
+
[uint8] Fragment count
|
| 100 |
+
[varint] Address length
|
| 101 |
+
[bytes] Address string (host:port)
|
| 102 |
+
[bytes] Payload
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
The client MUST use a unique Session ID for each UDP session. The server SHOULD assign a unique UDP port to each Session ID, unless it has another mechanism to differentiate packets from different sessions (e.g., symmetric NAT, varying outbound IP addresses, etc.).
|
| 106 |
+
|
| 107 |
+
The protocol does not provide an explicit way to close a UDP session. While a client can retain and reuse a Session ID indefinitely, the server SHOULD release and reassign the port associated with the Session ID after a period of inactivity or some other criteria. If the client sends a UDP packet to a Session ID that is no longer recognized by the server, the server MUST treat it as a new session and assign a new port.
|
| 108 |
+
|
| 109 |
+
If a server does not support UDP relay, it SHOULD silently discard all UDP messages received from the client.
|
| 110 |
+
|
| 111 |
+
#### Fragmentation
|
| 112 |
+
|
| 113 |
+
Due to the limit imposed by QUIC's unreliable datagram channel, any UDP packet that exceeds QUIC's maximum datagram size MUST either be fragmented or discarded.
|
| 114 |
+
|
| 115 |
+
For fragmented packets, each fragment MUST carry the same unique Packet ID. The Fragment ID, starting from 0, indicates the index out of the total Fragment Count. Both the server and client MUST wait for all fragments of a fragmented packet to arrive before processing them. If one or more fragments of a packet are lost, the entire packet MUST be discarded.
|
| 116 |
+
|
| 117 |
+
For packets that are not fragmented, the Fragment Count MUST be set to 1. In this case, the values of Packet ID and Fragment ID are irrelevant.
|
| 118 |
+
|
| 119 |
+
## Congestion Control
|
| 120 |
+
|
| 121 |
+
A unique feature of Hysteria is the ability to set the tx/rx (upload/download) rate on the client side. During authentication, the client sends its rx rate to the server via the `Hysteria-CC-RX` header. The server can use this to determine its transmission rate to the client, and vice versa by returning its rx rate to the client through the same header.
|
| 122 |
+
|
| 123 |
+
Three special cases are:
|
| 124 |
+
|
| 125 |
+
- If the client sends 0, it doesn't know its own rx rate. The server MUST use a congestion control algorithm (e.g., BBR, Cubic) to adjust its transmission rate.
|
| 126 |
+
- If the server responds with 0, it has no bandwidth limit. The client MAY transmit at any rate it wants.
|
| 127 |
+
- If the server responds with "auto", it chooses not to specify a rate. The client MUST use a congestion control algorithm to adjust its transmission rate.
|
| 128 |
+
|
| 129 |
+
## "Salamander" Obfuscation
|
| 130 |
+
|
| 131 |
+
The Hysteria protocol supports an optional obfuscation layer codenamed "Salamander".
|
| 132 |
+
|
| 133 |
+
"Salamander" encapsulates all QUIC packets in the following format:
|
| 134 |
+
|
| 135 |
+
```
|
| 136 |
+
[8 bytes] Salt
|
| 137 |
+
[bytes] Payload
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
For each QUIC packet, the obfuscator MUST calculate the BLAKE2b-256 hash of a randomly generated 8-byte salt appended to a user-provided pre-shared key.
|
| 141 |
+
|
| 142 |
+
```
|
| 143 |
+
hash = BLAKE2b-256(key + salt)
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
The hash is then used to obfuscate the payload using the following algorithm:
|
| 147 |
+
|
| 148 |
+
```
|
| 149 |
+
for i in range(0, len(payload)):
|
| 150 |
+
payload[i] ^= hash[i % 32]
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
The deobfuscator MUST use the same algorithms to calculate the salted hash and deobfuscate the payload. Any invalid packet MUST be discarded.
|
app/cmd/client.go
ADDED
|
@@ -0,0 +1,1031 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"crypto/sha256"
|
| 5 |
+
"crypto/x509"
|
| 6 |
+
"encoding/hex"
|
| 7 |
+
"errors"
|
| 8 |
+
"fmt"
|
| 9 |
+
"net"
|
| 10 |
+
"net/netip"
|
| 11 |
+
"os"
|
| 12 |
+
"os/signal"
|
| 13 |
+
"runtime"
|
| 14 |
+
"slices"
|
| 15 |
+
"strconv"
|
| 16 |
+
"strings"
|
| 17 |
+
"syscall"
|
| 18 |
+
"time"
|
| 19 |
+
|
| 20 |
+
"github.com/spf13/cobra"
|
| 21 |
+
"github.com/spf13/viper"
|
| 22 |
+
"go.uber.org/zap"
|
| 23 |
+
|
| 24 |
+
"github.com/apernet/hysteria/app/v2/internal/forwarding"
|
| 25 |
+
"github.com/apernet/hysteria/app/v2/internal/http"
|
| 26 |
+
"github.com/apernet/hysteria/app/v2/internal/proxymux"
|
| 27 |
+
"github.com/apernet/hysteria/app/v2/internal/redirect"
|
| 28 |
+
"github.com/apernet/hysteria/app/v2/internal/sockopts"
|
| 29 |
+
"github.com/apernet/hysteria/app/v2/internal/socks5"
|
| 30 |
+
"github.com/apernet/hysteria/app/v2/internal/tproxy"
|
| 31 |
+
"github.com/apernet/hysteria/app/v2/internal/tun"
|
| 32 |
+
"github.com/apernet/hysteria/app/v2/internal/url"
|
| 33 |
+
"github.com/apernet/hysteria/app/v2/internal/utils"
|
| 34 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 35 |
+
"github.com/apernet/hysteria/extras/v2/correctnet"
|
| 36 |
+
"github.com/apernet/hysteria/extras/v2/obfs"
|
| 37 |
+
"github.com/apernet/hysteria/extras/v2/transport/udphop"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
// Client flags
|
| 41 |
+
var (
|
| 42 |
+
showQR bool
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
var clientCmd = &cobra.Command{
|
| 46 |
+
Use: "client",
|
| 47 |
+
Short: "Client mode",
|
| 48 |
+
Run: runClient,
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
func init() {
|
| 52 |
+
initClientFlags()
|
| 53 |
+
rootCmd.AddCommand(clientCmd)
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
func initClientFlags() {
|
| 57 |
+
clientCmd.Flags().BoolVar(&showQR, "qr", false, "show QR code for server config sharing")
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
type clientConfig struct {
|
| 61 |
+
Server string `mapstructure:"server"`
|
| 62 |
+
Auth string `mapstructure:"auth"`
|
| 63 |
+
Transport clientConfigTransport `mapstructure:"transport"`
|
| 64 |
+
Obfs clientConfigObfs `mapstructure:"obfs"`
|
| 65 |
+
TLS clientConfigTLS `mapstructure:"tls"`
|
| 66 |
+
QUIC clientConfigQUIC `mapstructure:"quic"`
|
| 67 |
+
Bandwidth clientConfigBandwidth `mapstructure:"bandwidth"`
|
| 68 |
+
FastOpen bool `mapstructure:"fastOpen"`
|
| 69 |
+
Lazy bool `mapstructure:"lazy"`
|
| 70 |
+
SOCKS5 *socks5Config `mapstructure:"socks5"`
|
| 71 |
+
HTTP *httpConfig `mapstructure:"http"`
|
| 72 |
+
TCPForwarding []tcpForwardingEntry `mapstructure:"tcpForwarding"`
|
| 73 |
+
UDPForwarding []udpForwardingEntry `mapstructure:"udpForwarding"`
|
| 74 |
+
TCPTProxy *tcpTProxyConfig `mapstructure:"tcpTProxy"`
|
| 75 |
+
UDPTProxy *udpTProxyConfig `mapstructure:"udpTProxy"`
|
| 76 |
+
TCPRedirect *tcpRedirectConfig `mapstructure:"tcpRedirect"`
|
| 77 |
+
TUN *tunConfig `mapstructure:"tun"`
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
type clientConfigTransportUDP struct {
|
| 81 |
+
HopInterval time.Duration `mapstructure:"hopInterval"`
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
type clientConfigTransport struct {
|
| 85 |
+
Type string `mapstructure:"type"`
|
| 86 |
+
UDP clientConfigTransportUDP `mapstructure:"udp"`
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
type clientConfigObfsSalamander struct {
|
| 90 |
+
Password string `mapstructure:"password"`
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
type clientConfigObfs struct {
|
| 94 |
+
Type string `mapstructure:"type"`
|
| 95 |
+
Salamander clientConfigObfsSalamander `mapstructure:"salamander"`
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
type clientConfigTLS struct {
|
| 99 |
+
SNI string `mapstructure:"sni"`
|
| 100 |
+
Insecure bool `mapstructure:"insecure"`
|
| 101 |
+
PinSHA256 string `mapstructure:"pinSHA256"`
|
| 102 |
+
CA string `mapstructure:"ca"`
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
type clientConfigQUIC struct {
|
| 106 |
+
InitStreamReceiveWindow uint64 `mapstructure:"initStreamReceiveWindow"`
|
| 107 |
+
MaxStreamReceiveWindow uint64 `mapstructure:"maxStreamReceiveWindow"`
|
| 108 |
+
InitConnectionReceiveWindow uint64 `mapstructure:"initConnReceiveWindow"`
|
| 109 |
+
MaxConnectionReceiveWindow uint64 `mapstructure:"maxConnReceiveWindow"`
|
| 110 |
+
MaxIdleTimeout time.Duration `mapstructure:"maxIdleTimeout"`
|
| 111 |
+
KeepAlivePeriod time.Duration `mapstructure:"keepAlivePeriod"`
|
| 112 |
+
DisablePathMTUDiscovery bool `mapstructure:"disablePathMTUDiscovery"`
|
| 113 |
+
Sockopts clientConfigQUICSockopts `mapstructure:"sockopts"`
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
type clientConfigQUICSockopts struct {
|
| 117 |
+
BindInterface *string `mapstructure:"bindInterface"`
|
| 118 |
+
FirewallMark *uint32 `mapstructure:"fwmark"`
|
| 119 |
+
FdControlUnixSocket *string `mapstructure:"fdControlUnixSocket"`
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
type clientConfigBandwidth struct {
|
| 123 |
+
Up string `mapstructure:"up"`
|
| 124 |
+
Down string `mapstructure:"down"`
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
type socks5Config struct {
|
| 128 |
+
Listen string `mapstructure:"listen"`
|
| 129 |
+
Username string `mapstructure:"username"`
|
| 130 |
+
Password string `mapstructure:"password"`
|
| 131 |
+
DisableUDP bool `mapstructure:"disableUDP"`
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
type httpConfig struct {
|
| 135 |
+
Listen string `mapstructure:"listen"`
|
| 136 |
+
Username string `mapstructure:"username"`
|
| 137 |
+
Password string `mapstructure:"password"`
|
| 138 |
+
Realm string `mapstructure:"realm"`
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
type tcpForwardingEntry struct {
|
| 142 |
+
Listen string `mapstructure:"listen"`
|
| 143 |
+
Remote string `mapstructure:"remote"`
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
type udpForwardingEntry struct {
|
| 147 |
+
Listen string `mapstructure:"listen"`
|
| 148 |
+
Remote string `mapstructure:"remote"`
|
| 149 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
type tcpTProxyConfig struct {
|
| 153 |
+
Listen string `mapstructure:"listen"`
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
type udpTProxyConfig struct {
|
| 157 |
+
Listen string `mapstructure:"listen"`
|
| 158 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
type tcpRedirectConfig struct {
|
| 162 |
+
Listen string `mapstructure:"listen"`
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
type tunConfig struct {
|
| 166 |
+
Name string `mapstructure:"name"`
|
| 167 |
+
MTU uint32 `mapstructure:"mtu"`
|
| 168 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 169 |
+
Address struct {
|
| 170 |
+
IPv4 string `mapstructure:"ipv4"`
|
| 171 |
+
IPv6 string `mapstructure:"ipv6"`
|
| 172 |
+
} `mapstructure:"address"`
|
| 173 |
+
Route *struct {
|
| 174 |
+
Strict bool `mapstructure:"strict"`
|
| 175 |
+
IPv4 []string `mapstructure:"ipv4"`
|
| 176 |
+
IPv6 []string `mapstructure:"ipv6"`
|
| 177 |
+
IPv4Exclude []string `mapstructure:"ipv4Exclude"`
|
| 178 |
+
IPv6Exclude []string `mapstructure:"ipv6Exclude"`
|
| 179 |
+
} `mapstructure:"route"`
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
func (c *clientConfig) fillServerAddr(hyConfig *client.Config) error {
|
| 183 |
+
if c.Server == "" {
|
| 184 |
+
return configError{Field: "server", Err: errors.New("server address is empty")}
|
| 185 |
+
}
|
| 186 |
+
var addr net.Addr
|
| 187 |
+
var err error
|
| 188 |
+
host, port, hostPort := parseServerAddrString(c.Server)
|
| 189 |
+
if !isPortHoppingPort(port) {
|
| 190 |
+
addr, err = net.ResolveUDPAddr("udp", hostPort)
|
| 191 |
+
} else {
|
| 192 |
+
addr, err = udphop.ResolveUDPHopAddr(hostPort)
|
| 193 |
+
}
|
| 194 |
+
if err != nil {
|
| 195 |
+
return configError{Field: "server", Err: err}
|
| 196 |
+
}
|
| 197 |
+
hyConfig.ServerAddr = addr
|
| 198 |
+
// Special handling for SNI
|
| 199 |
+
if c.TLS.SNI == "" {
|
| 200 |
+
// Use server hostname as SNI
|
| 201 |
+
hyConfig.TLSConfig.ServerName = host
|
| 202 |
+
}
|
| 203 |
+
return nil
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// fillConnFactory must be called after fillServerAddr, as we have different logic
|
| 207 |
+
// for ConnFactory depending on whether we have a port hopping address.
|
| 208 |
+
func (c *clientConfig) fillConnFactory(hyConfig *client.Config) error {
|
| 209 |
+
so := &sockopts.SocketOptions{
|
| 210 |
+
BindInterface: c.QUIC.Sockopts.BindInterface,
|
| 211 |
+
FirewallMark: c.QUIC.Sockopts.FirewallMark,
|
| 212 |
+
FdControlUnixSocket: c.QUIC.Sockopts.FdControlUnixSocket,
|
| 213 |
+
}
|
| 214 |
+
if err := so.CheckSupported(); err != nil {
|
| 215 |
+
var unsupportedErr *sockopts.UnsupportedError
|
| 216 |
+
if errors.As(err, &unsupportedErr) {
|
| 217 |
+
return configError{
|
| 218 |
+
Field: "quic.sockopts." + unsupportedErr.Field,
|
| 219 |
+
Err: errors.New("unsupported on this platform"),
|
| 220 |
+
}
|
| 221 |
+
}
|
| 222 |
+
return configError{Field: "quic.sockopts", Err: err}
|
| 223 |
+
}
|
| 224 |
+
// Inner PacketConn
|
| 225 |
+
var newFunc func(addr net.Addr) (net.PacketConn, error)
|
| 226 |
+
switch strings.ToLower(c.Transport.Type) {
|
| 227 |
+
case "", "udp":
|
| 228 |
+
if hyConfig.ServerAddr.Network() == "udphop" {
|
| 229 |
+
hopAddr := hyConfig.ServerAddr.(*udphop.UDPHopAddr)
|
| 230 |
+
newFunc = func(addr net.Addr) (net.PacketConn, error) {
|
| 231 |
+
return udphop.NewUDPHopPacketConn(hopAddr, c.Transport.UDP.HopInterval, so.ListenUDP)
|
| 232 |
+
}
|
| 233 |
+
} else {
|
| 234 |
+
newFunc = func(addr net.Addr) (net.PacketConn, error) {
|
| 235 |
+
return so.ListenUDP()
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
default:
|
| 239 |
+
return configError{Field: "transport.type", Err: errors.New("unsupported transport type")}
|
| 240 |
+
}
|
| 241 |
+
// Obfuscation
|
| 242 |
+
var ob obfs.Obfuscator
|
| 243 |
+
var err error
|
| 244 |
+
switch strings.ToLower(c.Obfs.Type) {
|
| 245 |
+
case "", "plain":
|
| 246 |
+
// Keep it nil
|
| 247 |
+
case "salamander":
|
| 248 |
+
ob, err = obfs.NewSalamanderObfuscator([]byte(c.Obfs.Salamander.Password))
|
| 249 |
+
if err != nil {
|
| 250 |
+
return configError{Field: "obfs.salamander.password", Err: err}
|
| 251 |
+
}
|
| 252 |
+
default:
|
| 253 |
+
return configError{Field: "obfs.type", Err: errors.New("unsupported obfuscation type")}
|
| 254 |
+
}
|
| 255 |
+
hyConfig.ConnFactory = &adaptiveConnFactory{
|
| 256 |
+
NewFunc: newFunc,
|
| 257 |
+
Obfuscator: ob,
|
| 258 |
+
}
|
| 259 |
+
return nil
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
func (c *clientConfig) fillAuth(hyConfig *client.Config) error {
|
| 263 |
+
hyConfig.Auth = c.Auth
|
| 264 |
+
return nil
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
func (c *clientConfig) fillTLSConfig(hyConfig *client.Config) error {
|
| 268 |
+
if c.TLS.SNI != "" {
|
| 269 |
+
hyConfig.TLSConfig.ServerName = c.TLS.SNI
|
| 270 |
+
}
|
| 271 |
+
hyConfig.TLSConfig.InsecureSkipVerify = c.TLS.Insecure
|
| 272 |
+
if c.TLS.PinSHA256 != "" {
|
| 273 |
+
nHash := normalizeCertHash(c.TLS.PinSHA256)
|
| 274 |
+
hyConfig.TLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
| 275 |
+
for _, cert := range rawCerts {
|
| 276 |
+
hash := sha256.Sum256(cert)
|
| 277 |
+
hashHex := hex.EncodeToString(hash[:])
|
| 278 |
+
if hashHex == nHash {
|
| 279 |
+
return nil
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
// No match
|
| 283 |
+
return errors.New("no certificate matches the pinned hash")
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
if c.TLS.CA != "" {
|
| 287 |
+
ca, err := os.ReadFile(c.TLS.CA)
|
| 288 |
+
if err != nil {
|
| 289 |
+
return configError{Field: "tls.ca", Err: err}
|
| 290 |
+
}
|
| 291 |
+
cPool := x509.NewCertPool()
|
| 292 |
+
if !cPool.AppendCertsFromPEM(ca) {
|
| 293 |
+
return configError{Field: "tls.ca", Err: errors.New("failed to parse CA certificate")}
|
| 294 |
+
}
|
| 295 |
+
hyConfig.TLSConfig.RootCAs = cPool
|
| 296 |
+
}
|
| 297 |
+
return nil
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
func (c *clientConfig) fillQUICConfig(hyConfig *client.Config) error {
|
| 301 |
+
hyConfig.QUICConfig = client.QUICConfig{
|
| 302 |
+
InitialStreamReceiveWindow: c.QUIC.InitStreamReceiveWindow,
|
| 303 |
+
MaxStreamReceiveWindow: c.QUIC.MaxStreamReceiveWindow,
|
| 304 |
+
InitialConnectionReceiveWindow: c.QUIC.InitConnectionReceiveWindow,
|
| 305 |
+
MaxConnectionReceiveWindow: c.QUIC.MaxConnectionReceiveWindow,
|
| 306 |
+
MaxIdleTimeout: c.QUIC.MaxIdleTimeout,
|
| 307 |
+
KeepAlivePeriod: c.QUIC.KeepAlivePeriod,
|
| 308 |
+
DisablePathMTUDiscovery: c.QUIC.DisablePathMTUDiscovery,
|
| 309 |
+
}
|
| 310 |
+
return nil
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
func (c *clientConfig) fillBandwidthConfig(hyConfig *client.Config) error {
|
| 314 |
+
// New core now allows users to omit bandwidth values and use built-in congestion control
|
| 315 |
+
var err error
|
| 316 |
+
if c.Bandwidth.Up != "" {
|
| 317 |
+
hyConfig.BandwidthConfig.MaxTx, err = utils.ConvBandwidth(c.Bandwidth.Up)
|
| 318 |
+
if err != nil {
|
| 319 |
+
return configError{Field: "bandwidth.up", Err: err}
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
if c.Bandwidth.Down != "" {
|
| 323 |
+
hyConfig.BandwidthConfig.MaxRx, err = utils.ConvBandwidth(c.Bandwidth.Down)
|
| 324 |
+
if err != nil {
|
| 325 |
+
return configError{Field: "bandwidth.down", Err: err}
|
| 326 |
+
}
|
| 327 |
+
}
|
| 328 |
+
return nil
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
func (c *clientConfig) fillFastOpen(hyConfig *client.Config) error {
|
| 332 |
+
hyConfig.FastOpen = c.FastOpen
|
| 333 |
+
return nil
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
// URI generates a URI for sharing the config with others.
|
| 337 |
+
// Note that only the bare minimum of information required to
|
| 338 |
+
// connect to the server is included in the URI, specifically:
|
| 339 |
+
// - server address
|
| 340 |
+
// - authentication
|
| 341 |
+
// - obfuscation type
|
| 342 |
+
// - obfuscation password
|
| 343 |
+
// - TLS SNI
|
| 344 |
+
// - TLS insecure
|
| 345 |
+
// - TLS pinned SHA256 hash (normalized)
|
| 346 |
+
func (c *clientConfig) URI() string {
|
| 347 |
+
q := url.Values{}
|
| 348 |
+
switch strings.ToLower(c.Obfs.Type) {
|
| 349 |
+
case "salamander":
|
| 350 |
+
q.Set("obfs", "salamander")
|
| 351 |
+
q.Set("obfs-password", c.Obfs.Salamander.Password)
|
| 352 |
+
}
|
| 353 |
+
if c.TLS.SNI != "" {
|
| 354 |
+
q.Set("sni", c.TLS.SNI)
|
| 355 |
+
}
|
| 356 |
+
if c.TLS.Insecure {
|
| 357 |
+
q.Set("insecure", "1")
|
| 358 |
+
}
|
| 359 |
+
if c.TLS.PinSHA256 != "" {
|
| 360 |
+
q.Set("pinSHA256", normalizeCertHash(c.TLS.PinSHA256))
|
| 361 |
+
}
|
| 362 |
+
var user *url.Userinfo
|
| 363 |
+
if c.Auth != "" {
|
| 364 |
+
// We need to handle the special case of user:pass pairs
|
| 365 |
+
rs := strings.SplitN(c.Auth, ":", 2)
|
| 366 |
+
if len(rs) == 2 {
|
| 367 |
+
user = url.UserPassword(rs[0], rs[1])
|
| 368 |
+
} else {
|
| 369 |
+
user = url.User(c.Auth)
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
u := url.URL{
|
| 373 |
+
Scheme: "hysteria2",
|
| 374 |
+
User: user,
|
| 375 |
+
Host: c.Server,
|
| 376 |
+
Path: "/",
|
| 377 |
+
RawQuery: q.Encode(),
|
| 378 |
+
}
|
| 379 |
+
return u.String()
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
// parseURI tries to parse the server address field as a URI,
|
| 383 |
+
// and fills the config with the information contained in the URI.
|
| 384 |
+
// Returns whether the server address field is a valid URI.
|
| 385 |
+
// This allows a user to use put a URI as the server address and
|
| 386 |
+
// omit the fields that are already contained in the URI.
|
| 387 |
+
func (c *clientConfig) parseURI() bool {
|
| 388 |
+
u, err := url.Parse(c.Server)
|
| 389 |
+
if err != nil {
|
| 390 |
+
return false
|
| 391 |
+
}
|
| 392 |
+
if u.Scheme != "hysteria2" && u.Scheme != "hy2" {
|
| 393 |
+
return false
|
| 394 |
+
}
|
| 395 |
+
if u.User != nil {
|
| 396 |
+
auth, err := url.QueryUnescape(u.User.String())
|
| 397 |
+
if err != nil {
|
| 398 |
+
return false
|
| 399 |
+
}
|
| 400 |
+
c.Auth = auth
|
| 401 |
+
}
|
| 402 |
+
c.Server = u.Host
|
| 403 |
+
q := u.Query()
|
| 404 |
+
if obfsType := q.Get("obfs"); obfsType != "" {
|
| 405 |
+
c.Obfs.Type = obfsType
|
| 406 |
+
switch strings.ToLower(obfsType) {
|
| 407 |
+
case "salamander":
|
| 408 |
+
c.Obfs.Salamander.Password = q.Get("obfs-password")
|
| 409 |
+
}
|
| 410 |
+
}
|
| 411 |
+
if sni := q.Get("sni"); sni != "" {
|
| 412 |
+
c.TLS.SNI = sni
|
| 413 |
+
}
|
| 414 |
+
if insecure, err := strconv.ParseBool(q.Get("insecure")); err == nil {
|
| 415 |
+
c.TLS.Insecure = insecure
|
| 416 |
+
}
|
| 417 |
+
if pinSHA256 := q.Get("pinSHA256"); pinSHA256 != "" {
|
| 418 |
+
c.TLS.PinSHA256 = pinSHA256
|
| 419 |
+
}
|
| 420 |
+
return true
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
// Config validates the fields and returns a ready-to-use Hysteria client config
|
| 424 |
+
func (c *clientConfig) Config() (*client.Config, error) {
|
| 425 |
+
c.parseURI()
|
| 426 |
+
hyConfig := &client.Config{}
|
| 427 |
+
fillers := []func(*client.Config) error{
|
| 428 |
+
c.fillServerAddr,
|
| 429 |
+
c.fillConnFactory,
|
| 430 |
+
c.fillAuth,
|
| 431 |
+
c.fillTLSConfig,
|
| 432 |
+
c.fillQUICConfig,
|
| 433 |
+
c.fillBandwidthConfig,
|
| 434 |
+
c.fillFastOpen,
|
| 435 |
+
}
|
| 436 |
+
for _, f := range fillers {
|
| 437 |
+
if err := f(hyConfig); err != nil {
|
| 438 |
+
return nil, err
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
return hyConfig, nil
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
func runClient(cmd *cobra.Command, args []string) {
|
| 445 |
+
logger.Info("client mode")
|
| 446 |
+
|
| 447 |
+
if err := viper.ReadInConfig(); err != nil {
|
| 448 |
+
logger.Fatal("failed to read client config", zap.Error(err))
|
| 449 |
+
}
|
| 450 |
+
var config clientConfig
|
| 451 |
+
if err := viper.Unmarshal(&config); err != nil {
|
| 452 |
+
logger.Fatal("failed to parse client config", zap.Error(err))
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
c, err := client.NewReconnectableClient(
|
| 456 |
+
config.Config,
|
| 457 |
+
func(c client.Client, info *client.HandshakeInfo, count int) {
|
| 458 |
+
connectLog(info, count)
|
| 459 |
+
// On the client side, we start checking for updates after we successfully connect
|
| 460 |
+
// to the server, which, depending on whether lazy mode is enabled, may or may not
|
| 461 |
+
// be immediately after the client starts. We don't want the update check request
|
| 462 |
+
// to interfere with the lazy mode option.
|
| 463 |
+
if count == 1 && !disableUpdateCheck {
|
| 464 |
+
go runCheckUpdateClient(c)
|
| 465 |
+
}
|
| 466 |
+
}, config.Lazy)
|
| 467 |
+
if err != nil {
|
| 468 |
+
logger.Fatal("failed to initialize client", zap.Error(err))
|
| 469 |
+
}
|
| 470 |
+
defer c.Close()
|
| 471 |
+
|
| 472 |
+
uri := config.URI()
|
| 473 |
+
logger.Info("use this URI to share your server", zap.String("uri", uri))
|
| 474 |
+
if showQR {
|
| 475 |
+
utils.PrintQR(uri)
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
// Register modes
|
| 479 |
+
var runner clientModeRunner
|
| 480 |
+
if config.SOCKS5 != nil {
|
| 481 |
+
runner.Add("SOCKS5 server", func() error {
|
| 482 |
+
return clientSOCKS5(*config.SOCKS5, c)
|
| 483 |
+
})
|
| 484 |
+
}
|
| 485 |
+
if config.HTTP != nil {
|
| 486 |
+
runner.Add("HTTP proxy server", func() error {
|
| 487 |
+
return clientHTTP(*config.HTTP, c)
|
| 488 |
+
})
|
| 489 |
+
}
|
| 490 |
+
if len(config.TCPForwarding) > 0 {
|
| 491 |
+
runner.Add("TCP forwarding", func() error {
|
| 492 |
+
return clientTCPForwarding(config.TCPForwarding, c)
|
| 493 |
+
})
|
| 494 |
+
}
|
| 495 |
+
if len(config.UDPForwarding) > 0 {
|
| 496 |
+
runner.Add("UDP forwarding", func() error {
|
| 497 |
+
return clientUDPForwarding(config.UDPForwarding, c)
|
| 498 |
+
})
|
| 499 |
+
}
|
| 500 |
+
if config.TCPTProxy != nil {
|
| 501 |
+
runner.Add("TCP transparent proxy", func() error {
|
| 502 |
+
return clientTCPTProxy(*config.TCPTProxy, c)
|
| 503 |
+
})
|
| 504 |
+
}
|
| 505 |
+
if config.UDPTProxy != nil {
|
| 506 |
+
runner.Add("UDP transparent proxy", func() error {
|
| 507 |
+
return clientUDPTProxy(*config.UDPTProxy, c)
|
| 508 |
+
})
|
| 509 |
+
}
|
| 510 |
+
if config.TCPRedirect != nil {
|
| 511 |
+
runner.Add("TCP redirect", func() error {
|
| 512 |
+
return clientTCPRedirect(*config.TCPRedirect, c)
|
| 513 |
+
})
|
| 514 |
+
}
|
| 515 |
+
if config.TUN != nil {
|
| 516 |
+
runner.Add("TUN", func() error {
|
| 517 |
+
return clientTUN(*config.TUN, c)
|
| 518 |
+
})
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
signalChan := make(chan os.Signal, 1)
|
| 522 |
+
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
| 523 |
+
defer signal.Stop(signalChan)
|
| 524 |
+
|
| 525 |
+
runnerChan := make(chan clientModeRunnerResult, 1)
|
| 526 |
+
go func() {
|
| 527 |
+
runnerChan <- runner.Run()
|
| 528 |
+
}()
|
| 529 |
+
|
| 530 |
+
select {
|
| 531 |
+
case <-signalChan:
|
| 532 |
+
logger.Info("received signal, shutting down gracefully")
|
| 533 |
+
case r := <-runnerChan:
|
| 534 |
+
if r.OK {
|
| 535 |
+
logger.Info(r.Msg)
|
| 536 |
+
} else {
|
| 537 |
+
_ = c.Close() // Close the client here as Fatal will exit the program without running defer
|
| 538 |
+
if r.Err != nil {
|
| 539 |
+
logger.Fatal(r.Msg, zap.Error(r.Err))
|
| 540 |
+
} else {
|
| 541 |
+
logger.Fatal(r.Msg)
|
| 542 |
+
}
|
| 543 |
+
}
|
| 544 |
+
}
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
type clientModeRunner struct {
|
| 548 |
+
ModeMap map[string]func() error
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
type clientModeRunnerResult struct {
|
| 552 |
+
OK bool
|
| 553 |
+
Msg string
|
| 554 |
+
Err error
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
func (r *clientModeRunner) Add(name string, f func() error) {
|
| 558 |
+
if r.ModeMap == nil {
|
| 559 |
+
r.ModeMap = make(map[string]func() error)
|
| 560 |
+
}
|
| 561 |
+
r.ModeMap[name] = f
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
func (r *clientModeRunner) Run() clientModeRunnerResult {
|
| 565 |
+
if len(r.ModeMap) == 0 {
|
| 566 |
+
return clientModeRunnerResult{OK: false, Msg: "no mode specified"}
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
type modeError struct {
|
| 570 |
+
Name string
|
| 571 |
+
Err error
|
| 572 |
+
}
|
| 573 |
+
errChan := make(chan modeError, len(r.ModeMap))
|
| 574 |
+
for name, f := range r.ModeMap {
|
| 575 |
+
go func(name string, f func() error) {
|
| 576 |
+
err := f()
|
| 577 |
+
errChan <- modeError{name, err}
|
| 578 |
+
}(name, f)
|
| 579 |
+
}
|
| 580 |
+
// Fatal if any one of the modes fails
|
| 581 |
+
for i := 0; i < len(r.ModeMap); i++ {
|
| 582 |
+
e := <-errChan
|
| 583 |
+
if e.Err != nil {
|
| 584 |
+
return clientModeRunnerResult{OK: false, Msg: "failed to run " + e.Name, Err: e.Err}
|
| 585 |
+
}
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
// We don't really have any such cases, as currently none of our modes would stop on themselves without error.
|
| 589 |
+
// But we leave the possibility here for future expansion.
|
| 590 |
+
return clientModeRunnerResult{OK: true, Msg: "finished without error"}
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
func clientSOCKS5(config socks5Config, c client.Client) error {
|
| 594 |
+
if config.Listen == "" {
|
| 595 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 596 |
+
}
|
| 597 |
+
l, err := proxymux.ListenSOCKS(config.Listen)
|
| 598 |
+
if err != nil {
|
| 599 |
+
return configError{Field: "listen", Err: err}
|
| 600 |
+
}
|
| 601 |
+
var authFunc func(username, password string) bool
|
| 602 |
+
username, password := config.Username, config.Password
|
| 603 |
+
if username != "" && password != "" {
|
| 604 |
+
authFunc = func(u, p string) bool {
|
| 605 |
+
return u == username && p == password
|
| 606 |
+
}
|
| 607 |
+
}
|
| 608 |
+
s := socks5.Server{
|
| 609 |
+
HyClient: c,
|
| 610 |
+
AuthFunc: authFunc,
|
| 611 |
+
DisableUDP: config.DisableUDP,
|
| 612 |
+
EventLogger: &socks5Logger{},
|
| 613 |
+
}
|
| 614 |
+
logger.Info("SOCKS5 server listening", zap.String("addr", config.Listen))
|
| 615 |
+
return s.Serve(l)
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
func clientHTTP(config httpConfig, c client.Client) error {
|
| 619 |
+
if config.Listen == "" {
|
| 620 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 621 |
+
}
|
| 622 |
+
l, err := proxymux.ListenHTTP(config.Listen)
|
| 623 |
+
if err != nil {
|
| 624 |
+
return configError{Field: "listen", Err: err}
|
| 625 |
+
}
|
| 626 |
+
var authFunc func(username, password string) bool
|
| 627 |
+
username, password := config.Username, config.Password
|
| 628 |
+
if username != "" && password != "" {
|
| 629 |
+
authFunc = func(u, p string) bool {
|
| 630 |
+
return u == username && p == password
|
| 631 |
+
}
|
| 632 |
+
}
|
| 633 |
+
if config.Realm == "" {
|
| 634 |
+
config.Realm = "Hysteria"
|
| 635 |
+
}
|
| 636 |
+
h := http.Server{
|
| 637 |
+
HyClient: c,
|
| 638 |
+
AuthFunc: authFunc,
|
| 639 |
+
AuthRealm: config.Realm,
|
| 640 |
+
EventLogger: &httpLogger{},
|
| 641 |
+
}
|
| 642 |
+
logger.Info("HTTP proxy server listening", zap.String("addr", config.Listen))
|
| 643 |
+
return h.Serve(l)
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
func clientTCPForwarding(entries []tcpForwardingEntry, c client.Client) error {
|
| 647 |
+
errChan := make(chan error, len(entries))
|
| 648 |
+
for _, e := range entries {
|
| 649 |
+
if e.Listen == "" {
|
| 650 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 651 |
+
}
|
| 652 |
+
if e.Remote == "" {
|
| 653 |
+
return configError{Field: "remote", Err: errors.New("remote address is empty")}
|
| 654 |
+
}
|
| 655 |
+
l, err := correctnet.Listen("tcp", e.Listen)
|
| 656 |
+
if err != nil {
|
| 657 |
+
return configError{Field: "listen", Err: err}
|
| 658 |
+
}
|
| 659 |
+
logger.Info("TCP forwarding listening", zap.String("addr", e.Listen), zap.String("remote", e.Remote))
|
| 660 |
+
go func(remote string) {
|
| 661 |
+
t := &forwarding.TCPTunnel{
|
| 662 |
+
HyClient: c,
|
| 663 |
+
Remote: remote,
|
| 664 |
+
EventLogger: &tcpLogger{},
|
| 665 |
+
}
|
| 666 |
+
errChan <- t.Serve(l)
|
| 667 |
+
}(e.Remote)
|
| 668 |
+
}
|
| 669 |
+
// Return if any one of the forwarding fails
|
| 670 |
+
return <-errChan
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
func clientUDPForwarding(entries []udpForwardingEntry, c client.Client) error {
|
| 674 |
+
errChan := make(chan error, len(entries))
|
| 675 |
+
for _, e := range entries {
|
| 676 |
+
if e.Listen == "" {
|
| 677 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 678 |
+
}
|
| 679 |
+
if e.Remote == "" {
|
| 680 |
+
return configError{Field: "remote", Err: errors.New("remote address is empty")}
|
| 681 |
+
}
|
| 682 |
+
l, err := correctnet.ListenPacket("udp", e.Listen)
|
| 683 |
+
if err != nil {
|
| 684 |
+
return configError{Field: "listen", Err: err}
|
| 685 |
+
}
|
| 686 |
+
logger.Info("UDP forwarding listening", zap.String("addr", e.Listen), zap.String("remote", e.Remote))
|
| 687 |
+
go func(remote string, timeout time.Duration) {
|
| 688 |
+
u := &forwarding.UDPTunnel{
|
| 689 |
+
HyClient: c,
|
| 690 |
+
Remote: remote,
|
| 691 |
+
Timeout: timeout,
|
| 692 |
+
EventLogger: &udpLogger{},
|
| 693 |
+
}
|
| 694 |
+
errChan <- u.Serve(l)
|
| 695 |
+
}(e.Remote, e.Timeout)
|
| 696 |
+
}
|
| 697 |
+
// Return if any one of the forwarding fails
|
| 698 |
+
return <-errChan
|
| 699 |
+
}
|
| 700 |
+
|
| 701 |
+
func clientTCPTProxy(config tcpTProxyConfig, c client.Client) error {
|
| 702 |
+
if config.Listen == "" {
|
| 703 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 704 |
+
}
|
| 705 |
+
laddr, err := net.ResolveTCPAddr("tcp", config.Listen)
|
| 706 |
+
if err != nil {
|
| 707 |
+
return configError{Field: "listen", Err: err}
|
| 708 |
+
}
|
| 709 |
+
p := &tproxy.TCPTProxy{
|
| 710 |
+
HyClient: c,
|
| 711 |
+
EventLogger: &tcpTProxyLogger{},
|
| 712 |
+
}
|
| 713 |
+
logger.Info("TCP transparent proxy listening", zap.String("addr", config.Listen))
|
| 714 |
+
return p.ListenAndServe(laddr)
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
func clientUDPTProxy(config udpTProxyConfig, c client.Client) error {
|
| 718 |
+
if config.Listen == "" {
|
| 719 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 720 |
+
}
|
| 721 |
+
laddr, err := net.ResolveUDPAddr("udp", config.Listen)
|
| 722 |
+
if err != nil {
|
| 723 |
+
return configError{Field: "listen", Err: err}
|
| 724 |
+
}
|
| 725 |
+
p := &tproxy.UDPTProxy{
|
| 726 |
+
HyClient: c,
|
| 727 |
+
Timeout: config.Timeout,
|
| 728 |
+
EventLogger: &udpTProxyLogger{},
|
| 729 |
+
}
|
| 730 |
+
logger.Info("UDP transparent proxy listening", zap.String("addr", config.Listen))
|
| 731 |
+
return p.ListenAndServe(laddr)
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
+
func clientTCPRedirect(config tcpRedirectConfig, c client.Client) error {
|
| 735 |
+
if config.Listen == "" {
|
| 736 |
+
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
| 737 |
+
}
|
| 738 |
+
laddr, err := net.ResolveTCPAddr("tcp", config.Listen)
|
| 739 |
+
if err != nil {
|
| 740 |
+
return configError{Field: "listen", Err: err}
|
| 741 |
+
}
|
| 742 |
+
p := &redirect.TCPRedirect{
|
| 743 |
+
HyClient: c,
|
| 744 |
+
EventLogger: &tcpRedirectLogger{},
|
| 745 |
+
}
|
| 746 |
+
logger.Info("TCP redirect listening", zap.String("addr", config.Listen))
|
| 747 |
+
return p.ListenAndServe(laddr)
|
| 748 |
+
}
|
| 749 |
+
|
| 750 |
+
func clientTUN(config tunConfig, c client.Client) error {
|
| 751 |
+
supportedPlatforms := []string{"linux", "darwin", "windows", "android"}
|
| 752 |
+
if !slices.Contains(supportedPlatforms, runtime.GOOS) {
|
| 753 |
+
logger.Error("TUN is not supported on this platform", zap.String("platform", runtime.GOOS))
|
| 754 |
+
}
|
| 755 |
+
if config.Name == "" {
|
| 756 |
+
return configError{Field: "name", Err: errors.New("name is empty")}
|
| 757 |
+
}
|
| 758 |
+
if config.MTU == 0 {
|
| 759 |
+
config.MTU = 1500
|
| 760 |
+
}
|
| 761 |
+
timeout := int64(config.Timeout.Seconds())
|
| 762 |
+
if timeout == 0 {
|
| 763 |
+
timeout = 300
|
| 764 |
+
}
|
| 765 |
+
if config.Address.IPv4 == "" {
|
| 766 |
+
config.Address.IPv4 = "100.100.100.101/30"
|
| 767 |
+
}
|
| 768 |
+
prefix4, err := netip.ParsePrefix(config.Address.IPv4)
|
| 769 |
+
if err != nil {
|
| 770 |
+
return configError{Field: "address.ipv4", Err: err}
|
| 771 |
+
}
|
| 772 |
+
if config.Address.IPv6 == "" {
|
| 773 |
+
config.Address.IPv6 = "2001::ffff:ffff:ffff:fff1/126"
|
| 774 |
+
}
|
| 775 |
+
prefix6, err := netip.ParsePrefix(config.Address.IPv6)
|
| 776 |
+
if err != nil {
|
| 777 |
+
return configError{Field: "address.ipv6", Err: err}
|
| 778 |
+
}
|
| 779 |
+
server := &tun.Server{
|
| 780 |
+
HyClient: c,
|
| 781 |
+
EventLogger: &tunLogger{},
|
| 782 |
+
Logger: logger,
|
| 783 |
+
IfName: config.Name,
|
| 784 |
+
MTU: config.MTU,
|
| 785 |
+
Timeout: timeout,
|
| 786 |
+
Inet4Address: []netip.Prefix{prefix4},
|
| 787 |
+
Inet6Address: []netip.Prefix{prefix6},
|
| 788 |
+
}
|
| 789 |
+
if config.Route != nil {
|
| 790 |
+
server.AutoRoute = true
|
| 791 |
+
server.StructRoute = config.Route.Strict
|
| 792 |
+
|
| 793 |
+
parsePrefixes := func(field string, ss []string) ([]netip.Prefix, error) {
|
| 794 |
+
var prefixes []netip.Prefix
|
| 795 |
+
for i, s := range ss {
|
| 796 |
+
var p netip.Prefix
|
| 797 |
+
if strings.Contains(s, "/") {
|
| 798 |
+
var err error
|
| 799 |
+
p, err = netip.ParsePrefix(s)
|
| 800 |
+
if err != nil {
|
| 801 |
+
return nil, configError{Field: fmt.Sprintf("%s[%d]", field, i), Err: err}
|
| 802 |
+
}
|
| 803 |
+
} else {
|
| 804 |
+
pa, err := netip.ParseAddr(s)
|
| 805 |
+
if err != nil {
|
| 806 |
+
return nil, configError{Field: fmt.Sprintf("%s[%d]", field, i), Err: err}
|
| 807 |
+
}
|
| 808 |
+
p = netip.PrefixFrom(pa, pa.BitLen())
|
| 809 |
+
}
|
| 810 |
+
prefixes = append(prefixes, p)
|
| 811 |
+
}
|
| 812 |
+
return prefixes, nil
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
server.Inet4RouteAddress, err = parsePrefixes("route.ipv4", config.Route.IPv4)
|
| 816 |
+
if err != nil {
|
| 817 |
+
return err
|
| 818 |
+
}
|
| 819 |
+
server.Inet6RouteAddress, err = parsePrefixes("route.ipv6", config.Route.IPv6)
|
| 820 |
+
if err != nil {
|
| 821 |
+
return err
|
| 822 |
+
}
|
| 823 |
+
server.Inet4RouteExcludeAddress, err = parsePrefixes("route.ipv4Exclude", config.Route.IPv4Exclude)
|
| 824 |
+
if err != nil {
|
| 825 |
+
return err
|
| 826 |
+
}
|
| 827 |
+
server.Inet6RouteExcludeAddress, err = parsePrefixes("route.ipv6Exclude", config.Route.IPv6Exclude)
|
| 828 |
+
if err != nil {
|
| 829 |
+
return err
|
| 830 |
+
}
|
| 831 |
+
}
|
| 832 |
+
logger.Info("TUN listening", zap.String("interface", config.Name))
|
| 833 |
+
return server.Serve()
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
// parseServerAddrString parses server address string.
|
| 837 |
+
// Server address can be in either "host:port" or "host" format (in which case we assume port 443).
|
| 838 |
+
func parseServerAddrString(addrStr string) (host, port, hostPort string) {
|
| 839 |
+
h, p, err := net.SplitHostPort(addrStr)
|
| 840 |
+
if err != nil {
|
| 841 |
+
return addrStr, "443", net.JoinHostPort(addrStr, "443")
|
| 842 |
+
}
|
| 843 |
+
return h, p, addrStr
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
// isPortHoppingPort returns whether the port string is a port hopping port.
|
| 847 |
+
// We consider a port string to be a port hopping port if it contains "-" or ",".
|
| 848 |
+
func isPortHoppingPort(port string) bool {
|
| 849 |
+
return strings.Contains(port, "-") || strings.Contains(port, ",")
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
// normalizeCertHash normalizes a certificate hash string.
|
| 853 |
+
// It converts all characters to lowercase and removes possible separators such as ":" and "-".
|
| 854 |
+
func normalizeCertHash(hash string) string {
|
| 855 |
+
r := strings.ToLower(hash)
|
| 856 |
+
r = strings.ReplaceAll(r, ":", "")
|
| 857 |
+
r = strings.ReplaceAll(r, "-", "")
|
| 858 |
+
return r
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
type adaptiveConnFactory struct {
|
| 862 |
+
NewFunc func(addr net.Addr) (net.PacketConn, error)
|
| 863 |
+
Obfuscator obfs.Obfuscator // nil if no obfuscation
|
| 864 |
+
}
|
| 865 |
+
|
| 866 |
+
func (f *adaptiveConnFactory) New(addr net.Addr) (net.PacketConn, error) {
|
| 867 |
+
if f.Obfuscator == nil {
|
| 868 |
+
return f.NewFunc(addr)
|
| 869 |
+
} else {
|
| 870 |
+
conn, err := f.NewFunc(addr)
|
| 871 |
+
if err != nil {
|
| 872 |
+
return nil, err
|
| 873 |
+
}
|
| 874 |
+
return obfs.WrapPacketConn(conn, f.Obfuscator), nil
|
| 875 |
+
}
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
func connectLog(info *client.HandshakeInfo, count int) {
|
| 879 |
+
logger.Info("connected to server",
|
| 880 |
+
zap.Bool("udpEnabled", info.UDPEnabled),
|
| 881 |
+
zap.Uint64("tx", info.Tx),
|
| 882 |
+
zap.Int("count", count))
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
type socks5Logger struct{}
|
| 886 |
+
|
| 887 |
+
func (l *socks5Logger) TCPRequest(addr net.Addr, reqAddr string) {
|
| 888 |
+
logger.Debug("SOCKS5 TCP request", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr))
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
func (l *socks5Logger) TCPError(addr net.Addr, reqAddr string, err error) {
|
| 892 |
+
if err == nil {
|
| 893 |
+
logger.Debug("SOCKS5 TCP closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr))
|
| 894 |
+
} else {
|
| 895 |
+
logger.Warn("SOCKS5 TCP error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr), zap.Error(err))
|
| 896 |
+
}
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
func (l *socks5Logger) UDPRequest(addr net.Addr) {
|
| 900 |
+
logger.Debug("SOCKS5 UDP request", zap.String("addr", addr.String()))
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
func (l *socks5Logger) UDPError(addr net.Addr, err error) {
|
| 904 |
+
if err == nil {
|
| 905 |
+
logger.Debug("SOCKS5 UDP closed", zap.String("addr", addr.String()))
|
| 906 |
+
} else {
|
| 907 |
+
logger.Warn("SOCKS5 UDP error", zap.String("addr", addr.String()), zap.Error(err))
|
| 908 |
+
}
|
| 909 |
+
}
|
| 910 |
+
|
| 911 |
+
type httpLogger struct{}
|
| 912 |
+
|
| 913 |
+
func (l *httpLogger) ConnectRequest(addr net.Addr, reqAddr string) {
|
| 914 |
+
logger.Debug("HTTP CONNECT request", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr))
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
func (l *httpLogger) ConnectError(addr net.Addr, reqAddr string, err error) {
|
| 918 |
+
if err == nil {
|
| 919 |
+
logger.Debug("HTTP CONNECT closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr))
|
| 920 |
+
} else {
|
| 921 |
+
logger.Warn("HTTP CONNECT error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr), zap.Error(err))
|
| 922 |
+
}
|
| 923 |
+
}
|
| 924 |
+
|
| 925 |
+
func (l *httpLogger) HTTPRequest(addr net.Addr, reqURL string) {
|
| 926 |
+
logger.Debug("HTTP request", zap.String("addr", addr.String()), zap.String("reqURL", reqURL))
|
| 927 |
+
}
|
| 928 |
+
|
| 929 |
+
func (l *httpLogger) HTTPError(addr net.Addr, reqURL string, err error) {
|
| 930 |
+
if err == nil {
|
| 931 |
+
logger.Debug("HTTP closed", zap.String("addr", addr.String()), zap.String("reqURL", reqURL))
|
| 932 |
+
} else {
|
| 933 |
+
logger.Warn("HTTP error", zap.String("addr", addr.String()), zap.String("reqURL", reqURL), zap.Error(err))
|
| 934 |
+
}
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
type tcpLogger struct{}
|
| 938 |
+
|
| 939 |
+
func (l *tcpLogger) Connect(addr net.Addr) {
|
| 940 |
+
logger.Debug("TCP forwarding connect", zap.String("addr", addr.String()))
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
func (l *tcpLogger) Error(addr net.Addr, err error) {
|
| 944 |
+
if err == nil {
|
| 945 |
+
logger.Debug("TCP forwarding closed", zap.String("addr", addr.String()))
|
| 946 |
+
} else {
|
| 947 |
+
logger.Warn("TCP forwarding error", zap.String("addr", addr.String()), zap.Error(err))
|
| 948 |
+
}
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
type udpLogger struct{}
|
| 952 |
+
|
| 953 |
+
func (l *udpLogger) Connect(addr net.Addr) {
|
| 954 |
+
logger.Debug("UDP forwarding connect", zap.String("addr", addr.String()))
|
| 955 |
+
}
|
| 956 |
+
|
| 957 |
+
func (l *udpLogger) Error(addr net.Addr, err error) {
|
| 958 |
+
if err == nil {
|
| 959 |
+
logger.Debug("UDP forwarding closed", zap.String("addr", addr.String()))
|
| 960 |
+
} else {
|
| 961 |
+
logger.Warn("UDP forwarding error", zap.String("addr", addr.String()), zap.Error(err))
|
| 962 |
+
}
|
| 963 |
+
}
|
| 964 |
+
|
| 965 |
+
type tcpTProxyLogger struct{}
|
| 966 |
+
|
| 967 |
+
func (l *tcpTProxyLogger) Connect(addr, reqAddr net.Addr) {
|
| 968 |
+
logger.Debug("TCP transparent proxy connect", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
|
| 969 |
+
}
|
| 970 |
+
|
| 971 |
+
func (l *tcpTProxyLogger) Error(addr, reqAddr net.Addr, err error) {
|
| 972 |
+
if err == nil {
|
| 973 |
+
logger.Debug("TCP transparent proxy closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
|
| 974 |
+
} else {
|
| 975 |
+
logger.Warn("TCP transparent proxy error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()), zap.Error(err))
|
| 976 |
+
}
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
type udpTProxyLogger struct{}
|
| 980 |
+
|
| 981 |
+
func (l *udpTProxyLogger) Connect(addr, reqAddr net.Addr) {
|
| 982 |
+
logger.Debug("UDP transparent proxy connect", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
func (l *udpTProxyLogger) Error(addr, reqAddr net.Addr, err error) {
|
| 986 |
+
if err == nil {
|
| 987 |
+
logger.Debug("UDP transparent proxy closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
|
| 988 |
+
} else {
|
| 989 |
+
logger.Warn("UDP transparent proxy error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()), zap.Error(err))
|
| 990 |
+
}
|
| 991 |
+
}
|
| 992 |
+
|
| 993 |
+
type tcpRedirectLogger struct{}
|
| 994 |
+
|
| 995 |
+
func (l *tcpRedirectLogger) Connect(addr, reqAddr net.Addr) {
|
| 996 |
+
logger.Debug("TCP redirect connect", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
|
| 997 |
+
}
|
| 998 |
+
|
| 999 |
+
func (l *tcpRedirectLogger) Error(addr, reqAddr net.Addr, err error) {
|
| 1000 |
+
if err == nil {
|
| 1001 |
+
logger.Debug("TCP redirect closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
|
| 1002 |
+
} else {
|
| 1003 |
+
logger.Warn("TCP redirect error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()), zap.Error(err))
|
| 1004 |
+
}
|
| 1005 |
+
}
|
| 1006 |
+
|
| 1007 |
+
type tunLogger struct{}
|
| 1008 |
+
|
| 1009 |
+
func (l *tunLogger) TCPRequest(addr, reqAddr string) {
|
| 1010 |
+
logger.Debug("TUN TCP request", zap.String("addr", addr), zap.String("reqAddr", reqAddr))
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
func (l *tunLogger) TCPError(addr, reqAddr string, err error) {
|
| 1014 |
+
if err == nil {
|
| 1015 |
+
logger.Debug("TUN TCP closed", zap.String("addr", addr), zap.String("reqAddr", reqAddr))
|
| 1016 |
+
} else {
|
| 1017 |
+
logger.Warn("TUN TCP error", zap.String("addr", addr), zap.String("reqAddr", reqAddr), zap.Error(err))
|
| 1018 |
+
}
|
| 1019 |
+
}
|
| 1020 |
+
|
| 1021 |
+
func (l *tunLogger) UDPRequest(addr string) {
|
| 1022 |
+
logger.Debug("TUN UDP request", zap.String("addr", addr))
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
func (l *tunLogger) UDPError(addr string, err error) {
|
| 1026 |
+
if err == nil {
|
| 1027 |
+
logger.Debug("TUN UDP closed", zap.String("addr", addr))
|
| 1028 |
+
} else {
|
| 1029 |
+
logger.Warn("TUN UDP error", zap.String("addr", addr), zap.Error(err))
|
| 1030 |
+
}
|
| 1031 |
+
}
|
app/cmd/client_test.go
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"testing"
|
| 5 |
+
"time"
|
| 6 |
+
|
| 7 |
+
"github.com/stretchr/testify/assert"
|
| 8 |
+
|
| 9 |
+
"github.com/spf13/viper"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
// TestClientConfig tests the parsing of the client config
|
| 13 |
+
func TestClientConfig(t *testing.T) {
|
| 14 |
+
viper.SetConfigFile("client_test.yaml")
|
| 15 |
+
err := viper.ReadInConfig()
|
| 16 |
+
assert.NoError(t, err)
|
| 17 |
+
var config clientConfig
|
| 18 |
+
err = viper.Unmarshal(&config)
|
| 19 |
+
assert.NoError(t, err)
|
| 20 |
+
assert.Equal(t, config, clientConfig{
|
| 21 |
+
Server: "example.com",
|
| 22 |
+
Auth: "weak_ahh_password",
|
| 23 |
+
Transport: clientConfigTransport{
|
| 24 |
+
Type: "udp",
|
| 25 |
+
UDP: clientConfigTransportUDP{
|
| 26 |
+
HopInterval: 30 * time.Second,
|
| 27 |
+
},
|
| 28 |
+
},
|
| 29 |
+
Obfs: clientConfigObfs{
|
| 30 |
+
Type: "salamander",
|
| 31 |
+
Salamander: clientConfigObfsSalamander{
|
| 32 |
+
Password: "cry_me_a_r1ver",
|
| 33 |
+
},
|
| 34 |
+
},
|
| 35 |
+
TLS: clientConfigTLS{
|
| 36 |
+
SNI: "another.example.com",
|
| 37 |
+
Insecure: true,
|
| 38 |
+
PinSHA256: "114515DEADBEEF",
|
| 39 |
+
CA: "custom_ca.crt",
|
| 40 |
+
},
|
| 41 |
+
QUIC: clientConfigQUIC{
|
| 42 |
+
InitStreamReceiveWindow: 1145141,
|
| 43 |
+
MaxStreamReceiveWindow: 1145142,
|
| 44 |
+
InitConnectionReceiveWindow: 1145143,
|
| 45 |
+
MaxConnectionReceiveWindow: 1145144,
|
| 46 |
+
MaxIdleTimeout: 10 * time.Second,
|
| 47 |
+
KeepAlivePeriod: 4 * time.Second,
|
| 48 |
+
DisablePathMTUDiscovery: true,
|
| 49 |
+
Sockopts: clientConfigQUICSockopts{
|
| 50 |
+
BindInterface: stringRef("eth0"),
|
| 51 |
+
FirewallMark: uint32Ref(1234),
|
| 52 |
+
FdControlUnixSocket: stringRef("test.sock"),
|
| 53 |
+
},
|
| 54 |
+
},
|
| 55 |
+
Bandwidth: clientConfigBandwidth{
|
| 56 |
+
Up: "200 mbps",
|
| 57 |
+
Down: "1 gbps",
|
| 58 |
+
},
|
| 59 |
+
FastOpen: true,
|
| 60 |
+
Lazy: true,
|
| 61 |
+
SOCKS5: &socks5Config{
|
| 62 |
+
Listen: "127.0.0.1:1080",
|
| 63 |
+
Username: "anon",
|
| 64 |
+
Password: "bro",
|
| 65 |
+
DisableUDP: true,
|
| 66 |
+
},
|
| 67 |
+
HTTP: &httpConfig{
|
| 68 |
+
Listen: "127.0.0.1:8080",
|
| 69 |
+
Username: "qqq",
|
| 70 |
+
Password: "bruh",
|
| 71 |
+
Realm: "martian",
|
| 72 |
+
},
|
| 73 |
+
TCPForwarding: []tcpForwardingEntry{
|
| 74 |
+
{
|
| 75 |
+
Listen: "127.0.0.1:8088",
|
| 76 |
+
Remote: "internal.example.com:80",
|
| 77 |
+
},
|
| 78 |
+
},
|
| 79 |
+
UDPForwarding: []udpForwardingEntry{
|
| 80 |
+
{
|
| 81 |
+
Listen: "127.0.0.1:5353",
|
| 82 |
+
Remote: "internal.example.com:53",
|
| 83 |
+
Timeout: 50 * time.Second,
|
| 84 |
+
},
|
| 85 |
+
},
|
| 86 |
+
TCPTProxy: &tcpTProxyConfig{
|
| 87 |
+
Listen: "127.0.0.1:2500",
|
| 88 |
+
},
|
| 89 |
+
UDPTProxy: &udpTProxyConfig{
|
| 90 |
+
Listen: "127.0.0.1:2501",
|
| 91 |
+
Timeout: 20 * time.Second,
|
| 92 |
+
},
|
| 93 |
+
TCPRedirect: &tcpRedirectConfig{
|
| 94 |
+
Listen: "127.0.0.1:3500",
|
| 95 |
+
},
|
| 96 |
+
TUN: &tunConfig{
|
| 97 |
+
Name: "hytun",
|
| 98 |
+
MTU: 1500,
|
| 99 |
+
Timeout: 60 * time.Second,
|
| 100 |
+
Address: struct {
|
| 101 |
+
IPv4 string `mapstructure:"ipv4"`
|
| 102 |
+
IPv6 string `mapstructure:"ipv6"`
|
| 103 |
+
}{IPv4: "100.100.100.101/30", IPv6: "2001::ffff:ffff:ffff:fff1/126"},
|
| 104 |
+
Route: &struct {
|
| 105 |
+
Strict bool `mapstructure:"strict"`
|
| 106 |
+
IPv4 []string `mapstructure:"ipv4"`
|
| 107 |
+
IPv6 []string `mapstructure:"ipv6"`
|
| 108 |
+
IPv4Exclude []string `mapstructure:"ipv4Exclude"`
|
| 109 |
+
IPv6Exclude []string `mapstructure:"ipv6Exclude"`
|
| 110 |
+
}{
|
| 111 |
+
Strict: true,
|
| 112 |
+
IPv4: []string{"0.0.0.0/0"},
|
| 113 |
+
IPv6: []string{"2000::/3"},
|
| 114 |
+
IPv4Exclude: []string{"192.0.2.1/32"},
|
| 115 |
+
IPv6Exclude: []string{"2001:db8::1/128"},
|
| 116 |
+
},
|
| 117 |
+
},
|
| 118 |
+
})
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
// TestClientConfigURI tests URI-related functions of clientConfig
|
| 122 |
+
func TestClientConfigURI(t *testing.T) {
|
| 123 |
+
tests := []struct {
|
| 124 |
+
uri string
|
| 125 |
+
uriOK bool
|
| 126 |
+
config *clientConfig
|
| 127 |
+
}{
|
| 128 |
+
{
|
| 129 |
+
uri: "hysteria2://god@zilla.jp/",
|
| 130 |
+
uriOK: true,
|
| 131 |
+
config: &clientConfig{
|
| 132 |
+
Server: "zilla.jp",
|
| 133 |
+
Auth: "god",
|
| 134 |
+
},
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
uri: "hysteria2://john:wick@continental.org:4443/",
|
| 138 |
+
uriOK: true,
|
| 139 |
+
config: &clientConfig{
|
| 140 |
+
Server: "continental.org:4443",
|
| 141 |
+
Auth: "john:wick",
|
| 142 |
+
},
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
uri: "hysteria2://saul@better.call:7000-10000,20000/",
|
| 146 |
+
uriOK: true,
|
| 147 |
+
config: &clientConfig{
|
| 148 |
+
Server: "better.call:7000-10000,20000",
|
| 149 |
+
Auth: "saul",
|
| 150 |
+
},
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
uri: "hysteria2://noauth.com/?insecure=1&obfs=salamander&obfs-password=66ccff&pinSHA256=deadbeef&sni=crap.cc",
|
| 154 |
+
uriOK: true,
|
| 155 |
+
config: &clientConfig{
|
| 156 |
+
Server: "noauth.com",
|
| 157 |
+
Auth: "",
|
| 158 |
+
Obfs: clientConfigObfs{
|
| 159 |
+
Type: "salamander",
|
| 160 |
+
Salamander: clientConfigObfsSalamander{
|
| 161 |
+
Password: "66ccff",
|
| 162 |
+
},
|
| 163 |
+
},
|
| 164 |
+
TLS: clientConfigTLS{
|
| 165 |
+
SNI: "crap.cc",
|
| 166 |
+
Insecure: true,
|
| 167 |
+
PinSHA256: "deadbeef",
|
| 168 |
+
},
|
| 169 |
+
},
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
uri: "invalid.bs",
|
| 173 |
+
uriOK: false,
|
| 174 |
+
config: nil,
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
uri: "https://www.google.com/search?q=test",
|
| 178 |
+
uriOK: false,
|
| 179 |
+
config: nil,
|
| 180 |
+
},
|
| 181 |
+
}
|
| 182 |
+
for _, test := range tests {
|
| 183 |
+
t.Run(test.uri, func(t *testing.T) {
|
| 184 |
+
// Test parseURI
|
| 185 |
+
nc := &clientConfig{Server: test.uri}
|
| 186 |
+
assert.Equal(t, nc.parseURI(), test.uriOK)
|
| 187 |
+
if test.uriOK {
|
| 188 |
+
assert.Equal(t, nc, test.config)
|
| 189 |
+
}
|
| 190 |
+
// Test URI generation
|
| 191 |
+
if test.config != nil {
|
| 192 |
+
assert.Equal(t, test.config.URI(), test.uri)
|
| 193 |
+
}
|
| 194 |
+
})
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
func stringRef(s string) *string {
|
| 199 |
+
return &s
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
func uint32Ref(i uint32) *uint32 {
|
| 203 |
+
return &i
|
| 204 |
+
}
|
app/cmd/client_test.yaml
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
server: example.com
|
| 2 |
+
|
| 3 |
+
auth: weak_ahh_password
|
| 4 |
+
|
| 5 |
+
transport:
|
| 6 |
+
type: udp
|
| 7 |
+
udp:
|
| 8 |
+
hopInterval: 30s
|
| 9 |
+
|
| 10 |
+
obfs:
|
| 11 |
+
type: salamander
|
| 12 |
+
salamander:
|
| 13 |
+
password: cry_me_a_r1ver
|
| 14 |
+
|
| 15 |
+
tls:
|
| 16 |
+
sni: another.example.com
|
| 17 |
+
insecure: true
|
| 18 |
+
pinSHA256: 114515DEADBEEF
|
| 19 |
+
ca: custom_ca.crt
|
| 20 |
+
|
| 21 |
+
quic:
|
| 22 |
+
initStreamReceiveWindow: 1145141
|
| 23 |
+
maxStreamReceiveWindow: 1145142
|
| 24 |
+
initConnReceiveWindow: 1145143
|
| 25 |
+
maxConnReceiveWindow: 1145144
|
| 26 |
+
maxIdleTimeout: 10s
|
| 27 |
+
keepAlivePeriod: 4s
|
| 28 |
+
disablePathMTUDiscovery: true
|
| 29 |
+
sockopts:
|
| 30 |
+
bindInterface: eth0
|
| 31 |
+
fwmark: 1234
|
| 32 |
+
fdControlUnixSocket: test.sock
|
| 33 |
+
|
| 34 |
+
bandwidth:
|
| 35 |
+
up: 200 mbps
|
| 36 |
+
down: 1 gbps
|
| 37 |
+
|
| 38 |
+
fastOpen: true
|
| 39 |
+
|
| 40 |
+
lazy: true
|
| 41 |
+
|
| 42 |
+
socks5:
|
| 43 |
+
listen: 127.0.0.1:1080
|
| 44 |
+
username: anon
|
| 45 |
+
password: bro
|
| 46 |
+
disableUDP: true
|
| 47 |
+
|
| 48 |
+
http:
|
| 49 |
+
listen: 127.0.0.1:8080
|
| 50 |
+
username: qqq
|
| 51 |
+
password: bruh
|
| 52 |
+
realm: martian
|
| 53 |
+
|
| 54 |
+
tcpForwarding:
|
| 55 |
+
- listen: 127.0.0.1:8088
|
| 56 |
+
remote: internal.example.com:80
|
| 57 |
+
|
| 58 |
+
udpForwarding:
|
| 59 |
+
- listen: 127.0.0.1:5353
|
| 60 |
+
remote: internal.example.com:53
|
| 61 |
+
timeout: 50s
|
| 62 |
+
|
| 63 |
+
tcpTProxy:
|
| 64 |
+
listen: 127.0.0.1:2500
|
| 65 |
+
|
| 66 |
+
udpTProxy:
|
| 67 |
+
listen: 127.0.0.1:2501
|
| 68 |
+
timeout: 20s
|
| 69 |
+
|
| 70 |
+
tcpRedirect:
|
| 71 |
+
listen: 127.0.0.1:3500
|
| 72 |
+
|
| 73 |
+
tun:
|
| 74 |
+
name: "hytun"
|
| 75 |
+
mtu: 1500
|
| 76 |
+
timeout: 1m
|
| 77 |
+
address:
|
| 78 |
+
ipv4: 100.100.100.101/30
|
| 79 |
+
ipv6: 2001::ffff:ffff:ffff:fff1/126
|
| 80 |
+
route:
|
| 81 |
+
strict: true
|
| 82 |
+
ipv4: [ 0.0.0.0/0 ]
|
| 83 |
+
ipv6: [ "2000::/3" ]
|
| 84 |
+
ipv4Exclude: [ 192.0.2.1/32 ]
|
| 85 |
+
ipv6Exclude: [ "2001:db8::1/128" ]
|
app/cmd/errors.go
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"fmt"
|
| 5 |
+
)
|
| 6 |
+
|
| 7 |
+
type configError struct {
|
| 8 |
+
Field string
|
| 9 |
+
Err error
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
func (e configError) Error() string {
|
| 13 |
+
return fmt.Sprintf("invalid config: %s: %s", e.Field, e.Err)
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
func (e configError) Unwrap() error {
|
| 17 |
+
return e.Err
|
| 18 |
+
}
|
app/cmd/ping.go
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"time"
|
| 5 |
+
|
| 6 |
+
"github.com/spf13/cobra"
|
| 7 |
+
"github.com/spf13/viper"
|
| 8 |
+
"go.uber.org/zap"
|
| 9 |
+
|
| 10 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
// pingCmd represents the ping command
|
| 14 |
+
var pingCmd = &cobra.Command{
|
| 15 |
+
Use: "ping address",
|
| 16 |
+
Short: "Ping mode",
|
| 17 |
+
Long: "Perform a TCP ping to a specified remote address through the proxy server. Can be used as a simple connectivity test.",
|
| 18 |
+
Run: runPing,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
func init() {
|
| 22 |
+
rootCmd.AddCommand(pingCmd)
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
func runPing(cmd *cobra.Command, args []string) {
|
| 26 |
+
logger.Info("ping mode")
|
| 27 |
+
|
| 28 |
+
if len(args) != 1 {
|
| 29 |
+
logger.Fatal("must specify one and only one address")
|
| 30 |
+
}
|
| 31 |
+
addr := args[0]
|
| 32 |
+
|
| 33 |
+
if err := viper.ReadInConfig(); err != nil {
|
| 34 |
+
logger.Fatal("failed to read client config", zap.Error(err))
|
| 35 |
+
}
|
| 36 |
+
var config clientConfig
|
| 37 |
+
if err := viper.Unmarshal(&config); err != nil {
|
| 38 |
+
logger.Fatal("failed to parse client config", zap.Error(err))
|
| 39 |
+
}
|
| 40 |
+
hyConfig, err := config.Config()
|
| 41 |
+
if err != nil {
|
| 42 |
+
logger.Fatal("failed to load client config", zap.Error(err))
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
c, info, err := client.NewClient(hyConfig)
|
| 46 |
+
if err != nil {
|
| 47 |
+
logger.Fatal("failed to initialize client", zap.Error(err))
|
| 48 |
+
}
|
| 49 |
+
defer c.Close()
|
| 50 |
+
logger.Info("connected to server",
|
| 51 |
+
zap.Bool("udpEnabled", info.UDPEnabled),
|
| 52 |
+
zap.Uint64("tx", info.Tx))
|
| 53 |
+
|
| 54 |
+
logger.Info("connecting", zap.String("addr", addr))
|
| 55 |
+
start := time.Now()
|
| 56 |
+
conn, err := c.TCP(addr)
|
| 57 |
+
if err != nil {
|
| 58 |
+
logger.Fatal("failed to connect", zap.Error(err), zap.String("time", time.Since(start).String()))
|
| 59 |
+
}
|
| 60 |
+
defer conn.Close()
|
| 61 |
+
|
| 62 |
+
logger.Info("connected", zap.String("time", time.Since(start).String()))
|
| 63 |
+
}
|
app/cmd/root.go
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"fmt"
|
| 5 |
+
"os"
|
| 6 |
+
"strconv"
|
| 7 |
+
"strings"
|
| 8 |
+
|
| 9 |
+
"github.com/spf13/cobra"
|
| 10 |
+
"github.com/spf13/viper"
|
| 11 |
+
"go.uber.org/zap"
|
| 12 |
+
"go.uber.org/zap/zapcore"
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
const (
|
| 16 |
+
appLogo = `
|
| 17 |
+
░█░█░█░█░█▀▀░▀█▀░█▀▀░█▀▄░▀█▀░█▀█░░░▀▀▄
|
| 18 |
+
░█▀█░░█░░▀▀█░░█░░█▀▀░█▀▄░░█░░█▀█░░░▄▀░
|
| 19 |
+
░▀░▀░░▀░░▀▀▀░░▀░░▀▀▀░▀░▀░▀▀▀░▀░▀░░░▀▀▀
|
| 20 |
+
`
|
| 21 |
+
appDesc = "a powerful, lightning fast and censorship resistant proxy"
|
| 22 |
+
appAuthors = "Aperture Internet Laboratory <https://github.com/apernet>"
|
| 23 |
+
|
| 24 |
+
appLogLevelEnv = "HYSTERIA_LOG_LEVEL"
|
| 25 |
+
appLogFormatEnv = "HYSTERIA_LOG_FORMAT"
|
| 26 |
+
appDisableUpdateCheckEnv = "HYSTERIA_DISABLE_UPDATE_CHECK"
|
| 27 |
+
appACMEDirEnv = "HYSTERIA_ACME_DIR"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
var (
|
| 31 |
+
// These values will be injected by the build system
|
| 32 |
+
appVersion = "Unknown"
|
| 33 |
+
appDate = "Unknown"
|
| 34 |
+
appType = "Unknown" // aka channel
|
| 35 |
+
appToolchain = "Unknown"
|
| 36 |
+
appCommit = "Unknown"
|
| 37 |
+
appPlatform = "Unknown"
|
| 38 |
+
appArch = "Unknown"
|
| 39 |
+
libVersion = "Unknown"
|
| 40 |
+
|
| 41 |
+
appVersionLong = fmt.Sprintf("Version:\t%s\n"+
|
| 42 |
+
"BuildDate:\t%s\n"+
|
| 43 |
+
"BuildType:\t%s\n"+
|
| 44 |
+
"Toolchain:\t%s\n"+
|
| 45 |
+
"CommitHash:\t%s\n"+
|
| 46 |
+
"Platform:\t%s\n"+
|
| 47 |
+
"Architecture:\t%s\n"+
|
| 48 |
+
"Libraries:\tquic-go=%s",
|
| 49 |
+
appVersion, appDate, appType, appToolchain, appCommit, appPlatform, appArch, libVersion)
|
| 50 |
+
|
| 51 |
+
appAboutLong = fmt.Sprintf("%s\n%s\n%s\n\n%s", appLogo, appDesc, appAuthors, appVersionLong)
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
var logger *zap.Logger
|
| 55 |
+
|
| 56 |
+
// Flags
|
| 57 |
+
var (
|
| 58 |
+
cfgFile string
|
| 59 |
+
logLevel string
|
| 60 |
+
logFormat string
|
| 61 |
+
disableUpdateCheck bool
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
var rootCmd = &cobra.Command{
|
| 65 |
+
Use: "hysteria",
|
| 66 |
+
Short: appDesc,
|
| 67 |
+
Long: appAboutLong,
|
| 68 |
+
Run: runClient, // Default to client mode
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
var logLevelMap = map[string]zapcore.Level{
|
| 72 |
+
"debug": zapcore.DebugLevel,
|
| 73 |
+
"info": zapcore.InfoLevel,
|
| 74 |
+
"warn": zapcore.WarnLevel,
|
| 75 |
+
"error": zapcore.ErrorLevel,
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
var logFormatMap = map[string]zapcore.EncoderConfig{
|
| 79 |
+
"console": {
|
| 80 |
+
TimeKey: "time",
|
| 81 |
+
LevelKey: "level",
|
| 82 |
+
NameKey: "logger",
|
| 83 |
+
MessageKey: "msg",
|
| 84 |
+
LineEnding: zapcore.DefaultLineEnding,
|
| 85 |
+
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
| 86 |
+
EncodeTime: zapcore.RFC3339TimeEncoder,
|
| 87 |
+
EncodeDuration: zapcore.SecondsDurationEncoder,
|
| 88 |
+
},
|
| 89 |
+
"json": {
|
| 90 |
+
TimeKey: "time",
|
| 91 |
+
LevelKey: "level",
|
| 92 |
+
NameKey: "logger",
|
| 93 |
+
MessageKey: "msg",
|
| 94 |
+
LineEnding: zapcore.DefaultLineEnding,
|
| 95 |
+
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
| 96 |
+
EncodeTime: zapcore.EpochMillisTimeEncoder,
|
| 97 |
+
EncodeDuration: zapcore.SecondsDurationEncoder,
|
| 98 |
+
},
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
func Execute() {
|
| 102 |
+
err := rootCmd.Execute()
|
| 103 |
+
if err != nil {
|
| 104 |
+
os.Exit(1)
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
func init() {
|
| 109 |
+
initFlags()
|
| 110 |
+
cobra.MousetrapHelpText = "" // Disable the mousetrap so Windows users can run the exe directly by double-clicking
|
| 111 |
+
cobra.OnInitialize(initConfig)
|
| 112 |
+
cobra.OnInitialize(initLogger) // initLogger must come after initConfig as it depends on config
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
func initFlags() {
|
| 116 |
+
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file")
|
| 117 |
+
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", envOrDefaultString(appLogLevelEnv, "info"), "log level")
|
| 118 |
+
rootCmd.PersistentFlags().StringVarP(&logFormat, "log-format", "f", envOrDefaultString(appLogFormatEnv, "console"), "log format")
|
| 119 |
+
rootCmd.PersistentFlags().BoolVar(&disableUpdateCheck, "disable-update-check", envOrDefaultBool(appDisableUpdateCheckEnv, false), "disable update check")
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
func initConfig() {
|
| 123 |
+
if cfgFile != "" {
|
| 124 |
+
viper.SetConfigFile(cfgFile)
|
| 125 |
+
} else {
|
| 126 |
+
viper.SetConfigName("config")
|
| 127 |
+
viper.SetConfigType("yaml")
|
| 128 |
+
viper.SupportedExts = append([]string{"yaml", "yml"}, viper.SupportedExts...)
|
| 129 |
+
viper.AddConfigPath(".")
|
| 130 |
+
viper.AddConfigPath("$HOME/.hysteria")
|
| 131 |
+
viper.AddConfigPath("/etc/hysteria/")
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
func initLogger() {
|
| 136 |
+
level, ok := logLevelMap[strings.ToLower(logLevel)]
|
| 137 |
+
if !ok {
|
| 138 |
+
fmt.Printf("unsupported log level: %s\n", logLevel)
|
| 139 |
+
os.Exit(1)
|
| 140 |
+
}
|
| 141 |
+
enc, ok := logFormatMap[strings.ToLower(logFormat)]
|
| 142 |
+
if !ok {
|
| 143 |
+
fmt.Printf("unsupported log format: %s\n", logFormat)
|
| 144 |
+
os.Exit(1)
|
| 145 |
+
}
|
| 146 |
+
c := zap.Config{
|
| 147 |
+
Level: zap.NewAtomicLevelAt(level),
|
| 148 |
+
DisableCaller: true,
|
| 149 |
+
DisableStacktrace: true,
|
| 150 |
+
Encoding: strings.ToLower(logFormat),
|
| 151 |
+
EncoderConfig: enc,
|
| 152 |
+
OutputPaths: []string{"stderr"},
|
| 153 |
+
ErrorOutputPaths: []string{"stderr"},
|
| 154 |
+
}
|
| 155 |
+
var err error
|
| 156 |
+
logger, err = c.Build()
|
| 157 |
+
if err != nil {
|
| 158 |
+
fmt.Printf("failed to initialize logger: %s\n", err)
|
| 159 |
+
os.Exit(1)
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
func envOrDefaultString(key, def string) string {
|
| 164 |
+
if v := os.Getenv(key); v != "" {
|
| 165 |
+
return v
|
| 166 |
+
}
|
| 167 |
+
return def
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
func envOrDefaultBool(key string, def bool) bool {
|
| 171 |
+
if v := os.Getenv(key); v != "" {
|
| 172 |
+
b, _ := strconv.ParseBool(v)
|
| 173 |
+
return b
|
| 174 |
+
}
|
| 175 |
+
return def
|
| 176 |
+
}
|
app/cmd/server.go
ADDED
|
@@ -0,0 +1,1051 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"context"
|
| 5 |
+
"crypto/tls"
|
| 6 |
+
"encoding/json"
|
| 7 |
+
"errors"
|
| 8 |
+
"fmt"
|
| 9 |
+
"net"
|
| 10 |
+
"net/http"
|
| 11 |
+
"net/http/httputil"
|
| 12 |
+
"net/url"
|
| 13 |
+
"os"
|
| 14 |
+
"strconv"
|
| 15 |
+
"strings"
|
| 16 |
+
"time"
|
| 17 |
+
|
| 18 |
+
"github.com/caddyserver/certmagic"
|
| 19 |
+
"github.com/libdns/cloudflare"
|
| 20 |
+
"github.com/libdns/duckdns"
|
| 21 |
+
"github.com/libdns/gandi"
|
| 22 |
+
"github.com/libdns/godaddy"
|
| 23 |
+
"github.com/libdns/namedotcom"
|
| 24 |
+
"github.com/libdns/vultr"
|
| 25 |
+
"github.com/mholt/acmez/acme"
|
| 26 |
+
"github.com/spf13/cobra"
|
| 27 |
+
"github.com/spf13/viper"
|
| 28 |
+
"go.uber.org/zap"
|
| 29 |
+
|
| 30 |
+
"github.com/apernet/hysteria/app/v2/internal/utils"
|
| 31 |
+
"github.com/apernet/hysteria/core/v2/server"
|
| 32 |
+
"github.com/apernet/hysteria/extras/v2/auth"
|
| 33 |
+
"github.com/apernet/hysteria/extras/v2/correctnet"
|
| 34 |
+
"github.com/apernet/hysteria/extras/v2/masq"
|
| 35 |
+
"github.com/apernet/hysteria/extras/v2/obfs"
|
| 36 |
+
"github.com/apernet/hysteria/extras/v2/outbounds"
|
| 37 |
+
"github.com/apernet/hysteria/extras/v2/sniff"
|
| 38 |
+
"github.com/apernet/hysteria/extras/v2/trafficlogger"
|
| 39 |
+
eUtils "github.com/apernet/hysteria/extras/v2/utils"
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
const (
|
| 43 |
+
defaultListenAddr = ":443"
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
var serverCmd = &cobra.Command{
|
| 47 |
+
Use: "server",
|
| 48 |
+
Short: "Server mode",
|
| 49 |
+
Run: runServer,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
func init() {
|
| 53 |
+
rootCmd.AddCommand(serverCmd)
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
type serverConfig struct {
|
| 57 |
+
Listen string `mapstructure:"listen"`
|
| 58 |
+
Obfs serverConfigObfs `mapstructure:"obfs"`
|
| 59 |
+
TLS *serverConfigTLS `mapstructure:"tls"`
|
| 60 |
+
ACME *serverConfigACME `mapstructure:"acme"`
|
| 61 |
+
QUIC serverConfigQUIC `mapstructure:"quic"`
|
| 62 |
+
Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"`
|
| 63 |
+
IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"`
|
| 64 |
+
SpeedTest bool `mapstructure:"speedTest"`
|
| 65 |
+
DisableUDP bool `mapstructure:"disableUDP"`
|
| 66 |
+
UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"`
|
| 67 |
+
Auth serverConfigAuth `mapstructure:"auth"`
|
| 68 |
+
Resolver serverConfigResolver `mapstructure:"resolver"`
|
| 69 |
+
Sniff serverConfigSniff `mapstructure:"sniff"`
|
| 70 |
+
ACL serverConfigACL `mapstructure:"acl"`
|
| 71 |
+
Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"`
|
| 72 |
+
TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"`
|
| 73 |
+
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
type serverConfigObfsSalamander struct {
|
| 77 |
+
Password string `mapstructure:"password"`
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
type serverConfigObfs struct {
|
| 81 |
+
Type string `mapstructure:"type"`
|
| 82 |
+
Salamander serverConfigObfsSalamander `mapstructure:"salamander"`
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
type serverConfigTLS struct {
|
| 86 |
+
Cert string `mapstructure:"cert"`
|
| 87 |
+
Key string `mapstructure:"key"`
|
| 88 |
+
SNIGuard string `mapstructure:"sniGuard"` // "disable", "dns-san", "strict"
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
type serverConfigACME struct {
|
| 92 |
+
// Common fields
|
| 93 |
+
Domains []string `mapstructure:"domains"`
|
| 94 |
+
Email string `mapstructure:"email"`
|
| 95 |
+
CA string `mapstructure:"ca"`
|
| 96 |
+
ListenHost string `mapstructure:"listenHost"`
|
| 97 |
+
Dir string `mapstructure:"dir"`
|
| 98 |
+
|
| 99 |
+
// Type selection
|
| 100 |
+
Type string `mapstructure:"type"`
|
| 101 |
+
HTTP serverConfigACMEHTTP `mapstructure:"http"`
|
| 102 |
+
TLS serverConfigACMETLS `mapstructure:"tls"`
|
| 103 |
+
DNS serverConfigACMEDNS `mapstructure:"dns"`
|
| 104 |
+
|
| 105 |
+
// Legacy fields for backwards compatibility
|
| 106 |
+
// Only applicable when Type is empty
|
| 107 |
+
DisableHTTP bool `mapstructure:"disableHTTP"`
|
| 108 |
+
DisableTLSALPN bool `mapstructure:"disableTLSALPN"`
|
| 109 |
+
AltHTTPPort int `mapstructure:"altHTTPPort"`
|
| 110 |
+
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
type serverConfigACMEHTTP struct {
|
| 114 |
+
AltPort int `mapstructure:"altPort"`
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
type serverConfigACMETLS struct {
|
| 118 |
+
AltPort int `mapstructure:"altPort"`
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
type serverConfigACMEDNS struct {
|
| 122 |
+
Name string `mapstructure:"name"`
|
| 123 |
+
Config map[string]string `mapstructure:"config"`
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
type serverConfigQUIC struct {
|
| 127 |
+
InitStreamReceiveWindow uint64 `mapstructure:"initStreamReceiveWindow"`
|
| 128 |
+
MaxStreamReceiveWindow uint64 `mapstructure:"maxStreamReceiveWindow"`
|
| 129 |
+
InitConnectionReceiveWindow uint64 `mapstructure:"initConnReceiveWindow"`
|
| 130 |
+
MaxConnectionReceiveWindow uint64 `mapstructure:"maxConnReceiveWindow"`
|
| 131 |
+
MaxIdleTimeout time.Duration `mapstructure:"maxIdleTimeout"`
|
| 132 |
+
MaxIncomingStreams int64 `mapstructure:"maxIncomingStreams"`
|
| 133 |
+
DisablePathMTUDiscovery bool `mapstructure:"disablePathMTUDiscovery"`
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
type serverConfigBandwidth struct {
|
| 137 |
+
Up string `mapstructure:"up"`
|
| 138 |
+
Down string `mapstructure:"down"`
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
type serverConfigAuthHTTP struct {
|
| 142 |
+
URL string `mapstructure:"url"`
|
| 143 |
+
Insecure bool `mapstructure:"insecure"`
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
type serverConfigAuth struct {
|
| 147 |
+
Type string `mapstructure:"type"`
|
| 148 |
+
Password string `mapstructure:"password"`
|
| 149 |
+
UserPass map[string]string `mapstructure:"userpass"`
|
| 150 |
+
HTTP serverConfigAuthHTTP `mapstructure:"http"`
|
| 151 |
+
Command string `mapstructure:"command"`
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
type serverConfigResolverTCP struct {
|
| 155 |
+
Addr string `mapstructure:"addr"`
|
| 156 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
type serverConfigResolverUDP struct {
|
| 160 |
+
Addr string `mapstructure:"addr"`
|
| 161 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
type serverConfigResolverTLS struct {
|
| 165 |
+
Addr string `mapstructure:"addr"`
|
| 166 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 167 |
+
SNI string `mapstructure:"sni"`
|
| 168 |
+
Insecure bool `mapstructure:"insecure"`
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
type serverConfigResolverHTTPS struct {
|
| 172 |
+
Addr string `mapstructure:"addr"`
|
| 173 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 174 |
+
SNI string `mapstructure:"sni"`
|
| 175 |
+
Insecure bool `mapstructure:"insecure"`
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
type serverConfigResolver struct {
|
| 179 |
+
Type string `mapstructure:"type"`
|
| 180 |
+
TCP serverConfigResolverTCP `mapstructure:"tcp"`
|
| 181 |
+
UDP serverConfigResolverUDP `mapstructure:"udp"`
|
| 182 |
+
TLS serverConfigResolverTLS `mapstructure:"tls"`
|
| 183 |
+
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
type serverConfigSniff struct {
|
| 187 |
+
Enable bool `mapstructure:"enable"`
|
| 188 |
+
Timeout time.Duration `mapstructure:"timeout"`
|
| 189 |
+
RewriteDomain bool `mapstructure:"rewriteDomain"`
|
| 190 |
+
TCPPorts string `mapstructure:"tcpPorts"`
|
| 191 |
+
UDPPorts string `mapstructure:"udpPorts"`
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
type serverConfigACL struct {
|
| 195 |
+
File string `mapstructure:"file"`
|
| 196 |
+
Inline []string `mapstructure:"inline"`
|
| 197 |
+
GeoIP string `mapstructure:"geoip"`
|
| 198 |
+
GeoSite string `mapstructure:"geosite"`
|
| 199 |
+
GeoUpdateInterval time.Duration `mapstructure:"geoUpdateInterval"`
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
type serverConfigOutboundDirect struct {
|
| 203 |
+
Mode string `mapstructure:"mode"`
|
| 204 |
+
BindIPv4 string `mapstructure:"bindIPv4"`
|
| 205 |
+
BindIPv6 string `mapstructure:"bindIPv6"`
|
| 206 |
+
BindDevice string `mapstructure:"bindDevice"`
|
| 207 |
+
FastOpen bool `mapstructure:"fastOpen"`
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
type serverConfigOutboundSOCKS5 struct {
|
| 211 |
+
Addr string `mapstructure:"addr"`
|
| 212 |
+
Username string `mapstructure:"username"`
|
| 213 |
+
Password string `mapstructure:"password"`
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
type serverConfigOutboundHTTP struct {
|
| 217 |
+
URL string `mapstructure:"url"`
|
| 218 |
+
Insecure bool `mapstructure:"insecure"`
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
type serverConfigOutboundEntry struct {
|
| 222 |
+
Name string `mapstructure:"name"`
|
| 223 |
+
Type string `mapstructure:"type"`
|
| 224 |
+
Direct serverConfigOutboundDirect `mapstructure:"direct"`
|
| 225 |
+
SOCKS5 serverConfigOutboundSOCKS5 `mapstructure:"socks5"`
|
| 226 |
+
HTTP serverConfigOutboundHTTP `mapstructure:"http"`
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
type serverConfigTrafficStats struct {
|
| 230 |
+
Listen string `mapstructure:"listen"`
|
| 231 |
+
Secret string `mapstructure:"secret"`
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
type serverConfigMasqueradeFile struct {
|
| 235 |
+
Dir string `mapstructure:"dir"`
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
type serverConfigMasqueradeProxy struct {
|
| 239 |
+
URL string `mapstructure:"url"`
|
| 240 |
+
RewriteHost bool `mapstructure:"rewriteHost"`
|
| 241 |
+
Insecure bool `mapstructure:"insecure"`
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
type serverConfigMasqueradeString struct {
|
| 245 |
+
Content string `mapstructure:"content"`
|
| 246 |
+
Headers map[string]string `mapstructure:"headers"`
|
| 247 |
+
StatusCode int `mapstructure:"statusCode"`
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
type serverConfigMasquerade struct {
|
| 251 |
+
Type string `mapstructure:"type"`
|
| 252 |
+
File serverConfigMasqueradeFile `mapstructure:"file"`
|
| 253 |
+
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
|
| 254 |
+
String serverConfigMasqueradeString `mapstructure:"string"`
|
| 255 |
+
ListenHTTP string `mapstructure:"listenHTTP"`
|
| 256 |
+
ListenHTTPS string `mapstructure:"listenHTTPS"`
|
| 257 |
+
ForceHTTPS bool `mapstructure:"forceHTTPS"`
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
func (c *serverConfig) fillConn(hyConfig *server.Config) error {
|
| 261 |
+
listenAddr := c.Listen
|
| 262 |
+
if listenAddr == "" {
|
| 263 |
+
listenAddr = defaultListenAddr
|
| 264 |
+
}
|
| 265 |
+
uAddr, err := net.ResolveUDPAddr("udp", listenAddr)
|
| 266 |
+
if err != nil {
|
| 267 |
+
return configError{Field: "listen", Err: err}
|
| 268 |
+
}
|
| 269 |
+
conn, err := correctnet.ListenUDP("udp", uAddr)
|
| 270 |
+
if err != nil {
|
| 271 |
+
return configError{Field: "listen", Err: err}
|
| 272 |
+
}
|
| 273 |
+
switch strings.ToLower(c.Obfs.Type) {
|
| 274 |
+
case "", "plain":
|
| 275 |
+
hyConfig.Conn = conn
|
| 276 |
+
return nil
|
| 277 |
+
case "salamander":
|
| 278 |
+
ob, err := obfs.NewSalamanderObfuscator([]byte(c.Obfs.Salamander.Password))
|
| 279 |
+
if err != nil {
|
| 280 |
+
return configError{Field: "obfs.salamander.password", Err: err}
|
| 281 |
+
}
|
| 282 |
+
hyConfig.Conn = obfs.WrapPacketConn(conn, ob)
|
| 283 |
+
return nil
|
| 284 |
+
default:
|
| 285 |
+
return configError{Field: "obfs.type", Err: errors.New("unsupported obfuscation type")}
|
| 286 |
+
}
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
func (c *serverConfig) fillTLSConfig(hyConfig *server.Config) error {
|
| 290 |
+
if c.TLS == nil && c.ACME == nil {
|
| 291 |
+
return configError{Field: "tls", Err: errors.New("must set either tls or acme")}
|
| 292 |
+
}
|
| 293 |
+
if c.TLS != nil && c.ACME != nil {
|
| 294 |
+
return configError{Field: "tls", Err: errors.New("cannot set both tls and acme")}
|
| 295 |
+
}
|
| 296 |
+
if c.TLS != nil {
|
| 297 |
+
// SNI guard
|
| 298 |
+
var sniGuard utils.SNIGuardFunc
|
| 299 |
+
switch strings.ToLower(c.TLS.SNIGuard) {
|
| 300 |
+
case "", "dns-san":
|
| 301 |
+
sniGuard = utils.SNIGuardDNSSAN
|
| 302 |
+
case "strict":
|
| 303 |
+
sniGuard = utils.SNIGuardStrict
|
| 304 |
+
case "disable":
|
| 305 |
+
sniGuard = nil
|
| 306 |
+
default:
|
| 307 |
+
return configError{Field: "tls.sniGuard", Err: errors.New("unsupported SNI guard")}
|
| 308 |
+
}
|
| 309 |
+
// Local TLS cert
|
| 310 |
+
if c.TLS.Cert == "" || c.TLS.Key == "" {
|
| 311 |
+
return configError{Field: "tls", Err: errors.New("empty cert or key path")}
|
| 312 |
+
}
|
| 313 |
+
certLoader := &utils.LocalCertificateLoader{
|
| 314 |
+
CertFile: c.TLS.Cert,
|
| 315 |
+
KeyFile: c.TLS.Key,
|
| 316 |
+
SNIGuard: sniGuard,
|
| 317 |
+
}
|
| 318 |
+
// Try loading the cert-key pair here to catch errors early
|
| 319 |
+
// (e.g. invalid files or insufficient permissions)
|
| 320 |
+
err := certLoader.InitializeCache()
|
| 321 |
+
if err != nil {
|
| 322 |
+
var pathErr *os.PathError
|
| 323 |
+
if errors.As(err, &pathErr) {
|
| 324 |
+
if pathErr.Path == c.TLS.Cert {
|
| 325 |
+
return configError{Field: "tls.cert", Err: pathErr}
|
| 326 |
+
}
|
| 327 |
+
if pathErr.Path == c.TLS.Key {
|
| 328 |
+
return configError{Field: "tls.key", Err: pathErr}
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
return configError{Field: "tls", Err: err}
|
| 332 |
+
}
|
| 333 |
+
// Use GetCertificate instead of Certificates so that
|
| 334 |
+
// users can update the cert without restarting the server.
|
| 335 |
+
hyConfig.TLSConfig.GetCertificate = certLoader.GetCertificate
|
| 336 |
+
} else {
|
| 337 |
+
// ACME
|
| 338 |
+
dataDir := c.ACME.Dir
|
| 339 |
+
if dataDir == "" {
|
| 340 |
+
// If not specified in the config, check the environment variable
|
| 341 |
+
// before resorting to the default "acme" value. The main reason
|
| 342 |
+
// we have this is so that our setup script can set it to the
|
| 343 |
+
// user's home directory.
|
| 344 |
+
dataDir = envOrDefaultString(appACMEDirEnv, "acme")
|
| 345 |
+
}
|
| 346 |
+
cmCfg := &certmagic.Config{
|
| 347 |
+
RenewalWindowRatio: certmagic.DefaultRenewalWindowRatio,
|
| 348 |
+
KeySource: certmagic.DefaultKeyGenerator,
|
| 349 |
+
Storage: &certmagic.FileStorage{Path: dataDir},
|
| 350 |
+
Logger: logger,
|
| 351 |
+
}
|
| 352 |
+
cmIssuer := certmagic.NewACMEIssuer(cmCfg, certmagic.ACMEIssuer{
|
| 353 |
+
Email: c.ACME.Email,
|
| 354 |
+
Agreed: true,
|
| 355 |
+
ListenHost: c.ACME.ListenHost,
|
| 356 |
+
Logger: logger,
|
| 357 |
+
})
|
| 358 |
+
switch strings.ToLower(c.ACME.CA) {
|
| 359 |
+
case "letsencrypt", "le", "":
|
| 360 |
+
// Default to Let's Encrypt
|
| 361 |
+
cmIssuer.CA = certmagic.LetsEncryptProductionCA
|
| 362 |
+
case "zerossl", "zero":
|
| 363 |
+
cmIssuer.CA = certmagic.ZeroSSLProductionCA
|
| 364 |
+
eab, err := genZeroSSLEAB(c.ACME.Email)
|
| 365 |
+
if err != nil {
|
| 366 |
+
return configError{Field: "acme.ca", Err: err}
|
| 367 |
+
}
|
| 368 |
+
cmIssuer.ExternalAccount = eab
|
| 369 |
+
default:
|
| 370 |
+
return configError{Field: "acme.ca", Err: errors.New("unsupported CA")}
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
switch strings.ToLower(c.ACME.Type) {
|
| 374 |
+
case "http":
|
| 375 |
+
cmIssuer.DisableHTTPChallenge = false
|
| 376 |
+
cmIssuer.DisableTLSALPNChallenge = true
|
| 377 |
+
cmIssuer.DNS01Solver = nil
|
| 378 |
+
cmIssuer.AltHTTPPort = c.ACME.HTTP.AltPort
|
| 379 |
+
case "tls":
|
| 380 |
+
cmIssuer.DisableHTTPChallenge = true
|
| 381 |
+
cmIssuer.DisableTLSALPNChallenge = false
|
| 382 |
+
cmIssuer.DNS01Solver = nil
|
| 383 |
+
cmIssuer.AltTLSALPNPort = c.ACME.TLS.AltPort
|
| 384 |
+
case "dns":
|
| 385 |
+
cmIssuer.DisableHTTPChallenge = true
|
| 386 |
+
cmIssuer.DisableTLSALPNChallenge = true
|
| 387 |
+
if c.ACME.DNS.Name == "" {
|
| 388 |
+
return configError{Field: "acme.dns.name", Err: errors.New("empty DNS provider name")}
|
| 389 |
+
}
|
| 390 |
+
if c.ACME.DNS.Config == nil {
|
| 391 |
+
return configError{Field: "acme.dns.config", Err: errors.New("empty DNS provider config")}
|
| 392 |
+
}
|
| 393 |
+
switch strings.ToLower(c.ACME.DNS.Name) {
|
| 394 |
+
case "cloudflare":
|
| 395 |
+
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
|
| 396 |
+
DNSProvider: &cloudflare.Provider{
|
| 397 |
+
APIToken: c.ACME.DNS.Config["cloudflare_api_token"],
|
| 398 |
+
},
|
| 399 |
+
}
|
| 400 |
+
case "duckdns":
|
| 401 |
+
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
|
| 402 |
+
DNSProvider: &duckdns.Provider{
|
| 403 |
+
APIToken: c.ACME.DNS.Config["duckdns_api_token"],
|
| 404 |
+
OverrideDomain: c.ACME.DNS.Config["duckdns_override_domain"],
|
| 405 |
+
},
|
| 406 |
+
}
|
| 407 |
+
case "gandi":
|
| 408 |
+
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
|
| 409 |
+
DNSProvider: &gandi.Provider{
|
| 410 |
+
BearerToken: c.ACME.DNS.Config["gandi_api_token"],
|
| 411 |
+
},
|
| 412 |
+
}
|
| 413 |
+
case "godaddy":
|
| 414 |
+
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
|
| 415 |
+
DNSProvider: &godaddy.Provider{
|
| 416 |
+
APIToken: c.ACME.DNS.Config["godaddy_api_token"],
|
| 417 |
+
},
|
| 418 |
+
}
|
| 419 |
+
case "namedotcom":
|
| 420 |
+
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
|
| 421 |
+
DNSProvider: &namedotcom.Provider{
|
| 422 |
+
Token: c.ACME.DNS.Config["namedotcom_token"],
|
| 423 |
+
User: c.ACME.DNS.Config["namedotcom_user"],
|
| 424 |
+
Server: c.ACME.DNS.Config["namedotcom_server"],
|
| 425 |
+
},
|
| 426 |
+
}
|
| 427 |
+
case "vultr":
|
| 428 |
+
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
|
| 429 |
+
DNSProvider: &vultr.Provider{
|
| 430 |
+
APIToken: c.ACME.DNS.Config["vultr_api_token"],
|
| 431 |
+
},
|
| 432 |
+
}
|
| 433 |
+
default:
|
| 434 |
+
return configError{Field: "acme.dns.name", Err: errors.New("unsupported DNS provider")}
|
| 435 |
+
}
|
| 436 |
+
case "":
|
| 437 |
+
// Legacy compatibility mode
|
| 438 |
+
cmIssuer.DisableHTTPChallenge = c.ACME.DisableHTTP
|
| 439 |
+
cmIssuer.DisableTLSALPNChallenge = c.ACME.DisableTLSALPN
|
| 440 |
+
cmIssuer.AltHTTPPort = c.ACME.AltHTTPPort
|
| 441 |
+
cmIssuer.AltTLSALPNPort = c.ACME.AltTLSALPNPort
|
| 442 |
+
default:
|
| 443 |
+
return configError{Field: "acme.type", Err: errors.New("unsupported ACME type")}
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
cmCfg.Issuers = []certmagic.Issuer{cmIssuer}
|
| 447 |
+
cmCache := certmagic.NewCache(certmagic.CacheOptions{
|
| 448 |
+
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
| 449 |
+
return cmCfg, nil
|
| 450 |
+
},
|
| 451 |
+
Logger: logger,
|
| 452 |
+
})
|
| 453 |
+
cmCfg = certmagic.New(cmCache, *cmCfg)
|
| 454 |
+
|
| 455 |
+
if len(c.ACME.Domains) == 0 {
|
| 456 |
+
return configError{Field: "acme.domains", Err: errors.New("empty domains")}
|
| 457 |
+
}
|
| 458 |
+
err := cmCfg.ManageSync(context.Background(), c.ACME.Domains)
|
| 459 |
+
if err != nil {
|
| 460 |
+
return configError{Field: "acme.domains", Err: err}
|
| 461 |
+
}
|
| 462 |
+
hyConfig.TLSConfig.GetCertificate = cmCfg.GetCertificate
|
| 463 |
+
}
|
| 464 |
+
return nil
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
func genZeroSSLEAB(email string) (*acme.EAB, error) {
|
| 468 |
+
req, err := http.NewRequest(
|
| 469 |
+
http.MethodPost,
|
| 470 |
+
"https://api.zerossl.com/acme/eab-credentials-email",
|
| 471 |
+
strings.NewReader(url.Values{"email": []string{email}}.Encode()),
|
| 472 |
+
)
|
| 473 |
+
if err != nil {
|
| 474 |
+
return nil, fmt.Errorf("failed to creare ZeroSSL EAB request: %w", err)
|
| 475 |
+
}
|
| 476 |
+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
| 477 |
+
req.Header.Set("User-Agent", certmagic.UserAgent)
|
| 478 |
+
resp, err := http.DefaultClient.Do(req)
|
| 479 |
+
if err != nil {
|
| 480 |
+
return nil, fmt.Errorf("failed to send ZeroSSL EAB request: %w", err)
|
| 481 |
+
}
|
| 482 |
+
defer func() { _ = resp.Body.Close() }()
|
| 483 |
+
|
| 484 |
+
var result struct {
|
| 485 |
+
Success bool `json:"success"`
|
| 486 |
+
Error struct {
|
| 487 |
+
Code int `json:"code"`
|
| 488 |
+
Type string `json:"type"`
|
| 489 |
+
} `json:"error"`
|
| 490 |
+
EABKID string `json:"eab_kid"`
|
| 491 |
+
EABHMACKey string `json:"eab_hmac_key"`
|
| 492 |
+
}
|
| 493 |
+
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
| 494 |
+
return nil, fmt.Errorf("failed decoding ZeroSSL EAB API response: %w", err)
|
| 495 |
+
}
|
| 496 |
+
if result.Error.Code != 0 {
|
| 497 |
+
return nil, fmt.Errorf("failed getting ZeroSSL EAB credentials: HTTP %d: %s (code %d)", resp.StatusCode, result.Error.Type, result.Error.Code)
|
| 498 |
+
}
|
| 499 |
+
if resp.StatusCode != http.StatusOK {
|
| 500 |
+
return nil, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode)
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
return &acme.EAB{
|
| 504 |
+
KeyID: result.EABKID,
|
| 505 |
+
MACKey: result.EABHMACKey,
|
| 506 |
+
}, nil
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
func (c *serverConfig) fillQUICConfig(hyConfig *server.Config) error {
|
| 510 |
+
hyConfig.QUICConfig = server.QUICConfig{
|
| 511 |
+
InitialStreamReceiveWindow: c.QUIC.InitStreamReceiveWindow,
|
| 512 |
+
MaxStreamReceiveWindow: c.QUIC.MaxStreamReceiveWindow,
|
| 513 |
+
InitialConnectionReceiveWindow: c.QUIC.InitConnectionReceiveWindow,
|
| 514 |
+
MaxConnectionReceiveWindow: c.QUIC.MaxConnectionReceiveWindow,
|
| 515 |
+
MaxIdleTimeout: c.QUIC.MaxIdleTimeout,
|
| 516 |
+
MaxIncomingStreams: c.QUIC.MaxIncomingStreams,
|
| 517 |
+
DisablePathMTUDiscovery: c.QUIC.DisablePathMTUDiscovery,
|
| 518 |
+
}
|
| 519 |
+
return nil
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
|
| 523 |
+
opts := outbounds.DirectOutboundOptions{}
|
| 524 |
+
switch strings.ToLower(c.Mode) {
|
| 525 |
+
case "", "auto":
|
| 526 |
+
opts.Mode = outbounds.DirectOutboundModeAuto
|
| 527 |
+
case "64":
|
| 528 |
+
opts.Mode = outbounds.DirectOutboundMode64
|
| 529 |
+
case "46":
|
| 530 |
+
opts.Mode = outbounds.DirectOutboundMode46
|
| 531 |
+
case "6":
|
| 532 |
+
opts.Mode = outbounds.DirectOutboundMode6
|
| 533 |
+
case "4":
|
| 534 |
+
opts.Mode = outbounds.DirectOutboundMode4
|
| 535 |
+
default:
|
| 536 |
+
return nil, configError{Field: "outbounds.direct.mode", Err: errors.New("unsupported mode")}
|
| 537 |
+
}
|
| 538 |
+
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
|
| 539 |
+
bindDevice := len(c.BindDevice) > 0
|
| 540 |
+
if bindIP && bindDevice {
|
| 541 |
+
return nil, configError{Field: "outbounds.direct", Err: errors.New("cannot bind both IP and device")}
|
| 542 |
+
}
|
| 543 |
+
if bindIP {
|
| 544 |
+
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
|
| 545 |
+
if len(c.BindIPv4) > 0 && ip4 == nil {
|
| 546 |
+
return nil, configError{Field: "outbounds.direct.bindIPv4", Err: errors.New("invalid IPv4 address")}
|
| 547 |
+
}
|
| 548 |
+
if len(c.BindIPv6) > 0 && ip6 == nil {
|
| 549 |
+
return nil, configError{Field: "outbounds.direct.bindIPv6", Err: errors.New("invalid IPv6 address")}
|
| 550 |
+
}
|
| 551 |
+
opts.BindIP4 = ip4
|
| 552 |
+
opts.BindIP6 = ip6
|
| 553 |
+
}
|
| 554 |
+
if bindDevice {
|
| 555 |
+
opts.DeviceName = c.BindDevice
|
| 556 |
+
}
|
| 557 |
+
opts.FastOpen = c.FastOpen
|
| 558 |
+
return outbounds.NewDirectOutboundWithOptions(opts)
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
|
| 562 |
+
if c.Addr == "" {
|
| 563 |
+
return nil, configError{Field: "outbounds.socks5.addr", Err: errors.New("empty socks5 address")}
|
| 564 |
+
}
|
| 565 |
+
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
func serverConfigOutboundHTTPToOutbound(c serverConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
|
| 569 |
+
if c.URL == "" {
|
| 570 |
+
return nil, configError{Field: "outbounds.http.url", Err: errors.New("empty http address")}
|
| 571 |
+
}
|
| 572 |
+
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
func (c *serverConfig) fillRequestHook(hyConfig *server.Config) error {
|
| 576 |
+
if c.Sniff.Enable {
|
| 577 |
+
s := &sniff.Sniffer{
|
| 578 |
+
Timeout: c.Sniff.Timeout,
|
| 579 |
+
RewriteDomain: c.Sniff.RewriteDomain,
|
| 580 |
+
}
|
| 581 |
+
if c.Sniff.TCPPorts != "" {
|
| 582 |
+
s.TCPPorts = eUtils.ParsePortUnion(c.Sniff.TCPPorts)
|
| 583 |
+
if s.TCPPorts == nil {
|
| 584 |
+
return configError{Field: "sniff.tcpPorts", Err: errors.New("invalid port union")}
|
| 585 |
+
}
|
| 586 |
+
}
|
| 587 |
+
if c.Sniff.UDPPorts != "" {
|
| 588 |
+
s.UDPPorts = eUtils.ParsePortUnion(c.Sniff.UDPPorts)
|
| 589 |
+
if s.UDPPorts == nil {
|
| 590 |
+
return configError{Field: "sniff.udpPorts", Err: errors.New("invalid port union")}
|
| 591 |
+
}
|
| 592 |
+
}
|
| 593 |
+
hyConfig.RequestHook = s
|
| 594 |
+
}
|
| 595 |
+
return nil
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
func (c *serverConfig) fillOutboundConfig(hyConfig *server.Config) error {
|
| 599 |
+
// Resolver, ACL, actual outbound are all implemented through the Outbound interface.
|
| 600 |
+
// Depending on the config, we build a chain like this:
|
| 601 |
+
// Resolver(ACL(Outbounds...))
|
| 602 |
+
|
| 603 |
+
// Outbounds
|
| 604 |
+
var obs []outbounds.OutboundEntry
|
| 605 |
+
if len(c.Outbounds) == 0 {
|
| 606 |
+
// Guarantee we have at least one outbound
|
| 607 |
+
obs = []outbounds.OutboundEntry{{
|
| 608 |
+
Name: "default",
|
| 609 |
+
Outbound: outbounds.NewDirectOutboundSimple(outbounds.DirectOutboundModeAuto),
|
| 610 |
+
}}
|
| 611 |
+
} else {
|
| 612 |
+
obs = make([]outbounds.OutboundEntry, len(c.Outbounds))
|
| 613 |
+
for i, entry := range c.Outbounds {
|
| 614 |
+
if entry.Name == "" {
|
| 615 |
+
return configError{Field: "outbounds.name", Err: errors.New("empty outbound name")}
|
| 616 |
+
}
|
| 617 |
+
var ob outbounds.PluggableOutbound
|
| 618 |
+
var err error
|
| 619 |
+
switch strings.ToLower(entry.Type) {
|
| 620 |
+
case "direct":
|
| 621 |
+
ob, err = serverConfigOutboundDirectToOutbound(entry.Direct)
|
| 622 |
+
case "socks5":
|
| 623 |
+
ob, err = serverConfigOutboundSOCKS5ToOutbound(entry.SOCKS5)
|
| 624 |
+
case "http":
|
| 625 |
+
ob, err = serverConfigOutboundHTTPToOutbound(entry.HTTP)
|
| 626 |
+
default:
|
| 627 |
+
err = configError{Field: "outbounds.type", Err: errors.New("unsupported outbound type")}
|
| 628 |
+
}
|
| 629 |
+
if err != nil {
|
| 630 |
+
return err
|
| 631 |
+
}
|
| 632 |
+
obs[i] = outbounds.OutboundEntry{Name: entry.Name, Outbound: ob}
|
| 633 |
+
}
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
var uOb outbounds.PluggableOutbound // "unified" outbound
|
| 637 |
+
|
| 638 |
+
// ACL
|
| 639 |
+
hasACL := false
|
| 640 |
+
if c.ACL.File != "" && len(c.ACL.Inline) > 0 {
|
| 641 |
+
return configError{Field: "acl", Err: errors.New("cannot set both acl.file and acl.inline")}
|
| 642 |
+
}
|
| 643 |
+
gLoader := &utils.GeoLoader{
|
| 644 |
+
GeoIPFilename: c.ACL.GeoIP,
|
| 645 |
+
GeoSiteFilename: c.ACL.GeoSite,
|
| 646 |
+
UpdateInterval: c.ACL.GeoUpdateInterval,
|
| 647 |
+
DownloadFunc: geoDownloadFunc,
|
| 648 |
+
DownloadErrFunc: geoDownloadErrFunc,
|
| 649 |
+
}
|
| 650 |
+
if c.ACL.File != "" {
|
| 651 |
+
hasACL = true
|
| 652 |
+
acl, err := outbounds.NewACLEngineFromFile(c.ACL.File, obs, gLoader)
|
| 653 |
+
if err != nil {
|
| 654 |
+
return configError{Field: "acl.file", Err: err}
|
| 655 |
+
}
|
| 656 |
+
uOb = acl
|
| 657 |
+
} else if len(c.ACL.Inline) > 0 {
|
| 658 |
+
hasACL = true
|
| 659 |
+
acl, err := outbounds.NewACLEngineFromString(strings.Join(c.ACL.Inline, "\n"), obs, gLoader)
|
| 660 |
+
if err != nil {
|
| 661 |
+
return configError{Field: "acl.inline", Err: err}
|
| 662 |
+
}
|
| 663 |
+
uOb = acl
|
| 664 |
+
} else {
|
| 665 |
+
// No ACL, use the first outbound
|
| 666 |
+
uOb = obs[0].Outbound
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
// Resolver
|
| 670 |
+
switch strings.ToLower(c.Resolver.Type) {
|
| 671 |
+
case "", "system":
|
| 672 |
+
if hasACL {
|
| 673 |
+
// If the user uses ACL, we must put a resolver in front of it,
|
| 674 |
+
// for IP rules to work on domain requests.
|
| 675 |
+
uOb = outbounds.NewSystemResolver(uOb)
|
| 676 |
+
}
|
| 677 |
+
// Otherwise we can just rely on outbound handling on its own.
|
| 678 |
+
case "tcp":
|
| 679 |
+
if c.Resolver.TCP.Addr == "" {
|
| 680 |
+
return configError{Field: "resolver.tcp.addr", Err: errors.New("empty resolver address")}
|
| 681 |
+
}
|
| 682 |
+
uOb = outbounds.NewStandardResolverTCP(c.Resolver.TCP.Addr, c.Resolver.TCP.Timeout, uOb)
|
| 683 |
+
case "udp":
|
| 684 |
+
if c.Resolver.UDP.Addr == "" {
|
| 685 |
+
return configError{Field: "resolver.udp.addr", Err: errors.New("empty resolver address")}
|
| 686 |
+
}
|
| 687 |
+
uOb = outbounds.NewStandardResolverUDP(c.Resolver.UDP.Addr, c.Resolver.UDP.Timeout, uOb)
|
| 688 |
+
case "tls", "tcp-tls":
|
| 689 |
+
if c.Resolver.TLS.Addr == "" {
|
| 690 |
+
return configError{Field: "resolver.tls.addr", Err: errors.New("empty resolver address")}
|
| 691 |
+
}
|
| 692 |
+
uOb = outbounds.NewStandardResolverTLS(c.Resolver.TLS.Addr, c.Resolver.TLS.Timeout, c.Resolver.TLS.SNI, c.Resolver.TLS.Insecure, uOb)
|
| 693 |
+
case "https", "http":
|
| 694 |
+
if c.Resolver.HTTPS.Addr == "" {
|
| 695 |
+
return configError{Field: "resolver.https.addr", Err: errors.New("empty resolver address")}
|
| 696 |
+
}
|
| 697 |
+
uOb = outbounds.NewDoHResolver(c.Resolver.HTTPS.Addr, c.Resolver.HTTPS.Timeout, c.Resolver.HTTPS.SNI, c.Resolver.HTTPS.Insecure, uOb)
|
| 698 |
+
default:
|
| 699 |
+
return configError{Field: "resolver.type", Err: errors.New("unsupported resolver type")}
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
// Speed test
|
| 703 |
+
if c.SpeedTest {
|
| 704 |
+
uOb = outbounds.NewSpeedtestHandler(uOb)
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
+
hyConfig.Outbound = &outbounds.PluggableOutboundAdapter{PluggableOutbound: uOb}
|
| 708 |
+
return nil
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
func (c *serverConfig) fillBandwidthConfig(hyConfig *server.Config) error {
|
| 712 |
+
var err error
|
| 713 |
+
if c.Bandwidth.Up != "" {
|
| 714 |
+
hyConfig.BandwidthConfig.MaxTx, err = utils.ConvBandwidth(c.Bandwidth.Up)
|
| 715 |
+
if err != nil {
|
| 716 |
+
return configError{Field: "bandwidth.up", Err: err}
|
| 717 |
+
}
|
| 718 |
+
}
|
| 719 |
+
if c.Bandwidth.Down != "" {
|
| 720 |
+
hyConfig.BandwidthConfig.MaxRx, err = utils.ConvBandwidth(c.Bandwidth.Down)
|
| 721 |
+
if err != nil {
|
| 722 |
+
return configError{Field: "bandwidth.down", Err: err}
|
| 723 |
+
}
|
| 724 |
+
}
|
| 725 |
+
return nil
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
func (c *serverConfig) fillIgnoreClientBandwidth(hyConfig *server.Config) error {
|
| 729 |
+
hyConfig.IgnoreClientBandwidth = c.IgnoreClientBandwidth
|
| 730 |
+
return nil
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
func (c *serverConfig) fillDisableUDP(hyConfig *server.Config) error {
|
| 734 |
+
hyConfig.DisableUDP = c.DisableUDP
|
| 735 |
+
return nil
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
func (c *serverConfig) fillUDPIdleTimeout(hyConfig *server.Config) error {
|
| 739 |
+
hyConfig.UDPIdleTimeout = c.UDPIdleTimeout
|
| 740 |
+
return nil
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
func (c *serverConfig) fillAuthenticator(hyConfig *server.Config) error {
|
| 744 |
+
if c.Auth.Type == "" {
|
| 745 |
+
return configError{Field: "auth.type", Err: errors.New("empty auth type")}
|
| 746 |
+
}
|
| 747 |
+
switch strings.ToLower(c.Auth.Type) {
|
| 748 |
+
case "password":
|
| 749 |
+
if c.Auth.Password == "" {
|
| 750 |
+
return configError{Field: "auth.password", Err: errors.New("empty auth password")}
|
| 751 |
+
}
|
| 752 |
+
hyConfig.Authenticator = &auth.PasswordAuthenticator{Password: c.Auth.Password}
|
| 753 |
+
return nil
|
| 754 |
+
case "userpass":
|
| 755 |
+
if len(c.Auth.UserPass) == 0 {
|
| 756 |
+
return configError{Field: "auth.userpass", Err: errors.New("empty auth userpass")}
|
| 757 |
+
}
|
| 758 |
+
hyConfig.Authenticator = auth.NewUserPassAuthenticator(c.Auth.UserPass)
|
| 759 |
+
return nil
|
| 760 |
+
case "http", "https":
|
| 761 |
+
if c.Auth.HTTP.URL == "" {
|
| 762 |
+
return configError{Field: "auth.http.url", Err: errors.New("empty auth http url")}
|
| 763 |
+
}
|
| 764 |
+
hyConfig.Authenticator = auth.NewHTTPAuthenticator(c.Auth.HTTP.URL, c.Auth.HTTP.Insecure)
|
| 765 |
+
return nil
|
| 766 |
+
case "command", "cmd":
|
| 767 |
+
if c.Auth.Command == "" {
|
| 768 |
+
return configError{Field: "auth.command", Err: errors.New("empty auth command")}
|
| 769 |
+
}
|
| 770 |
+
hyConfig.Authenticator = &auth.CommandAuthenticator{Cmd: c.Auth.Command}
|
| 771 |
+
return nil
|
| 772 |
+
default:
|
| 773 |
+
return configError{Field: "auth.type", Err: errors.New("unsupported auth type")}
|
| 774 |
+
}
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
func (c *serverConfig) fillEventLogger(hyConfig *server.Config) error {
|
| 778 |
+
hyConfig.EventLogger = &serverLogger{}
|
| 779 |
+
return nil
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
func (c *serverConfig) fillTrafficLogger(hyConfig *server.Config) error {
|
| 783 |
+
if c.TrafficStats.Listen != "" {
|
| 784 |
+
tss := trafficlogger.NewTrafficStatsServer(c.TrafficStats.Secret)
|
| 785 |
+
hyConfig.TrafficLogger = tss
|
| 786 |
+
go runTrafficStatsServer(c.TrafficStats.Listen, tss)
|
| 787 |
+
}
|
| 788 |
+
return nil
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
+
// fillMasqHandler must be called after fillConn, as we may need to extract the QUIC
|
| 792 |
+
// port number from Conn for MasqTCPServer.
|
| 793 |
+
func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
|
| 794 |
+
var handler http.Handler
|
| 795 |
+
switch strings.ToLower(c.Masquerade.Type) {
|
| 796 |
+
case "", "404":
|
| 797 |
+
handler = http.NotFoundHandler()
|
| 798 |
+
case "file":
|
| 799 |
+
if c.Masquerade.File.Dir == "" {
|
| 800 |
+
return configError{Field: "masquerade.file.dir", Err: errors.New("empty file directory")}
|
| 801 |
+
}
|
| 802 |
+
handler = http.FileServer(http.Dir(c.Masquerade.File.Dir))
|
| 803 |
+
case "proxy":
|
| 804 |
+
if c.Masquerade.Proxy.URL == "" {
|
| 805 |
+
return configError{Field: "masquerade.proxy.url", Err: errors.New("empty proxy url")}
|
| 806 |
+
}
|
| 807 |
+
u, err := url.Parse(c.Masquerade.Proxy.URL)
|
| 808 |
+
if err != nil {
|
| 809 |
+
return configError{Field: "masquerade.proxy.url", Err: err}
|
| 810 |
+
}
|
| 811 |
+
if u.Scheme != "http" && u.Scheme != "https" {
|
| 812 |
+
return configError{Field: "masquerade.proxy.url", Err: fmt.Errorf("unsupported protocol scheme \"%s\"", u.Scheme)}
|
| 813 |
+
}
|
| 814 |
+
transport := http.DefaultTransport
|
| 815 |
+
if c.Masquerade.Proxy.Insecure {
|
| 816 |
+
transport = &http.Transport{
|
| 817 |
+
TLSClientConfig: &tls.Config{
|
| 818 |
+
InsecureSkipVerify: true,
|
| 819 |
+
},
|
| 820 |
+
// use default configs from http.DefaultTransport
|
| 821 |
+
Proxy: http.ProxyFromEnvironment,
|
| 822 |
+
DialContext: (&net.Dialer{
|
| 823 |
+
Timeout: 30 * time.Second,
|
| 824 |
+
KeepAlive: 30 * time.Second,
|
| 825 |
+
}).DialContext,
|
| 826 |
+
ForceAttemptHTTP2: true,
|
| 827 |
+
MaxIdleConns: 100,
|
| 828 |
+
IdleConnTimeout: 90 * time.Second,
|
| 829 |
+
TLSHandshakeTimeout: 10 * time.Second,
|
| 830 |
+
ExpectContinueTimeout: 1 * time.Second,
|
| 831 |
+
}
|
| 832 |
+
}
|
| 833 |
+
handler = &httputil.ReverseProxy{
|
| 834 |
+
Rewrite: func(r *httputil.ProxyRequest) {
|
| 835 |
+
r.SetURL(u)
|
| 836 |
+
// SetURL rewrites the Host header,
|
| 837 |
+
// but we don't want that if rewriteHost is false
|
| 838 |
+
if !c.Masquerade.Proxy.RewriteHost {
|
| 839 |
+
r.Out.Host = r.In.Host
|
| 840 |
+
}
|
| 841 |
+
},
|
| 842 |
+
Transport: transport,
|
| 843 |
+
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
| 844 |
+
logger.Error("HTTP reverse proxy error", zap.Error(err))
|
| 845 |
+
w.WriteHeader(http.StatusBadGateway)
|
| 846 |
+
},
|
| 847 |
+
}
|
| 848 |
+
case "string":
|
| 849 |
+
if c.Masquerade.String.Content == "" {
|
| 850 |
+
return configError{Field: "masquerade.string.content", Err: errors.New("empty string content")}
|
| 851 |
+
}
|
| 852 |
+
if c.Masquerade.String.StatusCode != 0 &&
|
| 853 |
+
(c.Masquerade.String.StatusCode < 200 ||
|
| 854 |
+
c.Masquerade.String.StatusCode > 599 ||
|
| 855 |
+
c.Masquerade.String.StatusCode == 233) {
|
| 856 |
+
// 233 is reserved for Hysteria authentication
|
| 857 |
+
return configError{Field: "masquerade.string.statusCode", Err: errors.New("invalid status code (must be 200-599, except 233)")}
|
| 858 |
+
}
|
| 859 |
+
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
| 860 |
+
for k, v := range c.Masquerade.String.Headers {
|
| 861 |
+
w.Header().Set(k, v)
|
| 862 |
+
}
|
| 863 |
+
if c.Masquerade.String.StatusCode != 0 {
|
| 864 |
+
w.WriteHeader(c.Masquerade.String.StatusCode)
|
| 865 |
+
} else {
|
| 866 |
+
w.WriteHeader(http.StatusOK) // Use 200 OK by default
|
| 867 |
+
}
|
| 868 |
+
_, _ = w.Write([]byte(c.Masquerade.String.Content))
|
| 869 |
+
})
|
| 870 |
+
default:
|
| 871 |
+
return configError{Field: "masquerade.type", Err: errors.New("unsupported masquerade type")}
|
| 872 |
+
}
|
| 873 |
+
hyConfig.MasqHandler = &masqHandlerLogWrapper{H: handler, QUIC: true}
|
| 874 |
+
|
| 875 |
+
if c.Masquerade.ListenHTTP != "" || c.Masquerade.ListenHTTPS != "" {
|
| 876 |
+
if c.Masquerade.ListenHTTP != "" && c.Masquerade.ListenHTTPS == "" {
|
| 877 |
+
return configError{Field: "masquerade.listenHTTPS", Err: errors.New("having only HTTP server without HTTPS is not supported")}
|
| 878 |
+
}
|
| 879 |
+
s := masq.MasqTCPServer{
|
| 880 |
+
QUICPort: extractPortFromAddr(hyConfig.Conn.LocalAddr().String()),
|
| 881 |
+
HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
|
| 882 |
+
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false},
|
| 883 |
+
TLSConfig: &tls.Config{
|
| 884 |
+
Certificates: hyConfig.TLSConfig.Certificates,
|
| 885 |
+
GetCertificate: hyConfig.TLSConfig.GetCertificate,
|
| 886 |
+
},
|
| 887 |
+
ForceHTTPS: c.Masquerade.ForceHTTPS,
|
| 888 |
+
}
|
| 889 |
+
go runMasqTCPServer(&s, c.Masquerade.ListenHTTP, c.Masquerade.ListenHTTPS)
|
| 890 |
+
}
|
| 891 |
+
return nil
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
// Config validates the fields and returns a ready-to-use Hysteria server config
|
| 895 |
+
func (c *serverConfig) Config() (*server.Config, error) {
|
| 896 |
+
hyConfig := &server.Config{}
|
| 897 |
+
fillers := []func(*server.Config) error{
|
| 898 |
+
c.fillConn,
|
| 899 |
+
c.fillTLSConfig,
|
| 900 |
+
c.fillQUICConfig,
|
| 901 |
+
c.fillRequestHook,
|
| 902 |
+
c.fillOutboundConfig,
|
| 903 |
+
c.fillBandwidthConfig,
|
| 904 |
+
c.fillIgnoreClientBandwidth,
|
| 905 |
+
c.fillDisableUDP,
|
| 906 |
+
c.fillUDPIdleTimeout,
|
| 907 |
+
c.fillAuthenticator,
|
| 908 |
+
c.fillEventLogger,
|
| 909 |
+
c.fillTrafficLogger,
|
| 910 |
+
c.fillMasqHandler,
|
| 911 |
+
}
|
| 912 |
+
for _, f := range fillers {
|
| 913 |
+
if err := f(hyConfig); err != nil {
|
| 914 |
+
return nil, err
|
| 915 |
+
}
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
+
return hyConfig, nil
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
func runServer(cmd *cobra.Command, args []string) {
|
| 922 |
+
logger.Info("server mode")
|
| 923 |
+
|
| 924 |
+
if err := viper.ReadInConfig(); err != nil {
|
| 925 |
+
logger.Fatal("failed to read server config", zap.Error(err))
|
| 926 |
+
}
|
| 927 |
+
var config serverConfig
|
| 928 |
+
if err := viper.Unmarshal(&config); err != nil {
|
| 929 |
+
logger.Fatal("failed to parse server config", zap.Error(err))
|
| 930 |
+
}
|
| 931 |
+
hyConfig, err := config.Config()
|
| 932 |
+
if err != nil {
|
| 933 |
+
logger.Fatal("failed to load server config", zap.Error(err))
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
s, err := server.NewServer(hyConfig)
|
| 937 |
+
if err != nil {
|
| 938 |
+
logger.Fatal("failed to initialize server", zap.Error(err))
|
| 939 |
+
}
|
| 940 |
+
if config.Listen != "" {
|
| 941 |
+
logger.Info("server up and running", zap.String("listen", config.Listen))
|
| 942 |
+
} else {
|
| 943 |
+
logger.Info("server up and running", zap.String("listen", defaultListenAddr))
|
| 944 |
+
}
|
| 945 |
+
|
| 946 |
+
if !disableUpdateCheck {
|
| 947 |
+
go runCheckUpdateServer()
|
| 948 |
+
}
|
| 949 |
+
|
| 950 |
+
if err := s.Serve(); err != nil {
|
| 951 |
+
logger.Fatal("failed to serve", zap.Error(err))
|
| 952 |
+
}
|
| 953 |
+
}
|
| 954 |
+
|
| 955 |
+
func runTrafficStatsServer(listen string, handler http.Handler) {
|
| 956 |
+
logger.Info("traffic stats server up and running", zap.String("listen", listen))
|
| 957 |
+
if err := correctnet.HTTPListenAndServe(listen, handler); err != nil {
|
| 958 |
+
logger.Fatal("failed to serve traffic stats", zap.Error(err))
|
| 959 |
+
}
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string) {
|
| 963 |
+
errChan := make(chan error, 2)
|
| 964 |
+
if httpAddr != "" {
|
| 965 |
+
go func() {
|
| 966 |
+
logger.Info("masquerade HTTP server up and running", zap.String("listen", httpAddr))
|
| 967 |
+
errChan <- s.ListenAndServeHTTP(httpAddr)
|
| 968 |
+
}()
|
| 969 |
+
}
|
| 970 |
+
if httpsAddr != "" {
|
| 971 |
+
go func() {
|
| 972 |
+
logger.Info("masquerade HTTPS server up and running", zap.String("listen", httpsAddr))
|
| 973 |
+
errChan <- s.ListenAndServeHTTPS(httpsAddr)
|
| 974 |
+
}()
|
| 975 |
+
}
|
| 976 |
+
err := <-errChan
|
| 977 |
+
if err != nil {
|
| 978 |
+
logger.Fatal("failed to serve masquerade HTTP(S)", zap.Error(err))
|
| 979 |
+
}
|
| 980 |
+
}
|
| 981 |
+
|
| 982 |
+
func geoDownloadFunc(filename, url string) {
|
| 983 |
+
logger.Info("downloading database", zap.String("filename", filename), zap.String("url", url))
|
| 984 |
+
}
|
| 985 |
+
|
| 986 |
+
func geoDownloadErrFunc(err error) {
|
| 987 |
+
if err != nil {
|
| 988 |
+
logger.Error("failed to download database", zap.Error(err))
|
| 989 |
+
}
|
| 990 |
+
}
|
| 991 |
+
|
| 992 |
+
type serverLogger struct{}
|
| 993 |
+
|
| 994 |
+
func (l *serverLogger) Connect(addr net.Addr, id string, tx uint64) {
|
| 995 |
+
logger.Info("client connected", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint64("tx", tx))
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
func (l *serverLogger) Disconnect(addr net.Addr, id string, err error) {
|
| 999 |
+
logger.Info("client disconnected", zap.String("addr", addr.String()), zap.String("id", id), zap.Error(err))
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
func (l *serverLogger) TCPRequest(addr net.Addr, id, reqAddr string) {
|
| 1003 |
+
logger.Debug("TCP request", zap.String("addr", addr.String()), zap.String("id", id), zap.String("reqAddr", reqAddr))
|
| 1004 |
+
}
|
| 1005 |
+
|
| 1006 |
+
func (l *serverLogger) TCPError(addr net.Addr, id, reqAddr string, err error) {
|
| 1007 |
+
if err == nil {
|
| 1008 |
+
logger.Debug("TCP closed", zap.String("addr", addr.String()), zap.String("id", id), zap.String("reqAddr", reqAddr))
|
| 1009 |
+
} else {
|
| 1010 |
+
logger.Warn("TCP error", zap.String("addr", addr.String()), zap.String("id", id), zap.String("reqAddr", reqAddr), zap.Error(err))
|
| 1011 |
+
}
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
func (l *serverLogger) UDPRequest(addr net.Addr, id string, sessionID uint32, reqAddr string) {
|
| 1015 |
+
logger.Debug("UDP request", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint32("sessionID", sessionID), zap.String("reqAddr", reqAddr))
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
func (l *serverLogger) UDPError(addr net.Addr, id string, sessionID uint32, err error) {
|
| 1019 |
+
if err == nil {
|
| 1020 |
+
logger.Debug("UDP closed", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint32("sessionID", sessionID))
|
| 1021 |
+
} else {
|
| 1022 |
+
logger.Warn("UDP error", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint32("sessionID", sessionID), zap.Error(err))
|
| 1023 |
+
}
|
| 1024 |
+
}
|
| 1025 |
+
|
| 1026 |
+
type masqHandlerLogWrapper struct {
|
| 1027 |
+
H http.Handler
|
| 1028 |
+
QUIC bool
|
| 1029 |
+
}
|
| 1030 |
+
|
| 1031 |
+
func (m *masqHandlerLogWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
| 1032 |
+
logger.Debug("masquerade request",
|
| 1033 |
+
zap.String("addr", r.RemoteAddr),
|
| 1034 |
+
zap.String("method", r.Method),
|
| 1035 |
+
zap.String("host", r.Host),
|
| 1036 |
+
zap.String("url", r.URL.String()),
|
| 1037 |
+
zap.Bool("quic", m.QUIC))
|
| 1038 |
+
m.H.ServeHTTP(w, r)
|
| 1039 |
+
}
|
| 1040 |
+
|
| 1041 |
+
func extractPortFromAddr(addr string) int {
|
| 1042 |
+
_, portStr, err := net.SplitHostPort(addr)
|
| 1043 |
+
if err != nil {
|
| 1044 |
+
return 0
|
| 1045 |
+
}
|
| 1046 |
+
port, err := strconv.Atoi(portStr)
|
| 1047 |
+
if err != nil {
|
| 1048 |
+
return 0
|
| 1049 |
+
}
|
| 1050 |
+
return port
|
| 1051 |
+
}
|
app/cmd/server_test.go
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"testing"
|
| 5 |
+
"time"
|
| 6 |
+
|
| 7 |
+
"github.com/stretchr/testify/assert"
|
| 8 |
+
|
| 9 |
+
"github.com/spf13/viper"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
// TestServerConfig tests the parsing of the server config
|
| 13 |
+
func TestServerConfig(t *testing.T) {
|
| 14 |
+
viper.SetConfigFile("server_test.yaml")
|
| 15 |
+
err := viper.ReadInConfig()
|
| 16 |
+
assert.NoError(t, err)
|
| 17 |
+
var config serverConfig
|
| 18 |
+
err = viper.Unmarshal(&config)
|
| 19 |
+
assert.NoError(t, err)
|
| 20 |
+
assert.Equal(t, config, serverConfig{
|
| 21 |
+
Listen: ":8443",
|
| 22 |
+
Obfs: serverConfigObfs{
|
| 23 |
+
Type: "salamander",
|
| 24 |
+
Salamander: serverConfigObfsSalamander{
|
| 25 |
+
Password: "cry_me_a_r1ver",
|
| 26 |
+
},
|
| 27 |
+
},
|
| 28 |
+
TLS: &serverConfigTLS{
|
| 29 |
+
Cert: "some.crt",
|
| 30 |
+
Key: "some.key",
|
| 31 |
+
SNIGuard: "strict",
|
| 32 |
+
},
|
| 33 |
+
ACME: &serverConfigACME{
|
| 34 |
+
Domains: []string{
|
| 35 |
+
"sub1.example.com",
|
| 36 |
+
"sub2.example.com",
|
| 37 |
+
},
|
| 38 |
+
Email: "haha@cringe.net",
|
| 39 |
+
CA: "zero",
|
| 40 |
+
ListenHost: "127.0.0.9",
|
| 41 |
+
Dir: "random_dir",
|
| 42 |
+
Type: "dns",
|
| 43 |
+
HTTP: serverConfigACMEHTTP{
|
| 44 |
+
AltPort: 8888,
|
| 45 |
+
},
|
| 46 |
+
TLS: serverConfigACMETLS{
|
| 47 |
+
AltPort: 44333,
|
| 48 |
+
},
|
| 49 |
+
DNS: serverConfigACMEDNS{
|
| 50 |
+
Name: "gomommy",
|
| 51 |
+
Config: map[string]string{
|
| 52 |
+
"key1": "value1",
|
| 53 |
+
"key2": "value2",
|
| 54 |
+
},
|
| 55 |
+
},
|
| 56 |
+
DisableHTTP: true,
|
| 57 |
+
DisableTLSALPN: true,
|
| 58 |
+
AltHTTPPort: 8080,
|
| 59 |
+
AltTLSALPNPort: 4433,
|
| 60 |
+
},
|
| 61 |
+
QUIC: serverConfigQUIC{
|
| 62 |
+
InitStreamReceiveWindow: 77881,
|
| 63 |
+
MaxStreamReceiveWindow: 77882,
|
| 64 |
+
InitConnectionReceiveWindow: 77883,
|
| 65 |
+
MaxConnectionReceiveWindow: 77884,
|
| 66 |
+
MaxIdleTimeout: 999 * time.Second,
|
| 67 |
+
MaxIncomingStreams: 256,
|
| 68 |
+
DisablePathMTUDiscovery: true,
|
| 69 |
+
},
|
| 70 |
+
Bandwidth: serverConfigBandwidth{
|
| 71 |
+
Up: "500 mbps",
|
| 72 |
+
Down: "100 mbps",
|
| 73 |
+
},
|
| 74 |
+
IgnoreClientBandwidth: true,
|
| 75 |
+
SpeedTest: true,
|
| 76 |
+
DisableUDP: true,
|
| 77 |
+
UDPIdleTimeout: 120 * time.Second,
|
| 78 |
+
Auth: serverConfigAuth{
|
| 79 |
+
Type: "password",
|
| 80 |
+
Password: "goofy_ahh_password",
|
| 81 |
+
UserPass: map[string]string{
|
| 82 |
+
"yolo": "swag",
|
| 83 |
+
"lol": "kek",
|
| 84 |
+
"foo": "bar",
|
| 85 |
+
},
|
| 86 |
+
HTTP: serverConfigAuthHTTP{
|
| 87 |
+
URL: "http://127.0.0.1:5000/auth",
|
| 88 |
+
Insecure: true,
|
| 89 |
+
},
|
| 90 |
+
Command: "/etc/some_command",
|
| 91 |
+
},
|
| 92 |
+
Resolver: serverConfigResolver{
|
| 93 |
+
Type: "udp",
|
| 94 |
+
TCP: serverConfigResolverTCP{
|
| 95 |
+
Addr: "123.123.123.123:5353",
|
| 96 |
+
Timeout: 4 * time.Second,
|
| 97 |
+
},
|
| 98 |
+
UDP: serverConfigResolverUDP{
|
| 99 |
+
Addr: "4.6.8.0:53",
|
| 100 |
+
Timeout: 2 * time.Second,
|
| 101 |
+
},
|
| 102 |
+
TLS: serverConfigResolverTLS{
|
| 103 |
+
Addr: "dot.yolo.com:8853",
|
| 104 |
+
Timeout: 10 * time.Second,
|
| 105 |
+
SNI: "server1.yolo.net",
|
| 106 |
+
Insecure: true,
|
| 107 |
+
},
|
| 108 |
+
HTTPS: serverConfigResolverHTTPS{
|
| 109 |
+
Addr: "cringe.ahh.cc",
|
| 110 |
+
Timeout: 5 * time.Second,
|
| 111 |
+
SNI: "real.stuff.net",
|
| 112 |
+
Insecure: true,
|
| 113 |
+
},
|
| 114 |
+
},
|
| 115 |
+
Sniff: serverConfigSniff{
|
| 116 |
+
Enable: true,
|
| 117 |
+
Timeout: 1 * time.Second,
|
| 118 |
+
RewriteDomain: true,
|
| 119 |
+
TCPPorts: "80,443,1000-2000",
|
| 120 |
+
UDPPorts: "443",
|
| 121 |
+
},
|
| 122 |
+
ACL: serverConfigACL{
|
| 123 |
+
File: "chnroute.txt",
|
| 124 |
+
Inline: []string{
|
| 125 |
+
"lmao(ok)",
|
| 126 |
+
"kek(cringe,boba,tea)",
|
| 127 |
+
},
|
| 128 |
+
GeoIP: "some.dat",
|
| 129 |
+
GeoSite: "some_site.dat",
|
| 130 |
+
GeoUpdateInterval: 168 * time.Hour,
|
| 131 |
+
},
|
| 132 |
+
Outbounds: []serverConfigOutboundEntry{
|
| 133 |
+
{
|
| 134 |
+
Name: "goodstuff",
|
| 135 |
+
Type: "direct",
|
| 136 |
+
Direct: serverConfigOutboundDirect{
|
| 137 |
+
Mode: "64",
|
| 138 |
+
BindIPv4: "2.4.6.8",
|
| 139 |
+
BindIPv6: "0:0:0:0:0:ffff:0204:0608",
|
| 140 |
+
BindDevice: "eth233",
|
| 141 |
+
FastOpen: true,
|
| 142 |
+
},
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
Name: "badstuff",
|
| 146 |
+
Type: "socks5",
|
| 147 |
+
SOCKS5: serverConfigOutboundSOCKS5{
|
| 148 |
+
Addr: "shady.proxy.ru:1080",
|
| 149 |
+
Username: "hackerman",
|
| 150 |
+
Password: "Elliot Alderson",
|
| 151 |
+
},
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
Name: "weirdstuff",
|
| 155 |
+
Type: "http",
|
| 156 |
+
HTTP: serverConfigOutboundHTTP{
|
| 157 |
+
URL: "https://eyy.lmao:4443/goofy",
|
| 158 |
+
Insecure: true,
|
| 159 |
+
},
|
| 160 |
+
},
|
| 161 |
+
},
|
| 162 |
+
TrafficStats: serverConfigTrafficStats{
|
| 163 |
+
Listen: ":9999",
|
| 164 |
+
Secret: "its_me_mario",
|
| 165 |
+
},
|
| 166 |
+
Masquerade: serverConfigMasquerade{
|
| 167 |
+
Type: "proxy",
|
| 168 |
+
File: serverConfigMasqueradeFile{
|
| 169 |
+
Dir: "/www/masq",
|
| 170 |
+
},
|
| 171 |
+
Proxy: serverConfigMasqueradeProxy{
|
| 172 |
+
URL: "https://some.site.net",
|
| 173 |
+
RewriteHost: true,
|
| 174 |
+
Insecure: true,
|
| 175 |
+
},
|
| 176 |
+
String: serverConfigMasqueradeString{
|
| 177 |
+
Content: "aint nothin here",
|
| 178 |
+
Headers: map[string]string{
|
| 179 |
+
"content-type": "text/plain",
|
| 180 |
+
"custom-haha": "lol",
|
| 181 |
+
},
|
| 182 |
+
StatusCode: 418,
|
| 183 |
+
},
|
| 184 |
+
ListenHTTP: ":80",
|
| 185 |
+
ListenHTTPS: ":443",
|
| 186 |
+
ForceHTTPS: true,
|
| 187 |
+
},
|
| 188 |
+
})
|
| 189 |
+
}
|
app/cmd/server_test.yaml
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
listen: :8443
|
| 2 |
+
|
| 3 |
+
obfs:
|
| 4 |
+
type: salamander
|
| 5 |
+
salamander:
|
| 6 |
+
password: cry_me_a_r1ver
|
| 7 |
+
|
| 8 |
+
tls:
|
| 9 |
+
cert: some.crt
|
| 10 |
+
key: some.key
|
| 11 |
+
sniGuard: strict
|
| 12 |
+
|
| 13 |
+
acme:
|
| 14 |
+
domains:
|
| 15 |
+
- sub1.example.com
|
| 16 |
+
- sub2.example.com
|
| 17 |
+
email: haha@cringe.net
|
| 18 |
+
ca: zero
|
| 19 |
+
listenHost: 127.0.0.9
|
| 20 |
+
dir: random_dir
|
| 21 |
+
type: dns
|
| 22 |
+
http:
|
| 23 |
+
altPort: 8888
|
| 24 |
+
tls:
|
| 25 |
+
altPort: 44333
|
| 26 |
+
dns:
|
| 27 |
+
name: gomommy
|
| 28 |
+
config:
|
| 29 |
+
key1: value1
|
| 30 |
+
key2: value2
|
| 31 |
+
disableHTTP: true
|
| 32 |
+
disableTLSALPN: true
|
| 33 |
+
altHTTPPort: 8080
|
| 34 |
+
altTLSALPNPort: 4433
|
| 35 |
+
|
| 36 |
+
quic:
|
| 37 |
+
initStreamReceiveWindow: 77881
|
| 38 |
+
maxStreamReceiveWindow: 77882
|
| 39 |
+
initConnReceiveWindow: 77883
|
| 40 |
+
maxConnReceiveWindow: 77884
|
| 41 |
+
maxIdleTimeout: 999s
|
| 42 |
+
maxIncomingStreams: 256
|
| 43 |
+
disablePathMTUDiscovery: true
|
| 44 |
+
|
| 45 |
+
bandwidth:
|
| 46 |
+
up: 500 mbps
|
| 47 |
+
down: 100 mbps
|
| 48 |
+
|
| 49 |
+
ignoreClientBandwidth: true
|
| 50 |
+
|
| 51 |
+
speedTest: true
|
| 52 |
+
|
| 53 |
+
disableUDP: true
|
| 54 |
+
udpIdleTimeout: 120s
|
| 55 |
+
|
| 56 |
+
auth:
|
| 57 |
+
type: password
|
| 58 |
+
password: goofy_ahh_password
|
| 59 |
+
userpass:
|
| 60 |
+
yolo: swag
|
| 61 |
+
lol: kek
|
| 62 |
+
foo: bar
|
| 63 |
+
http:
|
| 64 |
+
url: http://127.0.0.1:5000/auth
|
| 65 |
+
insecure: true
|
| 66 |
+
command: /etc/some_command
|
| 67 |
+
|
| 68 |
+
resolver:
|
| 69 |
+
type: udp
|
| 70 |
+
tcp:
|
| 71 |
+
addr: 123.123.123.123:5353
|
| 72 |
+
timeout: 4s
|
| 73 |
+
udp:
|
| 74 |
+
addr: 4.6.8.0:53
|
| 75 |
+
timeout: 2s
|
| 76 |
+
tls:
|
| 77 |
+
addr: dot.yolo.com:8853
|
| 78 |
+
timeout: 10s
|
| 79 |
+
sni: server1.yolo.net
|
| 80 |
+
insecure: true
|
| 81 |
+
https:
|
| 82 |
+
addr: cringe.ahh.cc
|
| 83 |
+
timeout: 5s
|
| 84 |
+
sni: real.stuff.net
|
| 85 |
+
insecure: true
|
| 86 |
+
|
| 87 |
+
sniff:
|
| 88 |
+
enable: true
|
| 89 |
+
timeout: 1s
|
| 90 |
+
rewriteDomain: true
|
| 91 |
+
tcpPorts: 80,443,1000-2000
|
| 92 |
+
udpPorts: 443
|
| 93 |
+
|
| 94 |
+
acl:
|
| 95 |
+
file: chnroute.txt
|
| 96 |
+
inline:
|
| 97 |
+
- lmao(ok)
|
| 98 |
+
- kek(cringe,boba,tea)
|
| 99 |
+
geoip: some.dat
|
| 100 |
+
geosite: some_site.dat
|
| 101 |
+
geoUpdateInterval: 168h
|
| 102 |
+
|
| 103 |
+
outbounds:
|
| 104 |
+
- name: goodstuff
|
| 105 |
+
type: direct
|
| 106 |
+
direct:
|
| 107 |
+
mode: 64
|
| 108 |
+
bindIPv4: 2.4.6.8
|
| 109 |
+
bindIPv6: 0:0:0:0:0:ffff:0204:0608
|
| 110 |
+
bindDevice: eth233
|
| 111 |
+
fastOpen: true
|
| 112 |
+
- name: badstuff
|
| 113 |
+
type: socks5
|
| 114 |
+
socks5:
|
| 115 |
+
addr: shady.proxy.ru:1080
|
| 116 |
+
username: hackerman
|
| 117 |
+
password: Elliot Alderson
|
| 118 |
+
- name: weirdstuff
|
| 119 |
+
type: http
|
| 120 |
+
http:
|
| 121 |
+
url: https://eyy.lmao:4443/goofy
|
| 122 |
+
insecure: true
|
| 123 |
+
|
| 124 |
+
trafficStats:
|
| 125 |
+
listen: :9999
|
| 126 |
+
secret: its_me_mario
|
| 127 |
+
|
| 128 |
+
masquerade:
|
| 129 |
+
type: proxy
|
| 130 |
+
file:
|
| 131 |
+
dir: /www/masq
|
| 132 |
+
proxy:
|
| 133 |
+
url: https://some.site.net
|
| 134 |
+
rewriteHost: true
|
| 135 |
+
insecure: true
|
| 136 |
+
string:
|
| 137 |
+
content: aint nothin here
|
| 138 |
+
headers:
|
| 139 |
+
content-type: text/plain
|
| 140 |
+
custom-haha: lol
|
| 141 |
+
statusCode: 418
|
| 142 |
+
listenHTTP: :80
|
| 143 |
+
listenHTTPS: :443
|
| 144 |
+
forceHTTPS: true
|
app/cmd/share.go
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"fmt"
|
| 5 |
+
|
| 6 |
+
"github.com/apernet/hysteria/app/v2/internal/utils"
|
| 7 |
+
"github.com/spf13/cobra"
|
| 8 |
+
"github.com/spf13/viper"
|
| 9 |
+
"go.uber.org/zap"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
var (
|
| 13 |
+
noText bool
|
| 14 |
+
withQR bool
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
// shareCmd represents the share command
|
| 18 |
+
var shareCmd = &cobra.Command{
|
| 19 |
+
Use: "share",
|
| 20 |
+
Short: "Generate share URI",
|
| 21 |
+
Long: "Generate a hysteria2:// URI from a client config for sharing",
|
| 22 |
+
Run: runShare,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
func init() {
|
| 26 |
+
initShareFlags()
|
| 27 |
+
rootCmd.AddCommand(shareCmd)
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
func initShareFlags() {
|
| 31 |
+
shareCmd.Flags().BoolVar(&noText, "notext", false, "do not show URI as text")
|
| 32 |
+
shareCmd.Flags().BoolVar(&withQR, "qr", false, "show URI as QR code")
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
func runShare(cmd *cobra.Command, args []string) {
|
| 36 |
+
if err := viper.ReadInConfig(); err != nil {
|
| 37 |
+
logger.Fatal("failed to read client config", zap.Error(err))
|
| 38 |
+
}
|
| 39 |
+
var config clientConfig
|
| 40 |
+
if err := viper.Unmarshal(&config); err != nil {
|
| 41 |
+
logger.Fatal("failed to parse client config", zap.Error(err))
|
| 42 |
+
}
|
| 43 |
+
if _, err := config.Config(); err != nil {
|
| 44 |
+
logger.Fatal("failed to load client config", zap.Error(err))
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
u := config.URI()
|
| 48 |
+
|
| 49 |
+
if !noText {
|
| 50 |
+
fmt.Println(u)
|
| 51 |
+
}
|
| 52 |
+
if withQR {
|
| 53 |
+
utils.PrintQR(u)
|
| 54 |
+
}
|
| 55 |
+
}
|
app/cmd/speedtest.go
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"errors"
|
| 5 |
+
"fmt"
|
| 6 |
+
"os"
|
| 7 |
+
"os/signal"
|
| 8 |
+
"syscall"
|
| 9 |
+
"time"
|
| 10 |
+
|
| 11 |
+
"github.com/spf13/cobra"
|
| 12 |
+
"github.com/spf13/viper"
|
| 13 |
+
"go.uber.org/zap"
|
| 14 |
+
|
| 15 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 16 |
+
hyErrors "github.com/apernet/hysteria/core/v2/errors"
|
| 17 |
+
"github.com/apernet/hysteria/extras/v2/outbounds"
|
| 18 |
+
"github.com/apernet/hysteria/extras/v2/outbounds/speedtest"
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
var (
|
| 22 |
+
skipDownload bool
|
| 23 |
+
skipUpload bool
|
| 24 |
+
dataSize uint32
|
| 25 |
+
useBytes bool
|
| 26 |
+
|
| 27 |
+
speedtestAddr = fmt.Sprintf("%s:%d", outbounds.SpeedtestDest, 0)
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
// speedtestCmd represents the speedtest command
|
| 31 |
+
var speedtestCmd = &cobra.Command{
|
| 32 |
+
Use: "speedtest",
|
| 33 |
+
Short: "Speed test mode",
|
| 34 |
+
Long: "Perform a speed test through the proxy server. The server must have speed test support enabled.",
|
| 35 |
+
Run: runSpeedtest,
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
func init() {
|
| 39 |
+
initSpeedtestFlags()
|
| 40 |
+
rootCmd.AddCommand(speedtestCmd)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
func initSpeedtestFlags() {
|
| 44 |
+
speedtestCmd.Flags().BoolVar(&skipDownload, "skip-download", false, "Skip download test")
|
| 45 |
+
speedtestCmd.Flags().BoolVar(&skipUpload, "skip-upload", false, "Skip upload test")
|
| 46 |
+
speedtestCmd.Flags().Uint32Var(&dataSize, "data-size", 1024*1024*100, "Data size for download and upload tests")
|
| 47 |
+
speedtestCmd.Flags().BoolVar(&useBytes, "use-bytes", false, "Use bytes per second instead of bits per second")
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
func runSpeedtest(cmd *cobra.Command, args []string) {
|
| 51 |
+
logger.Info("speed test mode")
|
| 52 |
+
|
| 53 |
+
if err := viper.ReadInConfig(); err != nil {
|
| 54 |
+
logger.Fatal("failed to read client config", zap.Error(err))
|
| 55 |
+
}
|
| 56 |
+
var config clientConfig
|
| 57 |
+
if err := viper.Unmarshal(&config); err != nil {
|
| 58 |
+
logger.Fatal("failed to parse client config", zap.Error(err))
|
| 59 |
+
}
|
| 60 |
+
hyConfig, err := config.Config()
|
| 61 |
+
if err != nil {
|
| 62 |
+
logger.Fatal("failed to load client config", zap.Error(err))
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
c, info, err := client.NewClient(hyConfig)
|
| 66 |
+
if err != nil {
|
| 67 |
+
logger.Fatal("failed to initialize client", zap.Error(err))
|
| 68 |
+
}
|
| 69 |
+
defer c.Close()
|
| 70 |
+
logger.Info("connected to server",
|
| 71 |
+
zap.Bool("udpEnabled", info.UDPEnabled),
|
| 72 |
+
zap.Uint64("tx", info.Tx))
|
| 73 |
+
|
| 74 |
+
signalChan := make(chan os.Signal, 1)
|
| 75 |
+
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
| 76 |
+
defer signal.Stop(signalChan)
|
| 77 |
+
|
| 78 |
+
runChan := make(chan struct{}, 1)
|
| 79 |
+
go func() {
|
| 80 |
+
if !skipDownload {
|
| 81 |
+
runDownloadTest(c)
|
| 82 |
+
}
|
| 83 |
+
if !skipUpload {
|
| 84 |
+
runUploadTest(c)
|
| 85 |
+
}
|
| 86 |
+
runChan <- struct{}{}
|
| 87 |
+
}()
|
| 88 |
+
|
| 89 |
+
select {
|
| 90 |
+
case <-signalChan:
|
| 91 |
+
logger.Info("received signal, shutting down gracefully")
|
| 92 |
+
case <-runChan:
|
| 93 |
+
logger.Info("speed test complete")
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
func runDownloadTest(c client.Client) {
|
| 98 |
+
logger.Info("performing download test")
|
| 99 |
+
downConn, err := c.TCP(speedtestAddr)
|
| 100 |
+
if err != nil {
|
| 101 |
+
if errors.As(err, &hyErrors.DialError{}) {
|
| 102 |
+
logger.Fatal("failed to connect (server may not support speed test)", zap.Error(err))
|
| 103 |
+
} else {
|
| 104 |
+
logger.Fatal("failed to connect", zap.Error(err))
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
defer downConn.Close()
|
| 108 |
+
|
| 109 |
+
downClient := &speedtest.Client{Conn: downConn}
|
| 110 |
+
currentTotal := uint32(0)
|
| 111 |
+
err = downClient.Download(dataSize, func(d time.Duration, b uint32, done bool) {
|
| 112 |
+
if !done {
|
| 113 |
+
currentTotal += b
|
| 114 |
+
logger.Info("downloading",
|
| 115 |
+
zap.Uint32("bytes", b),
|
| 116 |
+
zap.String("progress", fmt.Sprintf("%.2f%%", float64(currentTotal)/float64(dataSize)*100)),
|
| 117 |
+
zap.String("speed", formatSpeed(b, d, useBytes)))
|
| 118 |
+
} else {
|
| 119 |
+
logger.Info("download complete",
|
| 120 |
+
zap.Uint32("bytes", b),
|
| 121 |
+
zap.String("speed", formatSpeed(b, d, useBytes)))
|
| 122 |
+
}
|
| 123 |
+
})
|
| 124 |
+
if err != nil {
|
| 125 |
+
logger.Fatal("download test failed", zap.Error(err))
|
| 126 |
+
}
|
| 127 |
+
logger.Info("download test complete")
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
func runUploadTest(c client.Client) {
|
| 131 |
+
logger.Info("performing upload test")
|
| 132 |
+
upConn, err := c.TCP(speedtestAddr)
|
| 133 |
+
if err != nil {
|
| 134 |
+
if errors.As(err, &hyErrors.DialError{}) {
|
| 135 |
+
logger.Fatal("failed to connect (server may not support speed test)", zap.Error(err))
|
| 136 |
+
} else {
|
| 137 |
+
logger.Fatal("failed to connect", zap.Error(err))
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
defer upConn.Close()
|
| 141 |
+
|
| 142 |
+
upClient := &speedtest.Client{Conn: upConn}
|
| 143 |
+
currentTotal := uint32(0)
|
| 144 |
+
err = upClient.Upload(dataSize, func(d time.Duration, b uint32, done bool) {
|
| 145 |
+
if !done {
|
| 146 |
+
currentTotal += b
|
| 147 |
+
logger.Info("uploading",
|
| 148 |
+
zap.Uint32("bytes", b),
|
| 149 |
+
zap.String("progress", fmt.Sprintf("%.2f%%", float64(currentTotal)/float64(dataSize)*100)),
|
| 150 |
+
zap.String("speed", formatSpeed(b, d, useBytes)))
|
| 151 |
+
} else {
|
| 152 |
+
logger.Info("upload complete",
|
| 153 |
+
zap.Uint32("bytes", b),
|
| 154 |
+
zap.String("speed", formatSpeed(b, d, useBytes)))
|
| 155 |
+
}
|
| 156 |
+
})
|
| 157 |
+
if err != nil {
|
| 158 |
+
logger.Fatal("upload test failed", zap.Error(err))
|
| 159 |
+
}
|
| 160 |
+
logger.Info("upload test complete")
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
func formatSpeed(bytes uint32, duration time.Duration, useBytes bool) string {
|
| 164 |
+
speed := float64(bytes) / duration.Seconds()
|
| 165 |
+
var units []string
|
| 166 |
+
if useBytes {
|
| 167 |
+
units = []string{"B/s", "KB/s", "MB/s", "GB/s"}
|
| 168 |
+
} else {
|
| 169 |
+
units = []string{"bps", "Kbps", "Mbps", "Gbps"}
|
| 170 |
+
speed *= 8
|
| 171 |
+
}
|
| 172 |
+
unitIndex := 0
|
| 173 |
+
for speed > 1000 && unitIndex < len(units)-1 {
|
| 174 |
+
speed /= 1000
|
| 175 |
+
unitIndex++
|
| 176 |
+
}
|
| 177 |
+
return fmt.Sprintf("%.2f %s", speed, units[unitIndex])
|
| 178 |
+
}
|
app/cmd/update.go
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"time"
|
| 5 |
+
|
| 6 |
+
"github.com/spf13/cobra"
|
| 7 |
+
"go.uber.org/zap"
|
| 8 |
+
|
| 9 |
+
"github.com/apernet/hysteria/app/v2/internal/utils"
|
| 10 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
const (
|
| 14 |
+
updateCheckInterval = 24 * time.Hour
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
// checkUpdateCmd represents the checkUpdate command
|
| 18 |
+
var checkUpdateCmd = &cobra.Command{
|
| 19 |
+
Use: "check-update",
|
| 20 |
+
Short: "Check for updates",
|
| 21 |
+
Long: "Check for updates.",
|
| 22 |
+
Run: runCheckUpdate,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
func init() {
|
| 26 |
+
rootCmd.AddCommand(checkUpdateCmd)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
func runCheckUpdate(cmd *cobra.Command, args []string) {
|
| 30 |
+
logger.Info("checking for updates",
|
| 31 |
+
zap.String("version", appVersion),
|
| 32 |
+
zap.String("platform", appPlatform),
|
| 33 |
+
zap.String("arch", appArch),
|
| 34 |
+
zap.String("channel", appType),
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
checker := utils.NewServerUpdateChecker(appVersion, appPlatform, appArch, appType)
|
| 38 |
+
resp, err := checker.Check()
|
| 39 |
+
if err != nil {
|
| 40 |
+
logger.Fatal("failed to check for updates", zap.Error(err))
|
| 41 |
+
}
|
| 42 |
+
if resp.HasUpdate {
|
| 43 |
+
logger.Info("update available",
|
| 44 |
+
zap.String("version", resp.LatestVersion),
|
| 45 |
+
zap.String("url", resp.URL),
|
| 46 |
+
zap.Bool("urgent", resp.Urgent),
|
| 47 |
+
)
|
| 48 |
+
} else {
|
| 49 |
+
logger.Info("no update available")
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// runCheckUpdateServer is the background update checking routine for server mode
|
| 54 |
+
func runCheckUpdateServer() {
|
| 55 |
+
checker := utils.NewServerUpdateChecker(appVersion, appPlatform, appArch, appType)
|
| 56 |
+
checkUpdateRoutine(checker)
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// runCheckUpdateClient is the background update checking routine for client mode
|
| 60 |
+
func runCheckUpdateClient(hyClient client.Client) {
|
| 61 |
+
checker := utils.NewClientUpdateChecker(appVersion, appPlatform, appArch, appType, hyClient)
|
| 62 |
+
checkUpdateRoutine(checker)
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
func checkUpdateRoutine(checker *utils.UpdateChecker) {
|
| 66 |
+
ticker := time.NewTicker(updateCheckInterval)
|
| 67 |
+
for {
|
| 68 |
+
logger.Debug("checking for updates",
|
| 69 |
+
zap.String("version", appVersion),
|
| 70 |
+
zap.String("platform", appPlatform),
|
| 71 |
+
zap.String("arch", appArch),
|
| 72 |
+
zap.String("channel", appType),
|
| 73 |
+
)
|
| 74 |
+
resp, err := checker.Check()
|
| 75 |
+
if err != nil {
|
| 76 |
+
logger.Debug("failed to check for updates", zap.Error(err))
|
| 77 |
+
} else if resp.HasUpdate {
|
| 78 |
+
logger.Info("update available",
|
| 79 |
+
zap.String("version", resp.LatestVersion),
|
| 80 |
+
zap.String("url", resp.URL),
|
| 81 |
+
zap.Bool("urgent", resp.Urgent),
|
| 82 |
+
)
|
| 83 |
+
} else {
|
| 84 |
+
logger.Debug("no update available")
|
| 85 |
+
}
|
| 86 |
+
<-ticker.C
|
| 87 |
+
}
|
| 88 |
+
}
|
app/cmd/version.go
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package cmd
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"fmt"
|
| 5 |
+
|
| 6 |
+
"github.com/spf13/cobra"
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
// versionCmd represents the version command
|
| 10 |
+
var versionCmd = &cobra.Command{
|
| 11 |
+
Use: "version",
|
| 12 |
+
Short: "Show version",
|
| 13 |
+
Long: "Show version.",
|
| 14 |
+
Run: runVersion,
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
func init() {
|
| 18 |
+
rootCmd.AddCommand(versionCmd)
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
func runVersion(cmd *cobra.Command, args []string) {
|
| 22 |
+
fmt.Println(appAboutLong)
|
| 23 |
+
}
|
app/go.mod
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module github.com/apernet/hysteria/app/v2
|
| 2 |
+
|
| 3 |
+
go 1.22
|
| 4 |
+
|
| 5 |
+
toolchain go1.23.2
|
| 6 |
+
|
| 7 |
+
require (
|
| 8 |
+
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f
|
| 9 |
+
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
|
| 10 |
+
github.com/apernet/hysteria/extras/v2 v2.0.0-00010101000000-000000000000
|
| 11 |
+
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad
|
| 12 |
+
github.com/caddyserver/certmagic v0.17.2
|
| 13 |
+
github.com/libdns/cloudflare v0.1.1
|
| 14 |
+
github.com/libdns/duckdns v0.2.0
|
| 15 |
+
github.com/libdns/gandi v1.0.3
|
| 16 |
+
github.com/libdns/godaddy v1.0.3
|
| 17 |
+
github.com/libdns/namedotcom v0.3.3
|
| 18 |
+
github.com/libdns/vultr v1.0.0
|
| 19 |
+
github.com/mdp/qrterminal/v3 v3.1.1
|
| 20 |
+
github.com/mholt/acmez v1.0.4
|
| 21 |
+
github.com/sagernet/sing v0.3.2
|
| 22 |
+
github.com/spf13/cobra v1.7.0
|
| 23 |
+
github.com/spf13/viper v1.15.0
|
| 24 |
+
github.com/stretchr/testify v1.9.0
|
| 25 |
+
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
|
| 26 |
+
go.uber.org/zap v1.24.0
|
| 27 |
+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
| 28 |
+
golang.org/x/sys v0.25.0
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
require (
|
| 32 |
+
github.com/andybalholm/brotli v1.1.0 // indirect
|
| 33 |
+
github.com/apernet/quic-go v0.49.1-0.20250204013113-43c72b1281a0 // indirect
|
| 34 |
+
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
|
| 35 |
+
github.com/cloudflare/circl v1.3.9 // indirect
|
| 36 |
+
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
|
| 37 |
+
github.com/database64128/tfo-go/v2 v2.2.2 // indirect
|
| 38 |
+
github.com/davecgh/go-spew v1.1.1 // indirect
|
| 39 |
+
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
| 40 |
+
github.com/go-ole/go-ole v1.3.0 // indirect
|
| 41 |
+
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
| 42 |
+
github.com/google/go-querystring v1.1.0 // indirect
|
| 43 |
+
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
| 44 |
+
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
| 45 |
+
github.com/hashicorp/go-retryablehttp v0.7.6 // indirect
|
| 46 |
+
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
|
| 47 |
+
github.com/hashicorp/hcl v1.0.0 // indirect
|
| 48 |
+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
| 49 |
+
github.com/klauspost/compress v1.17.9 // indirect
|
| 50 |
+
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
|
| 51 |
+
github.com/libdns/libdns v0.2.2 // indirect
|
| 52 |
+
github.com/magiconair/properties v1.8.7 // indirect
|
| 53 |
+
github.com/miekg/dns v1.1.59 // indirect
|
| 54 |
+
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
| 55 |
+
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
| 56 |
+
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
| 57 |
+
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
| 58 |
+
github.com/pkg/errors v0.9.1 // indirect
|
| 59 |
+
github.com/pmezard/go-difflib v1.0.0 // indirect
|
| 60 |
+
github.com/quic-go/qpack v0.5.1 // indirect
|
| 61 |
+
github.com/refraction-networking/utls v1.6.6 // indirect
|
| 62 |
+
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
| 63 |
+
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
| 64 |
+
github.com/spf13/afero v1.9.3 // indirect
|
| 65 |
+
github.com/spf13/cast v1.5.0 // indirect
|
| 66 |
+
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
| 67 |
+
github.com/spf13/pflag v1.0.5 // indirect
|
| 68 |
+
github.com/stretchr/objx v0.5.2 // indirect
|
| 69 |
+
github.com/subosito/gotenv v1.4.2 // indirect
|
| 70 |
+
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
|
| 71 |
+
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
| 72 |
+
github.com/vultr/govultr/v3 v3.6.4 // indirect
|
| 73 |
+
go.uber.org/atomic v1.11.0 // indirect
|
| 74 |
+
go.uber.org/mock v0.5.0 // indirect
|
| 75 |
+
go.uber.org/multierr v1.11.0 // indirect
|
| 76 |
+
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
| 77 |
+
golang.org/x/crypto v0.26.0 // indirect
|
| 78 |
+
golang.org/x/mod v0.18.0 // indirect
|
| 79 |
+
golang.org/x/net v0.28.0 // indirect
|
| 80 |
+
golang.org/x/oauth2 v0.20.0 // indirect
|
| 81 |
+
golang.org/x/sync v0.8.0 // indirect
|
| 82 |
+
golang.org/x/text v0.17.0 // indirect
|
| 83 |
+
golang.org/x/tools v0.22.0 // indirect
|
| 84 |
+
google.golang.org/protobuf v1.34.1 // indirect
|
| 85 |
+
gopkg.in/ini.v1 v1.67.0 // indirect
|
| 86 |
+
gopkg.in/yaml.v3 v3.0.1 // indirect
|
| 87 |
+
rsc.io/qr v0.2.0 // indirect
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
replace github.com/apernet/hysteria/core/v2 => ../core
|
| 91 |
+
|
| 92 |
+
replace github.com/apernet/hysteria/extras/v2 => ../extras
|
app/go.sum
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
| 2 |
+
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
| 3 |
+
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
| 4 |
+
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
| 5 |
+
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
| 6 |
+
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
| 7 |
+
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
| 8 |
+
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
| 9 |
+
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
| 10 |
+
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
| 11 |
+
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
| 12 |
+
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
| 13 |
+
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
| 14 |
+
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
| 15 |
+
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
| 16 |
+
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
| 17 |
+
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
| 18 |
+
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
| 19 |
+
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
| 20 |
+
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
| 21 |
+
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
| 22 |
+
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
| 23 |
+
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
| 24 |
+
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
| 25 |
+
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
| 26 |
+
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
| 27 |
+
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
| 28 |
+
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
| 29 |
+
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
| 30 |
+
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
| 31 |
+
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
| 32 |
+
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
| 33 |
+
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
| 34 |
+
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
| 35 |
+
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
| 36 |
+
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
| 37 |
+
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
| 38 |
+
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
| 39 |
+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
| 40 |
+
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
| 41 |
+
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
| 42 |
+
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
| 43 |
+
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f h1:uVh0qpEslrWjgzx9vOcyCqsOY3c9kofDZ1n+qaw35ZY=
|
| 44 |
+
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f/go.mod h1:xkkq9D4ygcldQQhKS/w9CadiCKwCngU7K9E3DaKahpM=
|
| 45 |
+
github.com/apernet/quic-go v0.49.1-0.20250204013113-43c72b1281a0 h1:oc6//C91pY9gGOBioHeyJrmmpKv/nS8fvTeDpKNPLnI=
|
| 46 |
+
github.com/apernet/quic-go v0.49.1-0.20250204013113-43c72b1281a0/go.mod h1:/mMPNt1MHqduzaVB2qFHnJwam3BR5r5b35GvYouJs/o=
|
| 47 |
+
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad h1:QzQ2sKpc9o42HNRR8ukM5uMC/RzR2HgZd/Nvaqol2C0=
|
| 48 |
+
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad/go.mod h1:S5IydyLSN/QAfvY+r2GoomPJ6hidtXWm/Ad18sJVssk=
|
| 49 |
+
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
|
| 50 |
+
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
|
| 51 |
+
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
| 52 |
+
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
| 53 |
+
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
|
| 54 |
+
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
|
| 55 |
+
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
| 56 |
+
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
| 57 |
+
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
| 58 |
+
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
| 59 |
+
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
| 60 |
+
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
|
| 61 |
+
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
| 62 |
+
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
| 63 |
+
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
| 64 |
+
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
| 65 |
+
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
| 66 |
+
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
|
| 67 |
+
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
|
| 68 |
+
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
|
| 69 |
+
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
|
| 70 |
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 71 |
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
| 72 |
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 73 |
+
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
| 74 |
+
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
| 75 |
+
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
| 76 |
+
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
| 77 |
+
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
| 78 |
+
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
| 79 |
+
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
| 80 |
+
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
| 81 |
+
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
| 82 |
+
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
| 83 |
+
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
| 84 |
+
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
| 85 |
+
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
| 86 |
+
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
| 87 |
+
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
| 88 |
+
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
| 89 |
+
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
| 90 |
+
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
| 91 |
+
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
| 92 |
+
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
| 93 |
+
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
| 94 |
+
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
| 95 |
+
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
| 96 |
+
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
| 97 |
+
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
| 98 |
+
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
| 99 |
+
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
| 100 |
+
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
| 101 |
+
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
| 102 |
+
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
| 103 |
+
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
| 104 |
+
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
| 105 |
+
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
| 106 |
+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
| 107 |
+
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
| 108 |
+
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
| 109 |
+
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
| 110 |
+
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
| 111 |
+
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
| 112 |
+
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
| 113 |
+
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
| 114 |
+
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
| 115 |
+
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
| 116 |
+
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
| 117 |
+
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
| 118 |
+
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
| 119 |
+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
| 120 |
+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
| 121 |
+
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
| 122 |
+
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
| 123 |
+
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
| 124 |
+
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
| 125 |
+
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
| 126 |
+
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 127 |
+
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 128 |
+
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 129 |
+
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 130 |
+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 131 |
+
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 132 |
+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
| 133 |
+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
| 134 |
+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
| 135 |
+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
| 136 |
+
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
| 137 |
+
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
| 138 |
+
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
| 139 |
+
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
| 140 |
+
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
| 141 |
+
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
| 142 |
+
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
| 143 |
+
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
| 144 |
+
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
| 145 |
+
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
| 146 |
+
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
| 147 |
+
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
| 148 |
+
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
| 149 |
+
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
| 150 |
+
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
| 151 |
+
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
| 152 |
+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 153 |
+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
| 154 |
+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
| 155 |
+
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
| 156 |
+
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
| 157 |
+
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
| 158 |
+
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
| 159 |
+
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
| 160 |
+
github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM=
|
| 161 |
+
github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
| 162 |
+
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
| 163 |
+
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
| 164 |
+
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
|
| 165 |
+
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
| 166 |
+
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
| 167 |
+
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
| 168 |
+
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
| 169 |
+
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
| 170 |
+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
| 171 |
+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
| 172 |
+
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
| 173 |
+
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
| 174 |
+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
| 175 |
+
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
| 176 |
+
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
| 177 |
+
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
|
| 178 |
+
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
| 179 |
+
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
| 180 |
+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
| 181 |
+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
| 182 |
+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
| 183 |
+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
| 184 |
+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
| 185 |
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
| 186 |
+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
| 187 |
+
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
| 188 |
+
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
| 189 |
+
github.com/libdns/duckdns v0.2.0 h1:vd3pE09G2qTx1Zh1o3LmrivWSByD3Z5FbL7csX5vDgE=
|
| 190 |
+
github.com/libdns/duckdns v0.2.0/go.mod h1:jCQ/7+qvhLK39+28qXvKEYGBBvmHBCmIwNqdJTCUmVs=
|
| 191 |
+
github.com/libdns/gandi v1.0.3 h1:FIvipWOg/O4zi75fPRmtcolRKqI6MgrbpFy2p5KYdUk=
|
| 192 |
+
github.com/libdns/gandi v1.0.3/go.mod h1:G6dw58Xnji2xX+lb+uZxGbtmfxKllm1CGHE2bOPG3WA=
|
| 193 |
+
github.com/libdns/godaddy v1.0.3 h1:PX1FOYDQ1HGQzz8mVOmtwm3aa6Sv5MwCkNzivUUTA44=
|
| 194 |
+
github.com/libdns/godaddy v1.0.3/go.mod h1:vuKWUXnvblDvcaiRwutOoLl7DuB21x8tI06owsF/JTM=
|
| 195 |
+
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
| 196 |
+
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
| 197 |
+
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
| 198 |
+
github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE=
|
| 199 |
+
github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s=
|
| 200 |
+
github.com/libdns/vultr v1.0.0 h1:W8B4+k2bm9ro3bZLSZV9hMOQI+uO6Svu+GmD+Olz7ZI=
|
| 201 |
+
github.com/libdns/vultr v1.0.0/go.mod h1:8K1HJExcbeHS4YPkFHRZpqpXZzZ+DZAA0m0VikJgEqk=
|
| 202 |
+
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
| 203 |
+
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
| 204 |
+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
| 205 |
+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
| 206 |
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
| 207 |
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
| 208 |
+
github.com/mdp/qrterminal/v3 v3.1.1 h1:cIPwg3QU0OIm9+ce/lRfWXhPwEjOSKwk3HBwL3HBTyc=
|
| 209 |
+
github.com/mdp/qrterminal/v3 v3.1.1/go.mod h1:5lJlXe7Jdr8wlPDdcsJttv1/knsRgzXASyr4dcGZqNU=
|
| 210 |
+
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
|
| 211 |
+
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
|
| 212 |
+
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
| 213 |
+
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
|
| 214 |
+
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
| 215 |
+
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
| 216 |
+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
| 217 |
+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
| 218 |
+
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
| 219 |
+
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
| 220 |
+
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
| 221 |
+
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
| 222 |
+
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
| 223 |
+
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
| 224 |
+
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
| 225 |
+
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
| 226 |
+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
| 227 |
+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
| 228 |
+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
| 229 |
+
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
| 230 |
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
| 231 |
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
| 232 |
+
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
| 233 |
+
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
| 234 |
+
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
| 235 |
+
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
|
| 236 |
+
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
| 237 |
+
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
| 238 |
+
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
| 239 |
+
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
| 240 |
+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
| 241 |
+
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
| 242 |
+
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
| 243 |
+
github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo=
|
| 244 |
+
github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE=
|
| 245 |
+
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
| 246 |
+
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
| 247 |
+
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
| 248 |
+
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
| 249 |
+
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
| 250 |
+
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
| 251 |
+
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
| 252 |
+
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
| 253 |
+
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
| 254 |
+
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
| 255 |
+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
| 256 |
+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
| 257 |
+
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
| 258 |
+
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
| 259 |
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
| 260 |
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
| 261 |
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
| 262 |
+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
| 263 |
+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
| 264 |
+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
| 265 |
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
| 266 |
+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
| 267 |
+
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
| 268 |
+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 269 |
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 270 |
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 271 |
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
| 272 |
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
| 273 |
+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
| 274 |
+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
| 275 |
+
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
| 276 |
+
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
| 277 |
+
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
|
| 278 |
+
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
|
| 279 |
+
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM=
|
| 280 |
+
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
|
| 281 |
+
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
| 282 |
+
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
| 283 |
+
github.com/vultr/govultr/v3 v3.6.4 h1:unvY9eXlBw667ECQZDbBDOIaWB8wkk6Bx+yB0IMKXJ4=
|
| 284 |
+
github.com/vultr/govultr/v3 v3.6.4/go.mod h1:rt9v2x114jZmmLAE/h5N5jnxTmsK9ewwS2oQZ0UBQzM=
|
| 285 |
+
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
| 286 |
+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
| 287 |
+
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
| 288 |
+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
| 289 |
+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
| 290 |
+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
| 291 |
+
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
| 292 |
+
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
| 293 |
+
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
| 294 |
+
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
| 295 |
+
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
| 296 |
+
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
| 297 |
+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
| 298 |
+
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
| 299 |
+
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
| 300 |
+
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
| 301 |
+
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
| 302 |
+
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
| 303 |
+
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
| 304 |
+
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
| 305 |
+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
| 306 |
+
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
| 307 |
+
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
| 308 |
+
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
| 309 |
+
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
| 310 |
+
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
| 311 |
+
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
| 312 |
+
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
| 313 |
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
| 314 |
+
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
| 315 |
+
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
| 316 |
+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
| 317 |
+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
| 318 |
+
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
| 319 |
+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
| 320 |
+
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
| 321 |
+
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
| 322 |
+
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
| 323 |
+
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
| 324 |
+
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
| 325 |
+
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
| 326 |
+
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
| 327 |
+
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
| 328 |
+
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
| 329 |
+
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
| 330 |
+
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
| 331 |
+
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
| 332 |
+
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
| 333 |
+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
| 334 |
+
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
| 335 |
+
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
| 336 |
+
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
| 337 |
+
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
| 338 |
+
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
| 339 |
+
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
| 340 |
+
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
| 341 |
+
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
| 342 |
+
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
| 343 |
+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
| 344 |
+
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
| 345 |
+
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
| 346 |
+
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
| 347 |
+
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
| 348 |
+
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
| 349 |
+
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
| 350 |
+
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
| 351 |
+
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
| 352 |
+
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
| 353 |
+
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
| 354 |
+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
| 355 |
+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
| 356 |
+
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
| 357 |
+
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
| 358 |
+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
| 359 |
+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
| 360 |
+
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
| 361 |
+
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
| 362 |
+
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
| 363 |
+
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
| 364 |
+
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
| 365 |
+
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
| 366 |
+
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
| 367 |
+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
| 368 |
+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
| 369 |
+
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
| 370 |
+
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
| 371 |
+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
| 372 |
+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 373 |
+
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 374 |
+
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 375 |
+
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 376 |
+
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 377 |
+
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 378 |
+
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 379 |
+
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 380 |
+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 381 |
+
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 382 |
+
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
| 383 |
+
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
| 384 |
+
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
| 385 |
+
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
| 386 |
+
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
| 387 |
+
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
| 388 |
+
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
| 389 |
+
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
| 390 |
+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
| 391 |
+
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
| 392 |
+
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
| 393 |
+
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
| 394 |
+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
| 395 |
+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
| 396 |
+
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
| 397 |
+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
| 398 |
+
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
| 399 |
+
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
| 400 |
+
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
| 401 |
+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
| 402 |
+
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
| 403 |
+
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
| 404 |
+
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
| 405 |
+
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
| 406 |
+
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
| 407 |
+
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
| 408 |
+
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
| 409 |
+
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
| 410 |
+
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
| 411 |
+
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
| 412 |
+
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 413 |
+
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 414 |
+
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 415 |
+
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 416 |
+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 417 |
+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 418 |
+
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 419 |
+
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 420 |
+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 421 |
+
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 422 |
+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 423 |
+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 424 |
+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 425 |
+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
| 426 |
+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
| 427 |
+
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
| 428 |
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
| 429 |
+
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 430 |
+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 431 |
+
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 432 |
+
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 433 |
+
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 434 |
+
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 435 |
+
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 436 |
+
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 437 |
+
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 438 |
+
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 439 |
+
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 440 |
+
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 441 |
+
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 442 |
+
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 443 |
+
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 444 |
+
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 445 |
+
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 446 |
+
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 447 |
+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 448 |
+
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 449 |
+
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 450 |
+
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 451 |
+
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 452 |
+
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 453 |
+
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 454 |
+
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 455 |
+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 456 |
+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 457 |
+
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 458 |
+
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 459 |
+
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 460 |
+
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 461 |
+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 462 |
+
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 463 |
+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 464 |
+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 465 |
+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 466 |
+
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 467 |
+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 468 |
+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 469 |
+
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 470 |
+
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
| 471 |
+
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 472 |
+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
| 473 |
+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
| 474 |
+
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
| 475 |
+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
| 476 |
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
| 477 |
+
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
| 478 |
+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
| 479 |
+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
| 480 |
+
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
| 481 |
+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
| 482 |
+
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
| 483 |
+
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
| 484 |
+
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
| 485 |
+
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
| 486 |
+
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
| 487 |
+
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
| 488 |
+
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
| 489 |
+
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
| 490 |
+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
| 491 |
+
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
| 492 |
+
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
| 493 |
+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
| 494 |
+
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
| 495 |
+
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
| 496 |
+
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
| 497 |
+
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
| 498 |
+
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
| 499 |
+
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
| 500 |
+
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
| 501 |
+
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
| 502 |
+
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 503 |
+
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 504 |
+
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 505 |
+
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 506 |
+
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 507 |
+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 508 |
+
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 509 |
+
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 510 |
+
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 511 |
+
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 512 |
+
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 513 |
+
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 514 |
+
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 515 |
+
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 516 |
+
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 517 |
+
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 518 |
+
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 519 |
+
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 520 |
+
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
| 521 |
+
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
| 522 |
+
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
| 523 |
+
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
| 524 |
+
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
| 525 |
+
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
| 526 |
+
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
| 527 |
+
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
| 528 |
+
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
| 529 |
+
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
| 530 |
+
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
| 531 |
+
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
| 532 |
+
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
| 533 |
+
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
| 534 |
+
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
| 535 |
+
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
| 536 |
+
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
| 537 |
+
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
| 538 |
+
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
| 539 |
+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
| 540 |
+
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
| 541 |
+
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
| 542 |
+
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
| 543 |
+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 544 |
+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 545 |
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 546 |
+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 547 |
+
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
| 548 |
+
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
| 549 |
+
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
| 550 |
+
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
| 551 |
+
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
| 552 |
+
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
| 553 |
+
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
| 554 |
+
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
| 555 |
+
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
| 556 |
+
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
| 557 |
+
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
| 558 |
+
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
| 559 |
+
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
| 560 |
+
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
| 561 |
+
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
| 562 |
+
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
| 563 |
+
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
| 564 |
+
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
| 565 |
+
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
| 566 |
+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
| 567 |
+
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
| 568 |
+
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
| 569 |
+
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
| 570 |
+
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
| 571 |
+
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
| 572 |
+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
| 573 |
+
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
| 574 |
+
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
| 575 |
+
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
| 576 |
+
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
| 577 |
+
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
| 578 |
+
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
| 579 |
+
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
| 580 |
+
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
| 581 |
+
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
| 582 |
+
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
| 583 |
+
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
| 584 |
+
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
| 585 |
+
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
| 586 |
+
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
| 587 |
+
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
| 588 |
+
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 589 |
+
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 590 |
+
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 591 |
+
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 592 |
+
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 593 |
+
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 594 |
+
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 595 |
+
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
| 596 |
+
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
| 597 |
+
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
| 598 |
+
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
| 599 |
+
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 600 |
+
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 601 |
+
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 602 |
+
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 603 |
+
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 604 |
+
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 605 |
+
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 606 |
+
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 607 |
+
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 608 |
+
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
| 609 |
+
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
| 610 |
+
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
| 611 |
+
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
| 612 |
+
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
| 613 |
+
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
| 614 |
+
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
| 615 |
+
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
| 616 |
+
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
| 617 |
+
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
| 618 |
+
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
| 619 |
+
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
| 620 |
+
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
| 621 |
+
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
| 622 |
+
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
| 623 |
+
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
| 624 |
+
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
| 625 |
+
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
| 626 |
+
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
| 627 |
+
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
| 628 |
+
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
| 629 |
+
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
| 630 |
+
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
| 631 |
+
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
| 632 |
+
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
| 633 |
+
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
| 634 |
+
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
| 635 |
+
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
| 636 |
+
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
| 637 |
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
| 638 |
+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
| 639 |
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
| 640 |
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
| 641 |
+
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
| 642 |
+
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
| 643 |
+
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
| 644 |
+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
| 645 |
+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
| 646 |
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 647 |
+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 648 |
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
| 649 |
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 650 |
+
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
| 651 |
+
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
| 652 |
+
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
| 653 |
+
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
| 654 |
+
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
| 655 |
+
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
| 656 |
+
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
| 657 |
+
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
| 658 |
+
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
| 659 |
+
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
| 660 |
+
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
| 661 |
+
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
app/internal/forwarding/tcp.go
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package forwarding
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"io"
|
| 5 |
+
"net"
|
| 6 |
+
|
| 7 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
type TCPTunnel struct {
|
| 11 |
+
HyClient client.Client
|
| 12 |
+
Remote string
|
| 13 |
+
EventLogger TCPEventLogger
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
type TCPEventLogger interface {
|
| 17 |
+
Connect(addr net.Addr)
|
| 18 |
+
Error(addr net.Addr, err error)
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
func (t *TCPTunnel) Serve(listener net.Listener) error {
|
| 22 |
+
for {
|
| 23 |
+
conn, err := listener.Accept()
|
| 24 |
+
if err != nil {
|
| 25 |
+
return err
|
| 26 |
+
}
|
| 27 |
+
go t.handle(conn)
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
func (t *TCPTunnel) handle(conn net.Conn) {
|
| 32 |
+
defer conn.Close()
|
| 33 |
+
|
| 34 |
+
if t.EventLogger != nil {
|
| 35 |
+
t.EventLogger.Connect(conn.RemoteAddr())
|
| 36 |
+
}
|
| 37 |
+
var closeErr error
|
| 38 |
+
defer func() {
|
| 39 |
+
if t.EventLogger != nil {
|
| 40 |
+
t.EventLogger.Error(conn.RemoteAddr(), closeErr)
|
| 41 |
+
}
|
| 42 |
+
}()
|
| 43 |
+
|
| 44 |
+
rc, err := t.HyClient.TCP(t.Remote)
|
| 45 |
+
if err != nil {
|
| 46 |
+
closeErr = err
|
| 47 |
+
return
|
| 48 |
+
}
|
| 49 |
+
defer rc.Close()
|
| 50 |
+
|
| 51 |
+
// Start forwarding
|
| 52 |
+
copyErrChan := make(chan error, 2)
|
| 53 |
+
go func() {
|
| 54 |
+
_, copyErr := io.Copy(rc, conn)
|
| 55 |
+
copyErrChan <- copyErr
|
| 56 |
+
}()
|
| 57 |
+
go func() {
|
| 58 |
+
_, copyErr := io.Copy(conn, rc)
|
| 59 |
+
copyErrChan <- copyErr
|
| 60 |
+
}()
|
| 61 |
+
closeErr = <-copyErrChan
|
| 62 |
+
}
|
app/internal/forwarding/tcp_test.go
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package forwarding
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"crypto/rand"
|
| 5 |
+
"net"
|
| 6 |
+
"testing"
|
| 7 |
+
|
| 8 |
+
"github.com/stretchr/testify/assert"
|
| 9 |
+
|
| 10 |
+
"github.com/apernet/hysteria/app/v2/internal/utils_test"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
func TestTCPTunnel(t *testing.T) {
|
| 14 |
+
// Start the tunnel
|
| 15 |
+
l, err := net.Listen("tcp", "127.0.0.1:34567")
|
| 16 |
+
assert.NoError(t, err)
|
| 17 |
+
defer l.Close()
|
| 18 |
+
tunnel := &TCPTunnel{
|
| 19 |
+
HyClient: &utils_test.MockEchoHyClient{},
|
| 20 |
+
}
|
| 21 |
+
go tunnel.Serve(l)
|
| 22 |
+
|
| 23 |
+
for i := 0; i < 10; i++ {
|
| 24 |
+
conn, err := net.Dial("tcp", "127.0.0.1:34567")
|
| 25 |
+
assert.NoError(t, err)
|
| 26 |
+
|
| 27 |
+
data := make([]byte, 1024)
|
| 28 |
+
_, _ = rand.Read(data)
|
| 29 |
+
_, err = conn.Write(data)
|
| 30 |
+
assert.NoError(t, err)
|
| 31 |
+
|
| 32 |
+
recv := make([]byte, 1024)
|
| 33 |
+
_, err = conn.Read(recv)
|
| 34 |
+
assert.NoError(t, err)
|
| 35 |
+
|
| 36 |
+
assert.Equal(t, data, recv)
|
| 37 |
+
_ = conn.Close()
|
| 38 |
+
}
|
| 39 |
+
}
|
app/internal/forwarding/udp.go
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package forwarding
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"net"
|
| 5 |
+
"sync"
|
| 6 |
+
"sync/atomic"
|
| 7 |
+
"time"
|
| 8 |
+
|
| 9 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
const (
|
| 13 |
+
udpBufferSize = 4096
|
| 14 |
+
|
| 15 |
+
defaultTimeout = 60 * time.Second
|
| 16 |
+
idleCleanupInterval = 1 * time.Second
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
type atomicTime struct {
|
| 20 |
+
v atomic.Value
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
func newAtomicTime(t time.Time) *atomicTime {
|
| 24 |
+
a := &atomicTime{}
|
| 25 |
+
a.Set(t)
|
| 26 |
+
return a
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
func (t *atomicTime) Set(new time.Time) {
|
| 30 |
+
t.v.Store(new)
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
func (t *atomicTime) Get() time.Time {
|
| 34 |
+
return t.v.Load().(time.Time)
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
type sessionEntry struct {
|
| 38 |
+
HyConn client.HyUDPConn
|
| 39 |
+
Last *atomicTime
|
| 40 |
+
Timeout bool // true if the session is closed due to timeout
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
func (e *sessionEntry) Feed(data []byte, addr string) error {
|
| 44 |
+
e.Last.Set(time.Now())
|
| 45 |
+
return e.HyConn.Send(data, addr)
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
func (e *sessionEntry) ReceiveLoop(pc net.PacketConn, addr net.Addr) error {
|
| 49 |
+
for {
|
| 50 |
+
data, _, err := e.HyConn.Receive()
|
| 51 |
+
if err != nil {
|
| 52 |
+
return err
|
| 53 |
+
}
|
| 54 |
+
_, err = pc.WriteTo(data, addr)
|
| 55 |
+
if err != nil {
|
| 56 |
+
return err
|
| 57 |
+
}
|
| 58 |
+
e.Last.Set(time.Now())
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
type UDPTunnel struct {
|
| 63 |
+
HyClient client.Client
|
| 64 |
+
Remote string
|
| 65 |
+
Timeout time.Duration
|
| 66 |
+
EventLogger UDPEventLogger
|
| 67 |
+
|
| 68 |
+
m map[string]*sessionEntry // addr -> HyConn
|
| 69 |
+
mutex sync.RWMutex
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
type UDPEventLogger interface {
|
| 73 |
+
Connect(addr net.Addr)
|
| 74 |
+
Error(addr net.Addr, err error)
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
func (t *UDPTunnel) Serve(pc net.PacketConn) error {
|
| 78 |
+
t.m = make(map[string]*sessionEntry)
|
| 79 |
+
|
| 80 |
+
stopCh := make(chan struct{})
|
| 81 |
+
go t.idleCleanupLoop(stopCh)
|
| 82 |
+
defer close(stopCh)
|
| 83 |
+
defer t.cleanup(false)
|
| 84 |
+
|
| 85 |
+
buf := make([]byte, udpBufferSize)
|
| 86 |
+
for {
|
| 87 |
+
n, addr, err := pc.ReadFrom(buf)
|
| 88 |
+
if err != nil {
|
| 89 |
+
return err
|
| 90 |
+
}
|
| 91 |
+
t.feed(pc, addr, buf[:n])
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
func (t *UDPTunnel) idleCleanupLoop(stopCh <-chan struct{}) {
|
| 96 |
+
ticker := time.NewTicker(idleCleanupInterval)
|
| 97 |
+
defer ticker.Stop()
|
| 98 |
+
for {
|
| 99 |
+
select {
|
| 100 |
+
case <-ticker.C:
|
| 101 |
+
t.cleanup(true)
|
| 102 |
+
case <-stopCh:
|
| 103 |
+
return
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
func (t *UDPTunnel) cleanup(idleOnly bool) {
|
| 109 |
+
// We use RLock here as we are only scanning the map, not deleting from it.
|
| 110 |
+
t.mutex.RLock()
|
| 111 |
+
defer t.mutex.RUnlock()
|
| 112 |
+
|
| 113 |
+
timeout := t.Timeout
|
| 114 |
+
if timeout == 0 {
|
| 115 |
+
timeout = defaultTimeout
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
now := time.Now()
|
| 119 |
+
for _, entry := range t.m {
|
| 120 |
+
if !idleOnly || now.Sub(entry.Last.Get()) > timeout {
|
| 121 |
+
entry.Timeout = true
|
| 122 |
+
_ = entry.HyConn.Close()
|
| 123 |
+
// Closing the connection here will cause the ReceiveLoop to exit,
|
| 124 |
+
// and the session will be removed from the map there.
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
func (t *UDPTunnel) feed(pc net.PacketConn, addr net.Addr, data []byte) {
|
| 130 |
+
t.mutex.RLock()
|
| 131 |
+
entry := t.m[addr.String()]
|
| 132 |
+
t.mutex.RUnlock()
|
| 133 |
+
|
| 134 |
+
// Create a new session if not exists
|
| 135 |
+
if entry == nil {
|
| 136 |
+
if t.EventLogger != nil {
|
| 137 |
+
t.EventLogger.Connect(addr)
|
| 138 |
+
}
|
| 139 |
+
hyConn, err := t.HyClient.UDP()
|
| 140 |
+
if err != nil {
|
| 141 |
+
if t.EventLogger != nil {
|
| 142 |
+
t.EventLogger.Error(addr, err)
|
| 143 |
+
}
|
| 144 |
+
return
|
| 145 |
+
}
|
| 146 |
+
entry = &sessionEntry{
|
| 147 |
+
HyConn: hyConn,
|
| 148 |
+
Last: newAtomicTime(time.Now()),
|
| 149 |
+
}
|
| 150 |
+
// Start the receive loop for this session
|
| 151 |
+
// Local <- Remote
|
| 152 |
+
go func() {
|
| 153 |
+
err := entry.ReceiveLoop(pc, addr)
|
| 154 |
+
if !entry.Timeout {
|
| 155 |
+
_ = hyConn.Close()
|
| 156 |
+
if t.EventLogger != nil {
|
| 157 |
+
t.EventLogger.Error(addr, err)
|
| 158 |
+
}
|
| 159 |
+
} else {
|
| 160 |
+
// Connection already closed by timeout cleanup,
|
| 161 |
+
// no need to close again here.
|
| 162 |
+
// Use nil error to indicate timeout.
|
| 163 |
+
if t.EventLogger != nil {
|
| 164 |
+
t.EventLogger.Error(addr, nil)
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
// Remove the session from the map
|
| 168 |
+
t.mutex.Lock()
|
| 169 |
+
delete(t.m, addr.String())
|
| 170 |
+
t.mutex.Unlock()
|
| 171 |
+
}()
|
| 172 |
+
// Insert the session into the map
|
| 173 |
+
t.mutex.Lock()
|
| 174 |
+
t.m[addr.String()] = entry
|
| 175 |
+
t.mutex.Unlock()
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// Feed the message to the session
|
| 179 |
+
_ = entry.Feed(data, t.Remote)
|
| 180 |
+
}
|
app/internal/forwarding/udp_test.go
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package forwarding
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"crypto/rand"
|
| 5 |
+
"net"
|
| 6 |
+
"testing"
|
| 7 |
+
|
| 8 |
+
"github.com/stretchr/testify/assert"
|
| 9 |
+
|
| 10 |
+
"github.com/apernet/hysteria/app/v2/internal/utils_test"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
func TestUDPTunnel(t *testing.T) {
|
| 14 |
+
// Start the tunnel
|
| 15 |
+
l, err := net.ListenPacket("udp", "127.0.0.1:34567")
|
| 16 |
+
assert.NoError(t, err)
|
| 17 |
+
defer l.Close()
|
| 18 |
+
tunnel := &UDPTunnel{
|
| 19 |
+
HyClient: &utils_test.MockEchoHyClient{},
|
| 20 |
+
}
|
| 21 |
+
go tunnel.Serve(l)
|
| 22 |
+
|
| 23 |
+
for i := 0; i < 10; i++ {
|
| 24 |
+
conn, err := net.Dial("udp", "127.0.0.1:34567")
|
| 25 |
+
assert.NoError(t, err)
|
| 26 |
+
|
| 27 |
+
data := make([]byte, 1024)
|
| 28 |
+
_, _ = rand.Read(data)
|
| 29 |
+
_, err = conn.Write(data)
|
| 30 |
+
assert.NoError(t, err)
|
| 31 |
+
|
| 32 |
+
recv := make([]byte, 1024)
|
| 33 |
+
_, err = conn.Read(recv)
|
| 34 |
+
assert.NoError(t, err)
|
| 35 |
+
|
| 36 |
+
assert.Equal(t, data, recv)
|
| 37 |
+
_ = conn.Close()
|
| 38 |
+
}
|
| 39 |
+
}
|
app/internal/http/server.go
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package http
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"bufio"
|
| 5 |
+
"bytes"
|
| 6 |
+
"context"
|
| 7 |
+
"encoding/base64"
|
| 8 |
+
"fmt"
|
| 9 |
+
"io"
|
| 10 |
+
"net"
|
| 11 |
+
"net/http"
|
| 12 |
+
"strings"
|
| 13 |
+
"time"
|
| 14 |
+
|
| 15 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
const (
|
| 19 |
+
httpClientTimeout = 10 * time.Second
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
// Server is an HTTP server using a Hysteria client as outbound.
|
| 23 |
+
type Server struct {
|
| 24 |
+
HyClient client.Client
|
| 25 |
+
AuthFunc func(username, password string) bool // nil = no authentication
|
| 26 |
+
AuthRealm string
|
| 27 |
+
EventLogger EventLogger
|
| 28 |
+
|
| 29 |
+
httpClient *http.Client
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
type EventLogger interface {
|
| 33 |
+
ConnectRequest(addr net.Addr, reqAddr string)
|
| 34 |
+
ConnectError(addr net.Addr, reqAddr string, err error)
|
| 35 |
+
HTTPRequest(addr net.Addr, reqURL string)
|
| 36 |
+
HTTPError(addr net.Addr, reqURL string, err error)
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
func (s *Server) Serve(listener net.Listener) error {
|
| 40 |
+
for {
|
| 41 |
+
conn, err := listener.Accept()
|
| 42 |
+
if err != nil {
|
| 43 |
+
return err
|
| 44 |
+
}
|
| 45 |
+
go s.dispatch(conn)
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
func (s *Server) dispatch(conn net.Conn) {
|
| 50 |
+
bufReader := bufio.NewReader(conn)
|
| 51 |
+
for {
|
| 52 |
+
req, err := http.ReadRequest(bufReader)
|
| 53 |
+
if err != nil {
|
| 54 |
+
// Connection error or invalid request
|
| 55 |
+
_ = conn.Close()
|
| 56 |
+
return
|
| 57 |
+
}
|
| 58 |
+
if s.AuthFunc != nil {
|
| 59 |
+
authOK := false
|
| 60 |
+
// Check the Proxy-Authorization header
|
| 61 |
+
pAuth := req.Header.Get("Proxy-Authorization")
|
| 62 |
+
if strings.HasPrefix(pAuth, "Basic ") {
|
| 63 |
+
userPass, err := base64.URLEncoding.DecodeString(pAuth[6:])
|
| 64 |
+
if err == nil {
|
| 65 |
+
userPassParts := strings.SplitN(string(userPass), ":", 2)
|
| 66 |
+
if len(userPassParts) == 2 {
|
| 67 |
+
authOK = s.AuthFunc(userPassParts[0], userPassParts[1])
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
if !authOK {
|
| 72 |
+
// Proxy authentication required
|
| 73 |
+
_ = sendProxyAuthRequired(conn, req, s.AuthRealm)
|
| 74 |
+
_ = conn.Close()
|
| 75 |
+
return
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
if req.Method == http.MethodConnect {
|
| 79 |
+
if bufReader.Buffered() > 0 {
|
| 80 |
+
// There is still data in the buffered reader.
|
| 81 |
+
// We need to get it out and put it into a cachedConn,
|
| 82 |
+
// so that handleConnect can read it.
|
| 83 |
+
data := make([]byte, bufReader.Buffered())
|
| 84 |
+
_, err := io.ReadFull(bufReader, data)
|
| 85 |
+
if err != nil {
|
| 86 |
+
// Read from buffer failed, is this possible?
|
| 87 |
+
_ = conn.Close()
|
| 88 |
+
return
|
| 89 |
+
}
|
| 90 |
+
cachedConn := &cachedConn{
|
| 91 |
+
Conn: conn,
|
| 92 |
+
Buffer: *bytes.NewBuffer(data),
|
| 93 |
+
}
|
| 94 |
+
s.handleConnect(cachedConn, req)
|
| 95 |
+
} else {
|
| 96 |
+
// No data in the buffered reader, we can just pass the original connection.
|
| 97 |
+
s.handleConnect(conn, req)
|
| 98 |
+
}
|
| 99 |
+
// handleConnect will take over the connection,
|
| 100 |
+
// i.e. it will not return until the connection is closed.
|
| 101 |
+
// When it returns, there will be no more requests from this connection,
|
| 102 |
+
// so we simply exit the loop.
|
| 103 |
+
return
|
| 104 |
+
} else {
|
| 105 |
+
// handleRequest on the other hand handles one request at a time,
|
| 106 |
+
// and returns when the request is done. It returns a bool indicating
|
| 107 |
+
// whether the connection should be kept alive, but itself never closes
|
| 108 |
+
// the connection.
|
| 109 |
+
keepAlive := s.handleRequest(conn, req)
|
| 110 |
+
if !keepAlive {
|
| 111 |
+
_ = conn.Close()
|
| 112 |
+
return
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// cachedConn is a net.Conn wrapper that first Read()s from a buffer,
|
| 119 |
+
// and then from the underlying net.Conn when the buffer is drained.
|
| 120 |
+
type cachedConn struct {
|
| 121 |
+
net.Conn
|
| 122 |
+
Buffer bytes.Buffer
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
func (c *cachedConn) Read(b []byte) (int, error) {
|
| 126 |
+
if c.Buffer.Len() > 0 {
|
| 127 |
+
n, err := c.Buffer.Read(b)
|
| 128 |
+
if err == io.EOF {
|
| 129 |
+
// Buffer is drained, hide it from the caller
|
| 130 |
+
err = nil
|
| 131 |
+
}
|
| 132 |
+
return n, err
|
| 133 |
+
}
|
| 134 |
+
return c.Conn.Read(b)
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
func (s *Server) handleConnect(conn net.Conn, req *http.Request) {
|
| 138 |
+
defer conn.Close()
|
| 139 |
+
|
| 140 |
+
port := req.URL.Port()
|
| 141 |
+
if port == "" {
|
| 142 |
+
// HTTP defaults to port 80
|
| 143 |
+
port = "80"
|
| 144 |
+
}
|
| 145 |
+
reqAddr := net.JoinHostPort(req.URL.Hostname(), port)
|
| 146 |
+
|
| 147 |
+
// Connect request & error log
|
| 148 |
+
if s.EventLogger != nil {
|
| 149 |
+
s.EventLogger.ConnectRequest(conn.RemoteAddr(), reqAddr)
|
| 150 |
+
}
|
| 151 |
+
var closeErr error
|
| 152 |
+
defer func() {
|
| 153 |
+
if s.EventLogger != nil {
|
| 154 |
+
s.EventLogger.ConnectError(conn.RemoteAddr(), reqAddr, closeErr)
|
| 155 |
+
}
|
| 156 |
+
}()
|
| 157 |
+
|
| 158 |
+
// Dial
|
| 159 |
+
rConn, err := s.HyClient.TCP(reqAddr)
|
| 160 |
+
if err != nil {
|
| 161 |
+
_ = sendSimpleResponse(conn, req, http.StatusBadGateway)
|
| 162 |
+
closeErr = err
|
| 163 |
+
return
|
| 164 |
+
}
|
| 165 |
+
defer rConn.Close()
|
| 166 |
+
|
| 167 |
+
// Send 200 OK response and start relaying
|
| 168 |
+
_ = sendSimpleResponse(conn, req, http.StatusOK)
|
| 169 |
+
copyErrChan := make(chan error, 2)
|
| 170 |
+
go func() {
|
| 171 |
+
_, err := io.Copy(rConn, conn)
|
| 172 |
+
copyErrChan <- err
|
| 173 |
+
}()
|
| 174 |
+
go func() {
|
| 175 |
+
_, err := io.Copy(conn, rConn)
|
| 176 |
+
copyErrChan <- err
|
| 177 |
+
}()
|
| 178 |
+
closeErr = <-copyErrChan
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
func (s *Server) handleRequest(conn net.Conn, req *http.Request) bool {
|
| 182 |
+
// Some clients use Connection, some use Proxy-Connection
|
| 183 |
+
// https://www.oreilly.com/library/view/http-the-definitive/1565925092/re40.html
|
| 184 |
+
keepAlive := req.ProtoAtLeast(1, 1) &&
|
| 185 |
+
(strings.ToLower(req.Header.Get("Proxy-Connection")) == "keep-alive" ||
|
| 186 |
+
strings.ToLower(req.Header.Get("Connection")) == "keep-alive")
|
| 187 |
+
req.RequestURI = "" // Outgoing request should not have RequestURI
|
| 188 |
+
|
| 189 |
+
removeHopByHopHeaders(req.Header)
|
| 190 |
+
removeExtraHTTPHostPort(req)
|
| 191 |
+
|
| 192 |
+
if req.URL.Scheme == "" || req.URL.Host == "" {
|
| 193 |
+
_ = sendSimpleResponse(conn, req, http.StatusBadRequest)
|
| 194 |
+
return false
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// Request & error log
|
| 198 |
+
if s.EventLogger != nil {
|
| 199 |
+
s.EventLogger.HTTPRequest(conn.RemoteAddr(), req.URL.String())
|
| 200 |
+
}
|
| 201 |
+
var closeErr error
|
| 202 |
+
defer func() {
|
| 203 |
+
if s.EventLogger != nil {
|
| 204 |
+
s.EventLogger.HTTPError(conn.RemoteAddr(), req.URL.String(), closeErr)
|
| 205 |
+
}
|
| 206 |
+
}()
|
| 207 |
+
|
| 208 |
+
if s.httpClient == nil {
|
| 209 |
+
s.initHTTPClient()
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Do the request and send the response back
|
| 213 |
+
resp, err := s.httpClient.Do(req)
|
| 214 |
+
if err != nil {
|
| 215 |
+
closeErr = err
|
| 216 |
+
_ = sendSimpleResponse(conn, req, http.StatusBadGateway)
|
| 217 |
+
return false
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
removeHopByHopHeaders(resp.Header)
|
| 221 |
+
if keepAlive {
|
| 222 |
+
resp.Header.Set("Connection", "keep-alive")
|
| 223 |
+
resp.Header.Set("Proxy-Connection", "keep-alive")
|
| 224 |
+
resp.Header.Set("Keep-Alive", "timeout=60")
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
closeErr = resp.Write(conn)
|
| 228 |
+
return closeErr == nil && keepAlive
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
func (s *Server) initHTTPClient() {
|
| 232 |
+
s.httpClient = &http.Client{
|
| 233 |
+
Transport: &http.Transport{
|
| 234 |
+
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
| 235 |
+
// HyClient doesn't support context for now
|
| 236 |
+
return s.HyClient.TCP(addr)
|
| 237 |
+
},
|
| 238 |
+
},
|
| 239 |
+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
| 240 |
+
return http.ErrUseLastResponse
|
| 241 |
+
},
|
| 242 |
+
Timeout: httpClientTimeout,
|
| 243 |
+
}
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
func removeHopByHopHeaders(header http.Header) {
|
| 247 |
+
header.Del("Proxy-Connection") // Not in RFC but common
|
| 248 |
+
// https://www.ietf.org/rfc/rfc2616.txt
|
| 249 |
+
header.Del("Connection")
|
| 250 |
+
header.Del("Keep-Alive")
|
| 251 |
+
header.Del("Proxy-Authenticate")
|
| 252 |
+
header.Del("Proxy-Authorization")
|
| 253 |
+
header.Del("TE")
|
| 254 |
+
header.Del("Trailers")
|
| 255 |
+
header.Del("Transfer-Encoding")
|
| 256 |
+
header.Del("Upgrade")
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
func removeExtraHTTPHostPort(req *http.Request) {
|
| 260 |
+
host := req.Host
|
| 261 |
+
if host == "" {
|
| 262 |
+
host = req.URL.Host
|
| 263 |
+
}
|
| 264 |
+
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
|
| 265 |
+
host = pHost
|
| 266 |
+
}
|
| 267 |
+
req.Host = host
|
| 268 |
+
req.URL.Host = host
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
// sendSimpleResponse sends a simple HTTP response with the given status code.
|
| 272 |
+
func sendSimpleResponse(conn net.Conn, req *http.Request, statusCode int) error {
|
| 273 |
+
resp := &http.Response{
|
| 274 |
+
StatusCode: statusCode,
|
| 275 |
+
Status: http.StatusText(statusCode),
|
| 276 |
+
Proto: req.Proto,
|
| 277 |
+
ProtoMajor: req.ProtoMajor,
|
| 278 |
+
ProtoMinor: req.ProtoMinor,
|
| 279 |
+
Header: http.Header{},
|
| 280 |
+
}
|
| 281 |
+
// Remove the "Content-Length: 0" header, some clients (e.g. ffmpeg) may not like it.
|
| 282 |
+
resp.ContentLength = -1
|
| 283 |
+
// Also, prevent the "Connection: close" header.
|
| 284 |
+
resp.Close = false
|
| 285 |
+
resp.Uncompressed = true
|
| 286 |
+
return resp.Write(conn)
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
// sendProxyAuthRequired sends a 407 Proxy Authentication Required response.
|
| 290 |
+
func sendProxyAuthRequired(conn net.Conn, req *http.Request, realm string) error {
|
| 291 |
+
resp := &http.Response{
|
| 292 |
+
StatusCode: http.StatusProxyAuthRequired,
|
| 293 |
+
Status: http.StatusText(http.StatusProxyAuthRequired),
|
| 294 |
+
Proto: req.Proto,
|
| 295 |
+
ProtoMajor: req.ProtoMajor,
|
| 296 |
+
ProtoMinor: req.ProtoMinor,
|
| 297 |
+
Header: http.Header{},
|
| 298 |
+
}
|
| 299 |
+
resp.Header.Set("Proxy-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
|
| 300 |
+
return resp.Write(conn)
|
| 301 |
+
}
|
app/internal/http/server_test.go
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package http
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"errors"
|
| 5 |
+
"net"
|
| 6 |
+
"net/http"
|
| 7 |
+
"os/exec"
|
| 8 |
+
"strings"
|
| 9 |
+
"testing"
|
| 10 |
+
|
| 11 |
+
"github.com/stretchr/testify/assert"
|
| 12 |
+
|
| 13 |
+
"github.com/apernet/hysteria/core/v2/client"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
const (
|
| 17 |
+
testCertFile = "test.crt"
|
| 18 |
+
testKeyFile = "test.key"
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
type mockHyClient struct{}
|
| 22 |
+
|
| 23 |
+
func (c *mockHyClient) TCP(addr string) (net.Conn, error) {
|
| 24 |
+
return net.Dial("tcp", addr)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
func (c *mockHyClient) UDP() (client.HyUDPConn, error) {
|
| 28 |
+
return nil, errors.New("not implemented")
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
func (c *mockHyClient) Close() error {
|
| 32 |
+
return nil
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
func TestServer(t *testing.T) {
|
| 36 |
+
// Start the server
|
| 37 |
+
l, err := net.Listen("tcp", "127.0.0.1:18080")
|
| 38 |
+
assert.NoError(t, err)
|
| 39 |
+
defer l.Close()
|
| 40 |
+
s := &Server{
|
| 41 |
+
HyClient: &mockHyClient{},
|
| 42 |
+
}
|
| 43 |
+
go s.Serve(l)
|
| 44 |
+
|
| 45 |
+
// Start a test HTTP & HTTPS server
|
| 46 |
+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
| 47 |
+
w.Write([]byte("control is an illusion"))
|
| 48 |
+
})
|
| 49 |
+
go http.ListenAndServe("127.0.0.1:18081", nil)
|
| 50 |
+
go http.ListenAndServeTLS("127.0.0.1:18082", testCertFile, testKeyFile, nil)
|
| 51 |
+
|
| 52 |
+
// Run the Python test script
|
| 53 |
+
cmd := exec.Command("python", "server_test.py")
|
| 54 |
+
// Suppress HTTPS warning text from Python
|
| 55 |
+
cmd.Env = append(cmd.Env, "PYTHONWARNINGS=ignore:Unverified HTTPS request")
|
| 56 |
+
out, err := cmd.CombinedOutput()
|
| 57 |
+
assert.NoError(t, err)
|
| 58 |
+
assert.Equal(t, "OK", strings.TrimSpace(string(out)))
|
| 59 |
+
}
|
app/internal/http/server_test.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
|
| 3 |
+
proxies = {
|
| 4 |
+
"http": "http://127.0.0.1:18080",
|
| 5 |
+
"https": "http://127.0.0.1:18080",
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_http(it):
|
| 10 |
+
for i in range(it):
|
| 11 |
+
r = requests.get("http://127.0.0.1:18081", proxies=proxies)
|
| 12 |
+
assert r.status_code == 200 and r.text == "control is an illusion"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_https(it):
|
| 16 |
+
for i in range(it):
|
| 17 |
+
r = requests.get("https://127.0.0.1:18082", proxies=proxies, verify=False)
|
| 18 |
+
assert r.status_code == 200 and r.text == "control is an illusion"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if __name__ == "__main__":
|
| 22 |
+
test_http(10)
|
| 23 |
+
test_https(10)
|
| 24 |
+
print("OK")
|
app/internal/http/test.crt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
MIIDwTCCAqmgAwIBAgIUMeefneiCXWS2ovxNN+fJcdrOIfAwDQYJKoZIhvcNAQEL
|
| 3 |
+
BQAwcDELMAkGA1UEBhMCVFcxEzARBgNVBAgMClNvbWUtU3RhdGUxGTAXBgNVBAoM
|
| 4 |
+
EFJhbmRvbSBTdHVmZiBMTEMxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3
|
| 5 |
+
DQEJARYOcG9vcGVyQHNoaXQuY2MwHhcNMjMwNDI3MDAyMDQ1WhcNMzMwNDI0MDAy
|
| 6 |
+
MDQ1WjBwMQswCQYDVQQGEwJUVzETMBEGA1UECAwKU29tZS1TdGF0ZTEZMBcGA1UE
|
| 7 |
+
CgwQUmFuZG9tIFN0dWZmIExMQzESMBAGA1UEAwwJbG9jYWxob3N0MR0wGwYJKoZI
|
| 8 |
+
hvcNAQkBFg5wb29wZXJAc2hpdC5jYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
| 9 |
+
AQoCggEBAOU9/4AT/6fDKyEyZMMLFzUEVC8ZDJHoKZ+3g65ZFQLxRKqlEdhvOwq4
|
| 10 |
+
ZsxYF0sceUPDAsdrT+km0l1jAvq6u82n6xQQ60HpKe6hOvDX7KS0dPcKa+nfEa0W
|
| 11 |
+
DKamBB+TzxB2dBfBNS1oUU74nBb7ttpJiKnOpRJ0/J+CwslvhJzq04AUXC/W1CtW
|
| 12 |
+
CbZBg1JjY0fCN+Oy1WjEqMtRSB6k5Ipk40a8NcsqReBOMZChR8elruZ09sIlA6tf
|
| 13 |
+
jICOKToDVBmkjJ8m/GnxfV8MeLoK83M2VA73njsS6q9qe9KDVgIVQmifwi6JUb7N
|
| 14 |
+
o0A6f2Z47AWJmvq4goHJtnQ3fyoeIsMCAwEAAaNTMFEwHQYDVR0OBBYEFPrBsm6v
|
| 15 |
+
M29fKA3is22tK8yHYQaDMB8GA1UdIwQYMBaAFPrBsm6vM29fKA3is22tK8yHYQaD
|
| 16 |
+
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJvOwj0Tf8l9AWvf
|
| 17 |
+
1ZLyW0K3m5oJAoUayjlLP9q7KHgJHWd4QXxg4ApUDo523m4Own3FwtN06KCMqlxc
|
| 18 |
+
luDJi27ghRzZ8bpB9fUujikC1rs1oWYRz/K+JSO1VItan+azm9AQRj+nNepjUiT4
|
| 19 |
+
FjvRif+inC4392tcKuwrqiUFmLIggtFZdsLeKUL+hRGCRjY4BZw0d1sjjPtyVNUD
|
| 20 |
+
UMVO8pxlCV0NU4Nmt3vulD4YshAXM+Y8yX/vPRnaNGoRrbRgCg2VORRGaZVjQMHD
|
| 21 |
+
OLMvqM7pFKnVg0uiSbQ3xbQJ8WeX620zKI0So2+kZt9HoI+46gd7BdNfl7mmd6K7
|
| 22 |
+
ydYKuI8=
|
| 23 |
+
-----END CERTIFICATE-----
|
app/internal/http/test.key
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN RSA PRIVATE KEY-----
|
| 2 |
+
MIIEowIBAAKCAQEA5T3/gBP/p8MrITJkwwsXNQRULxkMkegpn7eDrlkVAvFEqqUR
|
| 3 |
+
2G87CrhmzFgXSxx5Q8MCx2tP6SbSXWMC+rq7zafrFBDrQekp7qE68NfspLR09wpr
|
| 4 |
+
6d8RrRYMpqYEH5PPEHZ0F8E1LWhRTvicFvu22kmIqc6lEnT8n4LCyW+EnOrTgBRc
|
| 5 |
+
L9bUK1YJtkGDUmNjR8I347LVaMSoy1FIHqTkimTjRrw1yypF4E4xkKFHx6Wu5nT2
|
| 6 |
+
wiUDq1+MgI4pOgNUGaSMnyb8afF9Xwx4ugrzczZUDveeOxLqr2p70oNWAhVCaJ/C
|
| 7 |
+
LolRvs2jQDp/ZnjsBYma+riCgcm2dDd/Kh4iwwIDAQABAoIBABjiU/vJL/U8AFCI
|
| 8 |
+
MdviNlCw+ZprM6wa8Xm+5/JjBR7epb+IT5mY6WXOgoon/c9PdfJfFswi3/fFGQy+
|
| 9 |
+
FLK21nAKjEAPXho3fy/CHK3MIon2dMPkQ7aNWlPZkuH8H3J2DwIQeaWieW1GZ50U
|
| 10 |
+
64yrIjwrw0P7hHuua0W9YfuPuWt29YpW5g6ilSRE0kdTzoB6TgMzlVRj6RWbxWLX
|
| 11 |
+
erwYFesSpLPiQrozK2yywlQsvRV2AxTlf5woJyRTyCqcao5jNZOJJl0mqeGKNKbu
|
| 12 |
+
1iYGtZl9aj1XIRxUt+JB2IMKNJasygIp+GRLUDCHKh8RVFwRlVaSNcWbfLDuyNWW
|
| 13 |
+
T3lUEjECgYEA84mrs4TLuPfklsQM4WPBdN/2Ud1r0Zn/W8icHcVc/DCFXbcV4aPA
|
| 14 |
+
g4yyyyEkyTac2RSbSp+rfUk/pJcG6CVjwaiRIPehdtcLIUP34EdIrwPrPT7/uWVA
|
| 15 |
+
o/Hp1ANSILecknQXeE1qDlHVeGAq2k3vAQH2J0m7lMfar7QCBTMTMHcCgYEA8PkO
|
| 16 |
+
Uj9+/LoHod2eb4raH29wntis31X5FX/C/8HlmFmQplxfMxpRckzDYQELdHvDggNY
|
| 17 |
+
ZQo6pdE22MjCu2bk9AHa2ukMyieWm/mPe46Upr1YV2o5cWnfFFNa/LP2Ii/dWY5V
|
| 18 |
+
rFNsHFnrnwcWymX7OKo0Xb8xYnKhKZJAFwSpXxUCgYBPMjXj6wtU20g6vwZxRT9k
|
| 19 |
+
AnDXrmmhf7LK5jHefJAAcsbr8t3qwpWYMejypZSQ2nGnJkxZuBLMa0WHAJX+aCpI
|
| 20 |
+
j8iiL+USAFxeNPwmswev4lZdVF9Uqtiad9DSYUIT4aHI/nejZ4lVnscMnjlRRIa0
|
| 21 |
+
jS6/F/soJtW2zZLangFfgQKBgCOSAAUwDkSsCThhiGOasXv2bT9laI9HF4+O3m/2
|
| 22 |
+
ZTfJ8Mo91GesuN0Qa77D8rbtFfz5FXFEw0d6zIfPir8y/xTtuSqbQCIPGfJIMl/g
|
| 23 |
+
uhyq0oGE0pnlMOLFMyceQXTmb9wqYIchgVHmDBvbZgfWafEBXt1/vYB0v0ltpzw+
|
| 24 |
+
menJAoGBAI0hx3+mrFgA+xJBEk4oexAlro1qbNWoR7BCmLQtd49jG3eZQu4JxWH2
|
| 25 |
+
kh58AIXzLl0X9t4pfMYasYL6jBGvw+AqNdo2krpiL7MWEE8w8FP/wibzqmuloziB
|
| 26 |
+
T7BZuCZjpcAM0IxLmQeeUK0LF0mihcqvssxveaet46mj7QoA7bGQ
|
| 27 |
+
-----END RSA PRIVATE KEY-----
|
app/internal/proxymux/.mockery.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
with-expecter: true
|
| 2 |
+
dir: internal/mocks
|
| 3 |
+
outpkg: mocks
|
| 4 |
+
packages:
|
| 5 |
+
net:
|
| 6 |
+
interfaces:
|
| 7 |
+
Listener:
|
| 8 |
+
config:
|
| 9 |
+
mockname: MockListener
|
| 10 |
+
Conn:
|
| 11 |
+
config:
|
| 12 |
+
mockname: MockConn
|
app/internal/proxymux/internal/mocks/mock_Conn.go
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Code generated by mockery v2.43.0. DO NOT EDIT.
|
| 2 |
+
|
| 3 |
+
package mocks
|
| 4 |
+
|
| 5 |
+
import (
|
| 6 |
+
net "net"
|
| 7 |
+
|
| 8 |
+
mock "github.com/stretchr/testify/mock"
|
| 9 |
+
|
| 10 |
+
time "time"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
// MockConn is an autogenerated mock type for the Conn type
|
| 14 |
+
type MockConn struct {
|
| 15 |
+
mock.Mock
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
type MockConn_Expecter struct {
|
| 19 |
+
mock *mock.Mock
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
func (_m *MockConn) EXPECT() *MockConn_Expecter {
|
| 23 |
+
return &MockConn_Expecter{mock: &_m.Mock}
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
// Close provides a mock function with given fields:
|
| 27 |
+
func (_m *MockConn) Close() error {
|
| 28 |
+
ret := _m.Called()
|
| 29 |
+
|
| 30 |
+
if len(ret) == 0 {
|
| 31 |
+
panic("no return value specified for Close")
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
var r0 error
|
| 35 |
+
if rf, ok := ret.Get(0).(func() error); ok {
|
| 36 |
+
r0 = rf()
|
| 37 |
+
} else {
|
| 38 |
+
r0 = ret.Error(0)
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
return r0
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// MockConn_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
| 45 |
+
type MockConn_Close_Call struct {
|
| 46 |
+
*mock.Call
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Close is a helper method to define mock.On call
|
| 50 |
+
func (_e *MockConn_Expecter) Close() *MockConn_Close_Call {
|
| 51 |
+
return &MockConn_Close_Call{Call: _e.mock.On("Close")}
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
func (_c *MockConn_Close_Call) Run(run func()) *MockConn_Close_Call {
|
| 55 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 56 |
+
run()
|
| 57 |
+
})
|
| 58 |
+
return _c
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
func (_c *MockConn_Close_Call) Return(_a0 error) *MockConn_Close_Call {
|
| 62 |
+
_c.Call.Return(_a0)
|
| 63 |
+
return _c
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
func (_c *MockConn_Close_Call) RunAndReturn(run func() error) *MockConn_Close_Call {
|
| 67 |
+
_c.Call.Return(run)
|
| 68 |
+
return _c
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// LocalAddr provides a mock function with given fields:
|
| 72 |
+
func (_m *MockConn) LocalAddr() net.Addr {
|
| 73 |
+
ret := _m.Called()
|
| 74 |
+
|
| 75 |
+
if len(ret) == 0 {
|
| 76 |
+
panic("no return value specified for LocalAddr")
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
var r0 net.Addr
|
| 80 |
+
if rf, ok := ret.Get(0).(func() net.Addr); ok {
|
| 81 |
+
r0 = rf()
|
| 82 |
+
} else {
|
| 83 |
+
if ret.Get(0) != nil {
|
| 84 |
+
r0 = ret.Get(0).(net.Addr)
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
return r0
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
// MockConn_LocalAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LocalAddr'
|
| 92 |
+
type MockConn_LocalAddr_Call struct {
|
| 93 |
+
*mock.Call
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// LocalAddr is a helper method to define mock.On call
|
| 97 |
+
func (_e *MockConn_Expecter) LocalAddr() *MockConn_LocalAddr_Call {
|
| 98 |
+
return &MockConn_LocalAddr_Call{Call: _e.mock.On("LocalAddr")}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
func (_c *MockConn_LocalAddr_Call) Run(run func()) *MockConn_LocalAddr_Call {
|
| 102 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 103 |
+
run()
|
| 104 |
+
})
|
| 105 |
+
return _c
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
func (_c *MockConn_LocalAddr_Call) Return(_a0 net.Addr) *MockConn_LocalAddr_Call {
|
| 109 |
+
_c.Call.Return(_a0)
|
| 110 |
+
return _c
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
func (_c *MockConn_LocalAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_LocalAddr_Call {
|
| 114 |
+
_c.Call.Return(run)
|
| 115 |
+
return _c
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// Read provides a mock function with given fields: b
|
| 119 |
+
func (_m *MockConn) Read(b []byte) (int, error) {
|
| 120 |
+
ret := _m.Called(b)
|
| 121 |
+
|
| 122 |
+
if len(ret) == 0 {
|
| 123 |
+
panic("no return value specified for Read")
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
var r0 int
|
| 127 |
+
var r1 error
|
| 128 |
+
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
|
| 129 |
+
return rf(b)
|
| 130 |
+
}
|
| 131 |
+
if rf, ok := ret.Get(0).(func([]byte) int); ok {
|
| 132 |
+
r0 = rf(b)
|
| 133 |
+
} else {
|
| 134 |
+
r0 = ret.Get(0).(int)
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
| 138 |
+
r1 = rf(b)
|
| 139 |
+
} else {
|
| 140 |
+
r1 = ret.Error(1)
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
return r0, r1
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// MockConn_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
| 147 |
+
type MockConn_Read_Call struct {
|
| 148 |
+
*mock.Call
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// Read is a helper method to define mock.On call
|
| 152 |
+
// - b []byte
|
| 153 |
+
func (_e *MockConn_Expecter) Read(b interface{}) *MockConn_Read_Call {
|
| 154 |
+
return &MockConn_Read_Call{Call: _e.mock.On("Read", b)}
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
func (_c *MockConn_Read_Call) Run(run func(b []byte)) *MockConn_Read_Call {
|
| 158 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 159 |
+
run(args[0].([]byte))
|
| 160 |
+
})
|
| 161 |
+
return _c
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
func (_c *MockConn_Read_Call) Return(n int, err error) *MockConn_Read_Call {
|
| 165 |
+
_c.Call.Return(n, err)
|
| 166 |
+
return _c
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
func (_c *MockConn_Read_Call) RunAndReturn(run func([]byte) (int, error)) *MockConn_Read_Call {
|
| 170 |
+
_c.Call.Return(run)
|
| 171 |
+
return _c
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
// RemoteAddr provides a mock function with given fields:
|
| 175 |
+
func (_m *MockConn) RemoteAddr() net.Addr {
|
| 176 |
+
ret := _m.Called()
|
| 177 |
+
|
| 178 |
+
if len(ret) == 0 {
|
| 179 |
+
panic("no return value specified for RemoteAddr")
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
var r0 net.Addr
|
| 183 |
+
if rf, ok := ret.Get(0).(func() net.Addr); ok {
|
| 184 |
+
r0 = rf()
|
| 185 |
+
} else {
|
| 186 |
+
if ret.Get(0) != nil {
|
| 187 |
+
r0 = ret.Get(0).(net.Addr)
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
return r0
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
// MockConn_RemoteAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoteAddr'
|
| 195 |
+
type MockConn_RemoteAddr_Call struct {
|
| 196 |
+
*mock.Call
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// RemoteAddr is a helper method to define mock.On call
|
| 200 |
+
func (_e *MockConn_Expecter) RemoteAddr() *MockConn_RemoteAddr_Call {
|
| 201 |
+
return &MockConn_RemoteAddr_Call{Call: _e.mock.On("RemoteAddr")}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
func (_c *MockConn_RemoteAddr_Call) Run(run func()) *MockConn_RemoteAddr_Call {
|
| 205 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 206 |
+
run()
|
| 207 |
+
})
|
| 208 |
+
return _c
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
func (_c *MockConn_RemoteAddr_Call) Return(_a0 net.Addr) *MockConn_RemoteAddr_Call {
|
| 212 |
+
_c.Call.Return(_a0)
|
| 213 |
+
return _c
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
func (_c *MockConn_RemoteAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_RemoteAddr_Call {
|
| 217 |
+
_c.Call.Return(run)
|
| 218 |
+
return _c
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// SetDeadline provides a mock function with given fields: t
|
| 222 |
+
func (_m *MockConn) SetDeadline(t time.Time) error {
|
| 223 |
+
ret := _m.Called(t)
|
| 224 |
+
|
| 225 |
+
if len(ret) == 0 {
|
| 226 |
+
panic("no return value specified for SetDeadline")
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
var r0 error
|
| 230 |
+
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
|
| 231 |
+
r0 = rf(t)
|
| 232 |
+
} else {
|
| 233 |
+
r0 = ret.Error(0)
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
return r0
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// MockConn_SetDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDeadline'
|
| 240 |
+
type MockConn_SetDeadline_Call struct {
|
| 241 |
+
*mock.Call
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// SetDeadline is a helper method to define mock.On call
|
| 245 |
+
// - t time.Time
|
| 246 |
+
func (_e *MockConn_Expecter) SetDeadline(t interface{}) *MockConn_SetDeadline_Call {
|
| 247 |
+
return &MockConn_SetDeadline_Call{Call: _e.mock.On("SetDeadline", t)}
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
func (_c *MockConn_SetDeadline_Call) Run(run func(t time.Time)) *MockConn_SetDeadline_Call {
|
| 251 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 252 |
+
run(args[0].(time.Time))
|
| 253 |
+
})
|
| 254 |
+
return _c
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
func (_c *MockConn_SetDeadline_Call) Return(_a0 error) *MockConn_SetDeadline_Call {
|
| 258 |
+
_c.Call.Return(_a0)
|
| 259 |
+
return _c
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
func (_c *MockConn_SetDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetDeadline_Call {
|
| 263 |
+
_c.Call.Return(run)
|
| 264 |
+
return _c
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
// SetReadDeadline provides a mock function with given fields: t
|
| 268 |
+
func (_m *MockConn) SetReadDeadline(t time.Time) error {
|
| 269 |
+
ret := _m.Called(t)
|
| 270 |
+
|
| 271 |
+
if len(ret) == 0 {
|
| 272 |
+
panic("no return value specified for SetReadDeadline")
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
var r0 error
|
| 276 |
+
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
|
| 277 |
+
r0 = rf(t)
|
| 278 |
+
} else {
|
| 279 |
+
r0 = ret.Error(0)
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
return r0
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
// MockConn_SetReadDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetReadDeadline'
|
| 286 |
+
type MockConn_SetReadDeadline_Call struct {
|
| 287 |
+
*mock.Call
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
// SetReadDeadline is a helper method to define mock.On call
|
| 291 |
+
// - t time.Time
|
| 292 |
+
func (_e *MockConn_Expecter) SetReadDeadline(t interface{}) *MockConn_SetReadDeadline_Call {
|
| 293 |
+
return &MockConn_SetReadDeadline_Call{Call: _e.mock.On("SetReadDeadline", t)}
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
func (_c *MockConn_SetReadDeadline_Call) Run(run func(t time.Time)) *MockConn_SetReadDeadline_Call {
|
| 297 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 298 |
+
run(args[0].(time.Time))
|
| 299 |
+
})
|
| 300 |
+
return _c
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
func (_c *MockConn_SetReadDeadline_Call) Return(_a0 error) *MockConn_SetReadDeadline_Call {
|
| 304 |
+
_c.Call.Return(_a0)
|
| 305 |
+
return _c
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
func (_c *MockConn_SetReadDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetReadDeadline_Call {
|
| 309 |
+
_c.Call.Return(run)
|
| 310 |
+
return _c
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
// SetWriteDeadline provides a mock function with given fields: t
|
| 314 |
+
func (_m *MockConn) SetWriteDeadline(t time.Time) error {
|
| 315 |
+
ret := _m.Called(t)
|
| 316 |
+
|
| 317 |
+
if len(ret) == 0 {
|
| 318 |
+
panic("no return value specified for SetWriteDeadline")
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
var r0 error
|
| 322 |
+
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
|
| 323 |
+
r0 = rf(t)
|
| 324 |
+
} else {
|
| 325 |
+
r0 = ret.Error(0)
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
return r0
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
// MockConn_SetWriteDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetWriteDeadline'
|
| 332 |
+
type MockConn_SetWriteDeadline_Call struct {
|
| 333 |
+
*mock.Call
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
// SetWriteDeadline is a helper method to define mock.On call
|
| 337 |
+
// - t time.Time
|
| 338 |
+
func (_e *MockConn_Expecter) SetWriteDeadline(t interface{}) *MockConn_SetWriteDeadline_Call {
|
| 339 |
+
return &MockConn_SetWriteDeadline_Call{Call: _e.mock.On("SetWriteDeadline", t)}
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
func (_c *MockConn_SetWriteDeadline_Call) Run(run func(t time.Time)) *MockConn_SetWriteDeadline_Call {
|
| 343 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 344 |
+
run(args[0].(time.Time))
|
| 345 |
+
})
|
| 346 |
+
return _c
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
func (_c *MockConn_SetWriteDeadline_Call) Return(_a0 error) *MockConn_SetWriteDeadline_Call {
|
| 350 |
+
_c.Call.Return(_a0)
|
| 351 |
+
return _c
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
func (_c *MockConn_SetWriteDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetWriteDeadline_Call {
|
| 355 |
+
_c.Call.Return(run)
|
| 356 |
+
return _c
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// Write provides a mock function with given fields: b
|
| 360 |
+
func (_m *MockConn) Write(b []byte) (int, error) {
|
| 361 |
+
ret := _m.Called(b)
|
| 362 |
+
|
| 363 |
+
if len(ret) == 0 {
|
| 364 |
+
panic("no return value specified for Write")
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
var r0 int
|
| 368 |
+
var r1 error
|
| 369 |
+
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
|
| 370 |
+
return rf(b)
|
| 371 |
+
}
|
| 372 |
+
if rf, ok := ret.Get(0).(func([]byte) int); ok {
|
| 373 |
+
r0 = rf(b)
|
| 374 |
+
} else {
|
| 375 |
+
r0 = ret.Get(0).(int)
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
| 379 |
+
r1 = rf(b)
|
| 380 |
+
} else {
|
| 381 |
+
r1 = ret.Error(1)
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
return r0, r1
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
// MockConn_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
| 388 |
+
type MockConn_Write_Call struct {
|
| 389 |
+
*mock.Call
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
// Write is a helper method to define mock.On call
|
| 393 |
+
// - b []byte
|
| 394 |
+
func (_e *MockConn_Expecter) Write(b interface{}) *MockConn_Write_Call {
|
| 395 |
+
return &MockConn_Write_Call{Call: _e.mock.On("Write", b)}
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
func (_c *MockConn_Write_Call) Run(run func(b []byte)) *MockConn_Write_Call {
|
| 399 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 400 |
+
run(args[0].([]byte))
|
| 401 |
+
})
|
| 402 |
+
return _c
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
func (_c *MockConn_Write_Call) Return(n int, err error) *MockConn_Write_Call {
|
| 406 |
+
_c.Call.Return(n, err)
|
| 407 |
+
return _c
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
func (_c *MockConn_Write_Call) RunAndReturn(run func([]byte) (int, error)) *MockConn_Write_Call {
|
| 411 |
+
_c.Call.Return(run)
|
| 412 |
+
return _c
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
// NewMockConn creates a new instance of MockConn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
| 416 |
+
// The first argument is typically a *testing.T value.
|
| 417 |
+
func NewMockConn(t interface {
|
| 418 |
+
mock.TestingT
|
| 419 |
+
Cleanup(func())
|
| 420 |
+
}) *MockConn {
|
| 421 |
+
mock := &MockConn{}
|
| 422 |
+
mock.Mock.Test(t)
|
| 423 |
+
|
| 424 |
+
t.Cleanup(func() { mock.AssertExpectations(t) })
|
| 425 |
+
|
| 426 |
+
return mock
|
| 427 |
+
}
|
app/internal/proxymux/internal/mocks/mock_Listener.go
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Code generated by mockery v2.43.0. DO NOT EDIT.
|
| 2 |
+
|
| 3 |
+
package mocks
|
| 4 |
+
|
| 5 |
+
import (
|
| 6 |
+
net "net"
|
| 7 |
+
|
| 8 |
+
mock "github.com/stretchr/testify/mock"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
// MockListener is an autogenerated mock type for the Listener type
|
| 12 |
+
type MockListener struct {
|
| 13 |
+
mock.Mock
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
type MockListener_Expecter struct {
|
| 17 |
+
mock *mock.Mock
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
func (_m *MockListener) EXPECT() *MockListener_Expecter {
|
| 21 |
+
return &MockListener_Expecter{mock: &_m.Mock}
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
// Accept provides a mock function with given fields:
|
| 25 |
+
func (_m *MockListener) Accept() (net.Conn, error) {
|
| 26 |
+
ret := _m.Called()
|
| 27 |
+
|
| 28 |
+
if len(ret) == 0 {
|
| 29 |
+
panic("no return value specified for Accept")
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
var r0 net.Conn
|
| 33 |
+
var r1 error
|
| 34 |
+
if rf, ok := ret.Get(0).(func() (net.Conn, error)); ok {
|
| 35 |
+
return rf()
|
| 36 |
+
}
|
| 37 |
+
if rf, ok := ret.Get(0).(func() net.Conn); ok {
|
| 38 |
+
r0 = rf()
|
| 39 |
+
} else {
|
| 40 |
+
if ret.Get(0) != nil {
|
| 41 |
+
r0 = ret.Get(0).(net.Conn)
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
if rf, ok := ret.Get(1).(func() error); ok {
|
| 46 |
+
r1 = rf()
|
| 47 |
+
} else {
|
| 48 |
+
r1 = ret.Error(1)
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
return r0, r1
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
// MockListener_Accept_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Accept'
|
| 55 |
+
type MockListener_Accept_Call struct {
|
| 56 |
+
*mock.Call
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// Accept is a helper method to define mock.On call
|
| 60 |
+
func (_e *MockListener_Expecter) Accept() *MockListener_Accept_Call {
|
| 61 |
+
return &MockListener_Accept_Call{Call: _e.mock.On("Accept")}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
func (_c *MockListener_Accept_Call) Run(run func()) *MockListener_Accept_Call {
|
| 65 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 66 |
+
run()
|
| 67 |
+
})
|
| 68 |
+
return _c
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
func (_c *MockListener_Accept_Call) Return(_a0 net.Conn, _a1 error) *MockListener_Accept_Call {
|
| 72 |
+
_c.Call.Return(_a0, _a1)
|
| 73 |
+
return _c
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
func (_c *MockListener_Accept_Call) RunAndReturn(run func() (net.Conn, error)) *MockListener_Accept_Call {
|
| 77 |
+
_c.Call.Return(run)
|
| 78 |
+
return _c
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// Addr provides a mock function with given fields:
|
| 82 |
+
func (_m *MockListener) Addr() net.Addr {
|
| 83 |
+
ret := _m.Called()
|
| 84 |
+
|
| 85 |
+
if len(ret) == 0 {
|
| 86 |
+
panic("no return value specified for Addr")
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
var r0 net.Addr
|
| 90 |
+
if rf, ok := ret.Get(0).(func() net.Addr); ok {
|
| 91 |
+
r0 = rf()
|
| 92 |
+
} else {
|
| 93 |
+
if ret.Get(0) != nil {
|
| 94 |
+
r0 = ret.Get(0).(net.Addr)
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
return r0
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
// MockListener_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr'
|
| 102 |
+
type MockListener_Addr_Call struct {
|
| 103 |
+
*mock.Call
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// Addr is a helper method to define mock.On call
|
| 107 |
+
func (_e *MockListener_Expecter) Addr() *MockListener_Addr_Call {
|
| 108 |
+
return &MockListener_Addr_Call{Call: _e.mock.On("Addr")}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
func (_c *MockListener_Addr_Call) Run(run func()) *MockListener_Addr_Call {
|
| 112 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 113 |
+
run()
|
| 114 |
+
})
|
| 115 |
+
return _c
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
func (_c *MockListener_Addr_Call) Return(_a0 net.Addr) *MockListener_Addr_Call {
|
| 119 |
+
_c.Call.Return(_a0)
|
| 120 |
+
return _c
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
func (_c *MockListener_Addr_Call) RunAndReturn(run func() net.Addr) *MockListener_Addr_Call {
|
| 124 |
+
_c.Call.Return(run)
|
| 125 |
+
return _c
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
// Close provides a mock function with given fields:
|
| 129 |
+
func (_m *MockListener) Close() error {
|
| 130 |
+
ret := _m.Called()
|
| 131 |
+
|
| 132 |
+
if len(ret) == 0 {
|
| 133 |
+
panic("no return value specified for Close")
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
var r0 error
|
| 137 |
+
if rf, ok := ret.Get(0).(func() error); ok {
|
| 138 |
+
r0 = rf()
|
| 139 |
+
} else {
|
| 140 |
+
r0 = ret.Error(0)
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
return r0
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// MockListener_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
| 147 |
+
type MockListener_Close_Call struct {
|
| 148 |
+
*mock.Call
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// Close is a helper method to define mock.On call
|
| 152 |
+
func (_e *MockListener_Expecter) Close() *MockListener_Close_Call {
|
| 153 |
+
return &MockListener_Close_Call{Call: _e.mock.On("Close")}
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
func (_c *MockListener_Close_Call) Run(run func()) *MockListener_Close_Call {
|
| 157 |
+
_c.Call.Run(func(args mock.Arguments) {
|
| 158 |
+
run()
|
| 159 |
+
})
|
| 160 |
+
return _c
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
func (_c *MockListener_Close_Call) Return(_a0 error) *MockListener_Close_Call {
|
| 164 |
+
_c.Call.Return(_a0)
|
| 165 |
+
return _c
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
func (_c *MockListener_Close_Call) RunAndReturn(run func() error) *MockListener_Close_Call {
|
| 169 |
+
_c.Call.Return(run)
|
| 170 |
+
return _c
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
// NewMockListener creates a new instance of MockListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
| 174 |
+
// The first argument is typically a *testing.T value.
|
| 175 |
+
func NewMockListener(t interface {
|
| 176 |
+
mock.TestingT
|
| 177 |
+
Cleanup(func())
|
| 178 |
+
}) *MockListener {
|
| 179 |
+
mock := &MockListener{}
|
| 180 |
+
mock.Mock.Test(t)
|
| 181 |
+
|
| 182 |
+
t.Cleanup(func() { mock.AssertExpectations(t) })
|
| 183 |
+
|
| 184 |
+
return mock
|
| 185 |
+
}
|
app/internal/proxymux/manager.go
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package proxymux
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"net"
|
| 5 |
+
"sync"
|
| 6 |
+
|
| 7 |
+
"github.com/apernet/hysteria/extras/v2/correctnet"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
type muxManager struct {
|
| 11 |
+
listeners map[string]*muxListener
|
| 12 |
+
lock sync.Mutex
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
var globalMuxManager *muxManager
|
| 16 |
+
|
| 17 |
+
func init() {
|
| 18 |
+
globalMuxManager = &muxManager{
|
| 19 |
+
listeners: make(map[string]*muxListener),
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
func (m *muxManager) GetOrCreate(address string) (*muxListener, error) {
|
| 24 |
+
key, err := m.canonicalizeAddrPort(address)
|
| 25 |
+
if err != nil {
|
| 26 |
+
return nil, err
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
m.lock.Lock()
|
| 30 |
+
defer m.lock.Unlock()
|
| 31 |
+
|
| 32 |
+
if ml, ok := m.listeners[key]; ok {
|
| 33 |
+
return ml, nil
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
listener, err := correctnet.Listen("tcp", key)
|
| 37 |
+
if err != nil {
|
| 38 |
+
return nil, err
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
ml := newMuxListener(listener, func() {
|
| 42 |
+
m.lock.Lock()
|
| 43 |
+
defer m.lock.Unlock()
|
| 44 |
+
delete(m.listeners, key)
|
| 45 |
+
})
|
| 46 |
+
m.listeners[key] = ml
|
| 47 |
+
return ml, nil
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
func (m *muxManager) canonicalizeAddrPort(address string) (string, error) {
|
| 51 |
+
taddr, err := net.ResolveTCPAddr("tcp", address)
|
| 52 |
+
if err != nil {
|
| 53 |
+
return "", err
|
| 54 |
+
}
|
| 55 |
+
return taddr.String(), nil
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
func ListenHTTP(address string) (net.Listener, error) {
|
| 59 |
+
ml, err := globalMuxManager.GetOrCreate(address)
|
| 60 |
+
if err != nil {
|
| 61 |
+
return nil, err
|
| 62 |
+
}
|
| 63 |
+
return ml.ListenHTTP()
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
func ListenSOCKS(address string) (net.Listener, error) {
|
| 67 |
+
ml, err := globalMuxManager.GetOrCreate(address)
|
| 68 |
+
if err != nil {
|
| 69 |
+
return nil, err
|
| 70 |
+
}
|
| 71 |
+
return ml.ListenSOCKS()
|
| 72 |
+
}
|
app/internal/proxymux/manager_test.go
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package proxymux
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"net"
|
| 5 |
+
"testing"
|
| 6 |
+
"time"
|
| 7 |
+
|
| 8 |
+
"github.com/stretchr/testify/assert"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
func TestListenSOCKS(t *testing.T) {
|
| 12 |
+
address := "127.2.39.129:11081"
|
| 13 |
+
|
| 14 |
+
sl, err := ListenSOCKS(address)
|
| 15 |
+
if !assert.NoError(t, err) {
|
| 16 |
+
return
|
| 17 |
+
}
|
| 18 |
+
defer func() {
|
| 19 |
+
sl.Close()
|
| 20 |
+
}()
|
| 21 |
+
|
| 22 |
+
hl, err := ListenHTTP(address)
|
| 23 |
+
if !assert.NoError(t, err) {
|
| 24 |
+
return
|
| 25 |
+
}
|
| 26 |
+
defer hl.Close()
|
| 27 |
+
|
| 28 |
+
_, err = ListenSOCKS(address)
|
| 29 |
+
if !assert.ErrorIs(t, err, ErrProtocolInUse) {
|
| 30 |
+
return
|
| 31 |
+
}
|
| 32 |
+
sl.Close()
|
| 33 |
+
|
| 34 |
+
sl, err = ListenSOCKS(address)
|
| 35 |
+
if !assert.NoError(t, err) {
|
| 36 |
+
return
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
func TestListenHTTP(t *testing.T) {
|
| 41 |
+
address := "127.2.39.129:11082"
|
| 42 |
+
|
| 43 |
+
hl, err := ListenHTTP(address)
|
| 44 |
+
if !assert.NoError(t, err) {
|
| 45 |
+
return
|
| 46 |
+
}
|
| 47 |
+
defer func() {
|
| 48 |
+
hl.Close()
|
| 49 |
+
}()
|
| 50 |
+
|
| 51 |
+
sl, err := ListenSOCKS(address)
|
| 52 |
+
if !assert.NoError(t, err) {
|
| 53 |
+
return
|
| 54 |
+
}
|
| 55 |
+
defer sl.Close()
|
| 56 |
+
|
| 57 |
+
_, err = ListenHTTP(address)
|
| 58 |
+
if !assert.ErrorIs(t, err, ErrProtocolInUse) {
|
| 59 |
+
return
|
| 60 |
+
}
|
| 61 |
+
hl.Close()
|
| 62 |
+
|
| 63 |
+
hl, err = ListenHTTP(address)
|
| 64 |
+
if !assert.NoError(t, err) {
|
| 65 |
+
return
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
func TestRelease(t *testing.T) {
|
| 70 |
+
address := "127.2.39.129:11083"
|
| 71 |
+
|
| 72 |
+
hl, err := ListenHTTP(address)
|
| 73 |
+
if !assert.NoError(t, err) {
|
| 74 |
+
return
|
| 75 |
+
}
|
| 76 |
+
sl, err := ListenSOCKS(address)
|
| 77 |
+
if !assert.NoError(t, err) {
|
| 78 |
+
return
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
if !assert.True(t, globalMuxManager.testAddressExists(address)) {
|
| 82 |
+
return
|
| 83 |
+
}
|
| 84 |
+
_, err = net.Listen("tcp", address)
|
| 85 |
+
if !assert.Error(t, err) {
|
| 86 |
+
return
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
hl.Close()
|
| 90 |
+
sl.Close()
|
| 91 |
+
|
| 92 |
+
// Wait for muxListener released
|
| 93 |
+
time.Sleep(time.Second)
|
| 94 |
+
if !assert.False(t, globalMuxManager.testAddressExists(address)) {
|
| 95 |
+
return
|
| 96 |
+
}
|
| 97 |
+
lis, err := net.Listen("tcp", address)
|
| 98 |
+
if !assert.NoError(t, err) {
|
| 99 |
+
return
|
| 100 |
+
}
|
| 101 |
+
defer lis.Close()
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
func (m *muxManager) testAddressExists(address string) bool {
|
| 105 |
+
m.lock.Lock()
|
| 106 |
+
defer m.lock.Unlock()
|
| 107 |
+
|
| 108 |
+
_, ok := m.listeners[address]
|
| 109 |
+
return ok
|
| 110 |
+
}
|
app/internal/proxymux/mux.go
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package proxymux
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"errors"
|
| 5 |
+
"fmt"
|
| 6 |
+
"io"
|
| 7 |
+
"net"
|
| 8 |
+
"sync"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
func newMuxListener(listener net.Listener, deleteFunc func()) *muxListener {
|
| 12 |
+
l := &muxListener{
|
| 13 |
+
base: listener,
|
| 14 |
+
acceptChan: make(chan net.Conn),
|
| 15 |
+
closeChan: make(chan struct{}),
|
| 16 |
+
deleteFunc: deleteFunc,
|
| 17 |
+
}
|
| 18 |
+
go l.acceptLoop()
|
| 19 |
+
go l.mainLoop()
|
| 20 |
+
return l
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
type muxListener struct {
|
| 24 |
+
lock sync.Mutex
|
| 25 |
+
base net.Listener
|
| 26 |
+
acceptErr error
|
| 27 |
+
|
| 28 |
+
acceptChan chan net.Conn
|
| 29 |
+
closeChan chan struct{}
|
| 30 |
+
|
| 31 |
+
socksListener *subListener
|
| 32 |
+
httpListener *subListener
|
| 33 |
+
|
| 34 |
+
deleteFunc func()
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
func (l *muxListener) acceptLoop() {
|
| 38 |
+
defer close(l.acceptChan)
|
| 39 |
+
|
| 40 |
+
for {
|
| 41 |
+
conn, err := l.base.Accept()
|
| 42 |
+
if err != nil {
|
| 43 |
+
l.lock.Lock()
|
| 44 |
+
l.acceptErr = err
|
| 45 |
+
l.lock.Unlock()
|
| 46 |
+
return
|
| 47 |
+
}
|
| 48 |
+
select {
|
| 49 |
+
case <-l.closeChan:
|
| 50 |
+
return
|
| 51 |
+
case l.acceptChan <- conn:
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
func (l *muxListener) mainLoop() {
|
| 57 |
+
defer func() {
|
| 58 |
+
l.deleteFunc()
|
| 59 |
+
l.base.Close()
|
| 60 |
+
|
| 61 |
+
close(l.closeChan)
|
| 62 |
+
|
| 63 |
+
l.lock.Lock()
|
| 64 |
+
defer l.lock.Unlock()
|
| 65 |
+
|
| 66 |
+
if sl := l.httpListener; sl != nil {
|
| 67 |
+
close(sl.acceptChan)
|
| 68 |
+
l.httpListener = nil
|
| 69 |
+
}
|
| 70 |
+
if sl := l.socksListener; sl != nil {
|
| 71 |
+
close(sl.acceptChan)
|
| 72 |
+
l.socksListener = nil
|
| 73 |
+
}
|
| 74 |
+
}()
|
| 75 |
+
|
| 76 |
+
for {
|
| 77 |
+
var socksCloseChan, httpCloseChan chan struct{}
|
| 78 |
+
if l.httpListener != nil {
|
| 79 |
+
httpCloseChan = l.httpListener.closeChan
|
| 80 |
+
}
|
| 81 |
+
if l.socksListener != nil {
|
| 82 |
+
socksCloseChan = l.socksListener.closeChan
|
| 83 |
+
}
|
| 84 |
+
select {
|
| 85 |
+
case <-l.closeChan:
|
| 86 |
+
return
|
| 87 |
+
case conn, ok := <-l.acceptChan:
|
| 88 |
+
if !ok {
|
| 89 |
+
return
|
| 90 |
+
}
|
| 91 |
+
go l.dispatch(conn)
|
| 92 |
+
case <-socksCloseChan:
|
| 93 |
+
l.lock.Lock()
|
| 94 |
+
if socksCloseChan == l.socksListener.closeChan {
|
| 95 |
+
// not replaced by another ListenSOCKS()
|
| 96 |
+
l.socksListener = nil
|
| 97 |
+
}
|
| 98 |
+
l.lock.Unlock()
|
| 99 |
+
if l.checkIdle() {
|
| 100 |
+
return
|
| 101 |
+
}
|
| 102 |
+
case <-httpCloseChan:
|
| 103 |
+
l.lock.Lock()
|
| 104 |
+
if httpCloseChan == l.httpListener.closeChan {
|
| 105 |
+
// not replaced by another ListenHTTP()
|
| 106 |
+
l.httpListener = nil
|
| 107 |
+
}
|
| 108 |
+
l.lock.Unlock()
|
| 109 |
+
if l.checkIdle() {
|
| 110 |
+
return
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
func (l *muxListener) dispatch(conn net.Conn) {
|
| 117 |
+
var b [1]byte
|
| 118 |
+
if _, err := io.ReadFull(conn, b[:]); err != nil {
|
| 119 |
+
conn.Close()
|
| 120 |
+
return
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
l.lock.Lock()
|
| 124 |
+
var target *subListener
|
| 125 |
+
if b[0] == 5 {
|
| 126 |
+
target = l.socksListener
|
| 127 |
+
} else {
|
| 128 |
+
target = l.httpListener
|
| 129 |
+
}
|
| 130 |
+
l.lock.Unlock()
|
| 131 |
+
|
| 132 |
+
if target == nil {
|
| 133 |
+
conn.Close()
|
| 134 |
+
return
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
wconn := &connWithOneByte{Conn: conn, b: b[0]}
|
| 138 |
+
|
| 139 |
+
select {
|
| 140 |
+
case <-target.closeChan:
|
| 141 |
+
case target.acceptChan <- wconn:
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
func (l *muxListener) checkIdle() bool {
|
| 146 |
+
l.lock.Lock()
|
| 147 |
+
defer l.lock.Unlock()
|
| 148 |
+
|
| 149 |
+
return l.httpListener == nil && l.socksListener == nil
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
func (l *muxListener) getAndClearAcceptError() error {
|
| 153 |
+
l.lock.Lock()
|
| 154 |
+
defer l.lock.Unlock()
|
| 155 |
+
|
| 156 |
+
if l.acceptErr == nil {
|
| 157 |
+
return nil
|
| 158 |
+
}
|
| 159 |
+
err := l.acceptErr
|
| 160 |
+
l.acceptErr = nil
|
| 161 |
+
return err
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
func (l *muxListener) ListenHTTP() (net.Listener, error) {
|
| 165 |
+
l.lock.Lock()
|
| 166 |
+
defer l.lock.Unlock()
|
| 167 |
+
|
| 168 |
+
if l.httpListener != nil {
|
| 169 |
+
subListenerPendingClosed := false
|
| 170 |
+
select {
|
| 171 |
+
case <-l.httpListener.closeChan:
|
| 172 |
+
subListenerPendingClosed = true
|
| 173 |
+
default:
|
| 174 |
+
}
|
| 175 |
+
if !subListenerPendingClosed {
|
| 176 |
+
return nil, OpErr{
|
| 177 |
+
Addr: l.base.Addr(),
|
| 178 |
+
Protocol: "http",
|
| 179 |
+
Op: "bind-protocol",
|
| 180 |
+
Err: ErrProtocolInUse,
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
l.httpListener = nil
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
select {
|
| 187 |
+
case <-l.closeChan:
|
| 188 |
+
return nil, net.ErrClosed
|
| 189 |
+
default:
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
sl := newSubListener(l.getAndClearAcceptError, l.base.Addr)
|
| 193 |
+
l.httpListener = sl
|
| 194 |
+
return sl, nil
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
func (l *muxListener) ListenSOCKS() (net.Listener, error) {
|
| 198 |
+
l.lock.Lock()
|
| 199 |
+
defer l.lock.Unlock()
|
| 200 |
+
|
| 201 |
+
if l.socksListener != nil {
|
| 202 |
+
subListenerPendingClosed := false
|
| 203 |
+
select {
|
| 204 |
+
case <-l.socksListener.closeChan:
|
| 205 |
+
subListenerPendingClosed = true
|
| 206 |
+
default:
|
| 207 |
+
}
|
| 208 |
+
if !subListenerPendingClosed {
|
| 209 |
+
return nil, OpErr{
|
| 210 |
+
Addr: l.base.Addr(),
|
| 211 |
+
Protocol: "socks",
|
| 212 |
+
Op: "bind-protocol",
|
| 213 |
+
Err: ErrProtocolInUse,
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
l.socksListener = nil
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
select {
|
| 220 |
+
case <-l.closeChan:
|
| 221 |
+
return nil, net.ErrClosed
|
| 222 |
+
default:
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
sl := newSubListener(l.getAndClearAcceptError, l.base.Addr)
|
| 226 |
+
l.socksListener = sl
|
| 227 |
+
return sl, nil
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
func newSubListener(acceptErrorFunc func() error, addrFunc func() net.Addr) *subListener {
|
| 231 |
+
return &subListener{
|
| 232 |
+
acceptChan: make(chan net.Conn),
|
| 233 |
+
acceptErrorFunc: acceptErrorFunc,
|
| 234 |
+
closeChan: make(chan struct{}),
|
| 235 |
+
addrFunc: addrFunc,
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
type subListener struct {
|
| 240 |
+
// receive connections or closure from upstream
|
| 241 |
+
acceptChan chan net.Conn
|
| 242 |
+
// get an error of Accept() from upstream
|
| 243 |
+
acceptErrorFunc func() error
|
| 244 |
+
// notify upstream that we are closed
|
| 245 |
+
closeChan chan struct{}
|
| 246 |
+
|
| 247 |
+
// Listener.Addr() implementation of base listener
|
| 248 |
+
addrFunc func() net.Addr
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
func (l *subListener) Accept() (net.Conn, error) {
|
| 252 |
+
select {
|
| 253 |
+
case <-l.closeChan:
|
| 254 |
+
// closed by ourselves
|
| 255 |
+
return nil, net.ErrClosed
|
| 256 |
+
case conn, ok := <-l.acceptChan:
|
| 257 |
+
if !ok {
|
| 258 |
+
// closed by upstream
|
| 259 |
+
if acceptErr := l.acceptErrorFunc(); acceptErr != nil {
|
| 260 |
+
return nil, acceptErr
|
| 261 |
+
}
|
| 262 |
+
return nil, net.ErrClosed
|
| 263 |
+
}
|
| 264 |
+
return conn, nil
|
| 265 |
+
}
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
func (l *subListener) Addr() net.Addr {
|
| 269 |
+
return l.addrFunc()
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// Close implements net.Listener.Close.
|
| 273 |
+
// Upstream should use close(l.acceptChan) instead.
|
| 274 |
+
func (l *subListener) Close() error {
|
| 275 |
+
select {
|
| 276 |
+
case <-l.closeChan:
|
| 277 |
+
return nil
|
| 278 |
+
default:
|
| 279 |
+
}
|
| 280 |
+
close(l.closeChan)
|
| 281 |
+
return nil
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// connWithOneByte is a net.Conn that returns b for the first read
|
| 285 |
+
// request, then forwards everything else to Conn.
|
| 286 |
+
type connWithOneByte struct {
|
| 287 |
+
net.Conn
|
| 288 |
+
|
| 289 |
+
b byte
|
| 290 |
+
bRead bool
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
func (c *connWithOneByte) Read(bs []byte) (int, error) {
|
| 294 |
+
if c.bRead {
|
| 295 |
+
return c.Conn.Read(bs)
|
| 296 |
+
}
|
| 297 |
+
if len(bs) == 0 {
|
| 298 |
+
return 0, nil
|
| 299 |
+
}
|
| 300 |
+
c.bRead = true
|
| 301 |
+
bs[0] = c.b
|
| 302 |
+
return 1, nil
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
type OpErr struct {
|
| 306 |
+
Addr net.Addr
|
| 307 |
+
Protocol string
|
| 308 |
+
Op string
|
| 309 |
+
Err error
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
func (m OpErr) Error() string {
|
| 313 |
+
return fmt.Sprintf("mux-listen: %s[%s]: %s: %v", m.Addr, m.Protocol, m.Op, m.Err)
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
func (m OpErr) Unwrap() error {
|
| 317 |
+
return m.Err
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
var ErrProtocolInUse = errors.New("protocol already in use")
|
app/internal/proxymux/mux_test.go
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package proxymux
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"bytes"
|
| 5 |
+
"net"
|
| 6 |
+
"sync"
|
| 7 |
+
"testing"
|
| 8 |
+
"time"
|
| 9 |
+
|
| 10 |
+
"github.com/apernet/hysteria/app/v2/internal/proxymux/internal/mocks"
|
| 11 |
+
|
| 12 |
+
"github.com/stretchr/testify/assert"
|
| 13 |
+
"github.com/stretchr/testify/mock"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
//go:generate mockery
|
| 17 |
+
|
| 18 |
+
func testMockListener(t *testing.T, connChan <-chan net.Conn) net.Listener {
|
| 19 |
+
closedChan := make(chan struct{})
|
| 20 |
+
mockListener := mocks.NewMockListener(t)
|
| 21 |
+
mockListener.EXPECT().Accept().RunAndReturn(func() (net.Conn, error) {
|
| 22 |
+
select {
|
| 23 |
+
case <-closedChan:
|
| 24 |
+
return nil, net.ErrClosed
|
| 25 |
+
case conn, ok := <-connChan:
|
| 26 |
+
if !ok {
|
| 27 |
+
panic("unexpected closed channel (connChan)")
|
| 28 |
+
}
|
| 29 |
+
return conn, nil
|
| 30 |
+
}
|
| 31 |
+
})
|
| 32 |
+
mockListener.EXPECT().Close().RunAndReturn(func() error {
|
| 33 |
+
select {
|
| 34 |
+
case <-closedChan:
|
| 35 |
+
default:
|
| 36 |
+
close(closedChan)
|
| 37 |
+
}
|
| 38 |
+
return nil
|
| 39 |
+
})
|
| 40 |
+
return mockListener
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
func testMockConn(t *testing.T, b []byte) net.Conn {
|
| 44 |
+
buf := bytes.NewReader(b)
|
| 45 |
+
isClosed := false
|
| 46 |
+
mockConn := mocks.NewMockConn(t)
|
| 47 |
+
mockConn.EXPECT().Read(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
|
| 48 |
+
if isClosed {
|
| 49 |
+
return 0, net.ErrClosed
|
| 50 |
+
}
|
| 51 |
+
return buf.Read(b)
|
| 52 |
+
})
|
| 53 |
+
mockConn.EXPECT().Close().RunAndReturn(func() error {
|
| 54 |
+
isClosed = true
|
| 55 |
+
return nil
|
| 56 |
+
})
|
| 57 |
+
return mockConn
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
func TestMuxHTTP(t *testing.T) {
|
| 61 |
+
connChan := make(chan net.Conn)
|
| 62 |
+
mockListener := testMockListener(t, connChan)
|
| 63 |
+
mockConn := testMockConn(t, []byte("CONNECT example.com:443 HTTP/1.1\r\n\r\n"))
|
| 64 |
+
|
| 65 |
+
mux := newMuxListener(mockListener, func() {})
|
| 66 |
+
hl, err := mux.ListenHTTP()
|
| 67 |
+
if !assert.NoError(t, err) {
|
| 68 |
+
return
|
| 69 |
+
}
|
| 70 |
+
sl, err := mux.ListenSOCKS()
|
| 71 |
+
if !assert.NoError(t, err) {
|
| 72 |
+
return
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
connChan <- mockConn
|
| 76 |
+
|
| 77 |
+
var socksConn, httpConn net.Conn
|
| 78 |
+
var socksErr, httpErr error
|
| 79 |
+
|
| 80 |
+
var wg sync.WaitGroup
|
| 81 |
+
wg.Add(2)
|
| 82 |
+
go func() {
|
| 83 |
+
socksConn, socksErr = sl.Accept()
|
| 84 |
+
wg.Done()
|
| 85 |
+
}()
|
| 86 |
+
go func() {
|
| 87 |
+
httpConn, httpErr = hl.Accept()
|
| 88 |
+
wg.Done()
|
| 89 |
+
}()
|
| 90 |
+
|
| 91 |
+
time.Sleep(time.Second)
|
| 92 |
+
|
| 93 |
+
sl.Close()
|
| 94 |
+
hl.Close()
|
| 95 |
+
|
| 96 |
+
wg.Wait()
|
| 97 |
+
|
| 98 |
+
assert.Nil(t, socksConn)
|
| 99 |
+
assert.ErrorIs(t, socksErr, net.ErrClosed)
|
| 100 |
+
assert.NotNil(t, httpConn)
|
| 101 |
+
httpConn.Close()
|
| 102 |
+
assert.NoError(t, httpErr)
|
| 103 |
+
|
| 104 |
+
// Wait for muxListener released
|
| 105 |
+
<-mux.acceptChan
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
func TestMuxSOCKS(t *testing.T) {
|
| 109 |
+
connChan := make(chan net.Conn)
|
| 110 |
+
mockListener := testMockListener(t, connChan)
|
| 111 |
+
mockConn := testMockConn(t, []byte{0x05, 0x02, 0x00, 0x01}) // SOCKS5 Connect Request: NOAUTH+GSSAPI
|
| 112 |
+
|
| 113 |
+
mux := newMuxListener(mockListener, func() {})
|
| 114 |
+
hl, err := mux.ListenHTTP()
|
| 115 |
+
if !assert.NoError(t, err) {
|
| 116 |
+
return
|
| 117 |
+
}
|
| 118 |
+
sl, err := mux.ListenSOCKS()
|
| 119 |
+
if !assert.NoError(t, err) {
|
| 120 |
+
return
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
connChan <- mockConn
|
| 124 |
+
|
| 125 |
+
var socksConn, httpConn net.Conn
|
| 126 |
+
var socksErr, httpErr error
|
| 127 |
+
|
| 128 |
+
var wg sync.WaitGroup
|
| 129 |
+
wg.Add(2)
|
| 130 |
+
go func() {
|
| 131 |
+
socksConn, socksErr = sl.Accept()
|
| 132 |
+
wg.Done()
|
| 133 |
+
}()
|
| 134 |
+
go func() {
|
| 135 |
+
httpConn, httpErr = hl.Accept()
|
| 136 |
+
wg.Done()
|
| 137 |
+
}()
|
| 138 |
+
|
| 139 |
+
time.Sleep(time.Second)
|
| 140 |
+
|
| 141 |
+
sl.Close()
|
| 142 |
+
hl.Close()
|
| 143 |
+
|
| 144 |
+
wg.Wait()
|
| 145 |
+
|
| 146 |
+
assert.NotNil(t, socksConn)
|
| 147 |
+
socksConn.Close()
|
| 148 |
+
assert.NoError(t, socksErr)
|
| 149 |
+
assert.Nil(t, httpConn)
|
| 150 |
+
assert.ErrorIs(t, httpErr, net.ErrClosed)
|
| 151 |
+
|
| 152 |
+
// Wait for muxListener released
|
| 153 |
+
<-mux.acceptChan
|
| 154 |
+
}
|
app/internal/redirect/getsockopt_linux.go
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//go:build !386
|
| 2 |
+
// +build !386
|
| 3 |
+
|
| 4 |
+
package redirect
|
| 5 |
+
|
| 6 |
+
import (
|
| 7 |
+
"syscall"
|
| 8 |
+
"unsafe"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
|
| 12 |
+
_, _, e := syscall.Syscall6(syscall.SYS_GETSOCKOPT, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
|
| 13 |
+
if e != 0 {
|
| 14 |
+
err = e
|
| 15 |
+
}
|
| 16 |
+
return
|
| 17 |
+
}
|
app/internal/redirect/getsockopt_linux_386.go
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package redirect
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"syscall"
|
| 5 |
+
"unsafe"
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
const (
|
| 9 |
+
sysGetsockopt = 15
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
// On 386 we cannot call socketcall with syscall.Syscall6, as it always fails with EFAULT.
|
| 13 |
+
// Use our own syscall.socketcall hack instead.
|
| 14 |
+
|
| 15 |
+
func syscall_socketcall(call int, a0, a1, a2, a3, a4, a5 uintptr) (n int, err syscall.Errno)
|
| 16 |
+
|
| 17 |
+
func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
|
| 18 |
+
_, e := syscall_socketcall(sysGetsockopt, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
|
| 19 |
+
if e != 0 {
|
| 20 |
+
err = e
|
| 21 |
+
}
|
| 22 |
+
return
|
| 23 |
+
}
|