Compare commits

..

No commits in common. "master" and "app/v1.3.1" have entirely different histories.

341 changed files with 11111 additions and 64354 deletions

2
.github/FUNDING.yml vendored
View file

@ -1 +1 @@
custom: [ 'https://v2.hysteria.network/docs/Donation/' ] custom: ['https://hysteria.network/docs/donations/']

View file

@ -1,26 +0,0 @@
---
name: Bug report
about: Report anything you think is a bug and needs to be fixed.
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
Attach logs from the client/server when the error occurs.
**Device and Operating System**
What are you using it on.
**Additional context**
Add any other context about the problem here.

View file

@ -1,26 +0,0 @@
---
name: Bug 反馈
about: 反馈任何你认为是 bug 需要修复的问题。
title: ''
labels: bug
assignees: ''
---
**描述问题**
请尽量清晰精准地描述你遇到的问题。
**如何复现**
复现问题的步骤。
**预期行为**
你认为修复后的行为应该是怎样的。
**日志**
附上客户端/服务器端在错误发生前后的日志。
**设备和操作系统**
你在用什么设备和操作系统。
**额外信息**
其他你认为有助于解决问题的信息。

View file

@ -0,0 +1,42 @@
name: "[en] Feature Request"
description: "Request to add a new feature, or improvement to an existing feature."
title: "[Feature Request] "
body:
- type: markdown
id: header
attributes:
value: |
Before creating an issue, please take a look at [Advanced Usage](https://hysteria.network/docs/advanced-usage/) & existing issues to make sure it does not exist or has already been proposed.
You can also join our Telegram group or use Discussion to share your ideas with the community.
If you have the skills to implement the features you want, Pull Requests are more than welcomed :)
- type: textarea
id: detail
attributes:
label: "Details"
description: |
Describe what you want to add or change.
validations:
required: true
- type: textarea
id: necessary
attributes:
label: "Value"
description: |
What is the value added?
validations:
required: true
- type: textarea
id: alternative
attributes:
label: "Available alternatives"
description: |
Are there other projects that have implemented this feature that we can refer to?
- type: textarea
id: other-info
attributes:
label: "Additional information"
description: |
Links to any relevant issues, pull requests, or discussions.

136
.github/ISSUE_TEMPLATE/en_newbie.yml vendored Normal file
View file

@ -0,0 +1,136 @@
name: "[en] Help me!"
description: "Unable to connect? Server/client crashed? Choose this to get help."
title: "[Help me] "
body:
- type: markdown
id: header
attributes:
value: |
Before creating an issue, please take a look at [Quick Start Guide](https://hysteria.network/docs/quick-start/) and [Advanced Usage](https://hysteria.network/docs/advanced-usage/).
You can find solutions to common problems in [Common Problems](https://hysteria.network/docs/common-problems/). Anything already covered there will be closed without reply.
You can [join our Telegram group](https://t.me/hysteria_github) our use Discussion for community support.
Try searching existing issues to see if it has been already answered.
If your problem still can't be solved, fill out the form as detailed as you can to help us reproduce it.
- type: textarea
id: detail
attributes:
label: "Details"
description: |
Describe the problem you encountered in detail.
If you are using hysteria in an unusual way, describe your setup and what you are trying to achieve.
validations:
required: true
- type: input
id: server-install-info
attributes:
label: "Hysteria server information"
description: |
Paste the version of hysteria server here (output of `hysteria --version`).
If you used a script to install and config hysteria on your server, please paste the command that you executed here (such as `curl https://xxx | sh -`)
If you are using a VPN provider, please ask the VPN provider for help first.
placeholder: |
hysteria version v1.x.x 2006-01-02t08:04:05z 0123456789abcdef0123456789abcdef01234567
validations:
required: true
- type: textarea
id: server-provider-info
attributes:
label: "VPS information"
description: |
Fill in the provider and specs of the VPS you are using to run the hysteria server here.
If you are using a VPN provider, please fill in the website of the VPN provider.
placeholder: |
TurtleShell, Chuncheon, ARM, 1 Core, 512MB RAM
validations:
required: true
- type: textarea
id: server-config-info
attributes:
label: "Server config"
description: |
Paste the server config.json you are using here.
If you are using a script that doesn't require any configuration, please specify `N/A`.
placeholder: |
{
"listen": ":36712",
"acme": {
"domains": [
"your.domain.com"
],
"email": "hacker@gmail.com"
},
"obfs": "fuck me till the daylight",
"up_mbps": 100,
"down_mbps": 100
}
validations:
required: true
- type: textarea
id: server-log
attributes:
label: "Server logs"
description: |
Paste the hysteria server output here. Screenshots are acceptable but plaintext would be much better.
validations:
required: true
- type: input
id: client-install-info
attributes:
label: "Hysteria client information"
description: |
Paste the version of hysteria client here (output of `hysteria --version`).
If you are using any third-party clients (e.g. Clash, Passwall, or SagerNet), paste their version instead. You can also find help in their communities.
placeholder: |
hysteria version v1.x.x 2006-01-02T08:04:05Z 0123456789abcdef0123456789abcdef01234567
validations:
required: true
- type: textarea
id: client-config-info
attributes:
label: "Client config"
description: |
Paste the client config.json you are using here.
Make sure to remove sensitive information (e.g. server address, password).
If you are using a third-party client, you can paste or upload a screenshot of their configuration instead.
placeholder: |
{
"server": "example.com:36712",
"obfs": "fuck me till the daylight",
"up_mbps": 10,
"down_mbps": 50,
"socks5": {
"listen": "127.0.0.1:1080"
},
"http": {
"listen": "127.0.0.1:8080"
}
}
validations:
required: true
- type: input
id: client-environment
attributes:
label: "Client environment (operating system)"
description: |
The OS you are using to run hysteria client.
If you are running hysteria client on OpenWRT, provide the version of OpenWRT (and any plugins you are using, e.g. Passwall).
placeholder: |
Windows 11
validations:
required: true
- type: textarea
id: client-log
attributes:
label: "Client logs"
description: |
Paste the hysteria client output here. Screenshots are acceptable but plaintext would be much better.
validations:
required: true

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project.
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,20 +0,0 @@
---
name: 功能请求
about: 为这个项目提出改进意见。
title: ''
labels: enhancement
assignees: ''
---
**你的功能请求是否与某个问题有关?**
请尽量清晰精准地描述你遇到的问题。例如:我家运营商限制 UDP 协议速度,导致 Hysteria 很慢,希望增加 FakeTCP 支持。
**描述你希望的解决方案**
请尽量清晰精准地描述你希望的解决方案。
**有没有其他替代方案**
请尽量清晰精准地描述你认为可能的替代方案。
**额外信息**
其他你认为有助于开发者了解你需求的信息。

View file

@ -0,0 +1,43 @@
name: "[zh] 功能请求"
description: "希望 Hysteria 添加新功能?或者希望 Hysteria 作出什么改变? 请选这个。"
title: "[功能请求] "
body:
- type: markdown
id: header
attributes:
value: |
在创建 Issue 之前, 请花几分钟阅读一下我们 Wiki 上的 [高级用法](https://hysteria.network/zh/docs/advanced-usage/)。 确认你想要的功能是否已经被实现。
如果你有什么好的想法, 欢迎 [加入 Hysteria 的 Telegram 群组](https://t.me/hysteria_github) 参与功能上的讨论。
也请搜索一下已有 Issue 检查一下你所需的功能有没有人曾经提出过。
如果你有能力实现这个功能, 欢迎为 Hysteria 提交 Pull Request。
- type: textarea
id: detail
attributes:
label: "功能描述"
description: |
请描述你希望 Hysteria 增加的功能或者希望 Hysteria 能作出的变更。
validations:
required: true
- type: textarea
id: necessary
attributes:
label: "这个功能的必要性"
description: |
为什么这个功能对 Hysteria 来说是必须的? 或者为什么你认为这个功能需要内置在 Hysteria 中?
validations:
required: true
- type: textarea
id: alternative
attributes:
label: "当前可用的替代方案"
description: |
在当前没有这个功能的前提下, 你使用什么方案来达到类似的效果?
- type: textarea
id: other-info
attributes:
label: "补充"
description: |
如果有任何涉及到这个功能请求的 Issue、 Pull Request、 博客文章等, 请把链接贴在下面。

137
.github/ISSUE_TEMPLATE/zh_newbie.yml vendored Normal file
View file

@ -0,0 +1,137 @@
name: "[zh] 请求帮助"
description: "不会用?连不上?请选这个。"
title: "[请求帮助] "
body:
- type: markdown
id: header
attributes:
value: |
在创建 Issue 之前, 请花几分钟阅读一下我们 Wiki 上的 [配置指南](https://hysteria.network/zh/docs/quick-start/)
最新的配置参数在 [高级用法](https://hysteria.network/zh/docs/advanced-usage/) 里有详细的说明。
您可能遇到的绝大部分问题都能在 [常见问题](https://hysteria.network/zh/docs/common-problems/) 中找到解决方案。
任何已有解决方案的 Issue 将会被直接关闭, 感谢理解。
请考虑 [加入 Hysteria 的 Telegram 群组](https://t.me/hysteria_github) 来寻求即时的社区帮助。
也请搜索一下已有 Issue 看看是否能找到现成的解决方案。
请尽可能详细地填写下面这个表单来帮助我们检查并复现您遇到的问题, 请记住我们不会预测魔法, 只有复现了您遇到的问题, 我们才知道该如何帮助你解决它。
- type: textarea
id: detail
attributes:
label: "问题详情"
description: |
请描述你遇到的问题。
如果你的需求和通常的用法有所不同, 也请在这里说明。
validations:
required: true
- type: input
id: server-install-info
attributes:
label: "服务端安装信息或者一键脚本信息"
description: |
请填写你使用的服务端版本(在 VPS 上执行 `hysteria --version` 把输出贴在这里)。
如果你使用一键脚本, 请把一键脚本让你复制和执行的命令贴在这里。
如果你使用机场, 请优先联系机场售后以获取使用帮助。
placeholder: |
hysteria version v1.x.x 2006-01-02t08:04:05z 0123456789abcdef0123456789abcdef01234567
validations:
required: true
- type: textarea
id: server-provider-info
attributes:
label: "VPS 信息"
description: |
请填写你搭建服务端所使用的 VPS 服务商以及 VPS 配置。
如果你使用机场, 请填写机场网址。
placeholder: |
TurtleShell 春川机房 ARM 单核 512MB内存
validations:
required: true
- type: textarea
id: server-config-info
attributes:
label: "服务端配置"
description: |
请把你的服务端配置 JSON 粘贴在这里。
如果你使用的是一键脚本并且不需要任何配置, 请填写「无」。
placeholder: |
{
"listen": ":36712",
"acme": {
"domains": [
"your.domain.com"
],
"email": "hacker@gmail.com"
},
"obfs": "fuck me till the daylight",
"up_mbps": 100,
"down_mbps": 100
}
validations:
required: true
- type: textarea
id: server-log
attributes:
label: "服务端日志"
description: |
请把你的服务端日志贴在这里, 可以是截图但是请尽可能提供纯文本。
validations:
required: true
- type: input
id: client-install-info
attributes:
label: "客户端安装信息"
description: |
请填写你使用的客户端版本(在客户端执行 `hysteria --version` 并把输出贴在这里)。
如果你使用第三方客户端(包括但不限于 Clash、 Passwall、 SagerNet 请贴上它们的版本, 或者到这些第三方客户端的社群寻求帮助。
placeholder: |
hysteria version v1.x.x 2006-01-02T08:04:05Z 0123456789abcdef0123456789abcdef01234567
validations:
required: true
- type: textarea
id: client-config-info
attributes:
label: "客户端配置"
description: |
请把你的客户端配置 JSON 粘贴在这里。
你可以移除客户端配置里的敏感信息(像服务器地址、 混淆密码、 认证密码), 但是这也意味着你必须自己检查这些配置是否填写正确。
如果你使用第三方客户端, 你可以贴上第三方客户端的配置或者配置截图。
placeholder: |
{
"server": "example.com:36712",
"obfs": "fuck me till the daylight",
"up_mbps": 10,
"down_mbps": 50,
"socks5": {
"listen": "127.0.0.1:1080"
},
"http": {
"listen": "127.0.0.1:8080"
}
}
validations:
required: true
- type: input
id: client-environment
attributes:
label: "客户端运行环境(操作系统)"
description: |
请填写客户端使用的操作系统的名称和版本。
如果你在 OpenWRT 上运行 hysteria 客户端, 请填写 OpenWRT (以及你使用的插件, 如 Passwall的版本。
placeholder: |
Windows 11
validations:
required: true
- type: textarea
id: client-log
attributes:
label: "客户端日志"
description: |
请把你的客户端日志贴在这里, 可以是截图但是请尽可能提供纯文本。
validations:
required: true

View file

@ -1,104 +0,0 @@
name: "Create release tags for nested modules"
on:
push:
tags:
- app/v*.*.*
permissions:
contents: write
jobs:
tag:
name: "Create tags"
runs-on: ubuntu-latest
steps:
- name: "Extract tagbase"
id: extract_tagbase
uses: actions/github-script@v7
with:
script: |
const ref = context.ref;
core.info(`context.ref: ${ref}`);
const refPrefix = 'refs/tags/app/';
if (!ref.startsWith(refPrefix)) {
core.setFailed(`context.ref does not start with ${refPrefix}: ${ref}`);
return;
}
const tagbase = ref.slice(refPrefix.length);
core.info(`tagbase: ${tagbase}`);
core.setOutput('tagbase', tagbase);
- name: "Tagging core/*"
uses: actions/github-script@v7
env:
INPUT_TAGPREFIX: "core/"
INPUT_TAGBASE: ${{ steps.extract_tagbase.outputs.tagbase }}
with:
script: |
const tagbase = core.getInput('tagbase', { required: true });
const tagprefix = core.getInput('tagprefix', { required: true });
const refname = `tags/${tagprefix}${tagbase}`;
core.info(`creating ref ${refname}`);
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/${refname}`,
sha: context.sha
});
core.info(`created ref ${refname}`);
return;
} catch (error) {
core.info(`failed to create ref ${refname}: ${error}`);
}
core.info(`updating ref ${refname}`)
try {
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: refname,
sha: context.sha
});
core.info(`updated ref ${refname}`);
return;
} catch (error) {
core.setFailed(`failed to update ref ${refname}: ${error}`);
}
- name: "Tagging extras/*"
uses: actions/github-script@v7
env:
INPUT_TAGPREFIX: "extras/"
INPUT_TAGBASE: ${{ steps.extract_tagbase.outputs.tagbase }}
with:
script: |
const tagbase = core.getInput('tagbase', { required: true });
const tagprefix = core.getInput('tagprefix', { required: true });
const refname = `tags/${tagprefix}${tagbase}`;
core.info(`creating ref ${refname}`);
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/${refname}`,
sha: context.sha
});
core.info(`created ref ${refname}`);
return;
} catch (error) {
core.info(`failed to create ref ${refname}: ${error}`);
}
core.info(`updating ref ${refname}`)
try {
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: refname,
sha: context.sha
});
core.info(`updated ref ${refname}`);
return;
} catch (error) {
core.setFailed(`failed to update ref ${refname}: ${error}`);
}

71
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '17 14 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

38
.github/workflows/dev-build-master.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: "Build master"
on:
push:
branches:
- 'master'
tags-ignore:
- 'v*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- name: Check out
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Run build script
env:
HY_APP_PLATFORMS: 'darwin/amd64,darwin/arm64,windows/amd64,windows/386,linux/amd64,linux/386,linux/arm,linux/arm64,linux/s390x,linux/mipsle,freebsd/amd64,freebsd/386,freebsd/arm,freebsd/arm64'
run: ./build.sh
shell: bash
- name: Archive
uses: actions/upload-artifact@v3
with:
name: hysteria-binaries-${{ github.sha }}
path: ./build

View file

@ -1,52 +0,0 @@
name: "Build master branch"
on:
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- name: Check out
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
- name: Setup Python # This is for the build script
uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26b
add-to-path: false
- name: Run build script
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
export HY_APP_PLATFORMS=$(sed 's/\r$//' platforms.txt | awk '!/^#/ && !/^$/' | paste -sd ",")
python hyperbole.py build -r
- name: Generate hashes
run: |
for file in build/*; do
sha256sum $file >> build/hashes.txt
done
- name: Archive
uses: actions/upload-artifact@v4
with:
name: hysteria-master-${{ github.sha }}
path: build

View file

@ -1,9 +1,9 @@
name: "Build Docker Image" name: Build Docker Image
on: on:
push: push:
tags: tags:
- app/v*.*.* - 'v*'
jobs: jobs:
docker: docker:
@ -13,32 +13,32 @@ jobs:
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Get version - name: Get tag
id: get_version id: get_tag
run: echo "version=$(git describe --tags --always --match 'app/v*' | sed -n 's|app/\([^/-]*\)\(-.*\)\{0,1\}|\1|p')" >> $GITHUB_OUTPUT run: echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v2.1.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push - name: Build and push
id: docker_build id: docker_build
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3.2.0
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:v2,tobyxdd/hysteria:${{ steps.get_version.outputs.version }} tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:${{ steps.get_tag.outputs.tag }}
- name: Image digest - name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}

View file

@ -1,71 +1,58 @@
name: "Build release" name: Build and release
on: on:
push: push:
tags: tags:
- app/v*.*.* - 'v*'
jobs: jobs:
build: build:
name: Build name: Build and release
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps: steps:
- name: Check out
uses: actions/checkout@v4
- name: Get version - name: Check out
id: get_version uses: actions/checkout@v3
run: echo "version=$(git describe --tags --always --match 'app/v*' | sed -n 's|app/\([^/-]*\)\(-.*\)\{0,1\}|\1|p')" >> $GITHUB_OUTPUT
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: "1.24" go-version: 1.19
- name: Setup Python # This is for the build script
uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26b
add-to-path: false
- name: Run build script - name: Run build script
env: env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} HY_APP_PLATFORMS: 'darwin/amd64,darwin/arm64,windows/amd64,windows/386,linux/amd64,linux/386,linux/arm,linux/arm64,linux/s390x,linux/mipsle,freebsd/amd64,freebsd/386,freebsd/arm,freebsd/arm64'
run: | run: ./build.sh
export HY_APP_PLATFORMS=$(sed 's/\r$//' platforms.txt | awk '!/^#/ && !/^$/' | paste -sd ",") shell: bash
python hyperbole.py build -r
- name: Generate hashes - name: Generate hashes
run: | run: |
for file in build/*; do cd build
sha256sum $file >> build/hashes.txt for f in $(find . -type f); do
sha256sum $f | sudo tee -a hashes.txt
done done
- name: Upload GitHub - name: Upload
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with: with:
files: build/* files: |
./build/hysteria-darwin-amd64
- name: Upload CF bucket ./build/hysteria-darwin-arm64
uses: shallwefootball/upload-s3-action@v1.3.3 ./build/hysteria-windows-amd64.exe
with: ./build/hysteria-windows-386.exe
aws_key_id: ${{ secrets.CF_KEY_ID }} ./build/hysteria-linux-amd64
aws_secret_access_key: ${{ secrets.CF_KEY }} ./build/hysteria-linux-386
aws_bucket: "hydownload" ./build/hysteria-linux-arm
endpoint: "https://bea223c61d5a41250d127bd67f51dfec.r2.cloudflarestorage.com/" ./build/hysteria-linux-arm64
source_dir: "build" ./build/hysteria-linux-s390x
destination_dir: "app/${{ steps.get_version.outputs.version }}" ./build/hysteria-linux-mipsle
./build/hysteria-freebsd-amd64
- name: Publish to API ./build/hysteria-freebsd-386
run: | ./build/hysteria-freebsd-arm
export HY_API_POST_KEY=${{ secrets.HY2_API_POST_KEY }} ./build/hysteria-freebsd-arm64
pip install requests ./build/hashes.txt
python hyperbole.py publish

View file

@ -1,29 +0,0 @@
name: "Publish scripts"
on:
push:
branches:
- master
paths:
- scripts/**
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish scripts to Cloudflare Pages
steps:
- name: Check out
uses: actions/checkout@v4
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: hy2scripts
directory: scripts
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: main

338
.gitignore vendored
View file

@ -1,10 +1,7 @@
# Created by https://www.toptal.com/developers/gitignore/api/goland+all,intellij+all,go,windows,linux,macos,python,pycharm+all # Created by https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,intellij+all,go,windows,linux,macos,python,pycharm+all # Edit at https://www.gitignore.io/?templates=go,linux,macos,windows,intellij+all
### Go ### ### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
@ -21,11 +18,12 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
# Go workspace file ### Go Patch ###
go.work /vendor/
/Godeps/
### GoLand+all ### ### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
@ -35,9 +33,6 @@ go.work
.idea/**/dictionaries .idea/**/dictionaries
.idea/**/shelf .idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files # Generated files
.idea/**/contentModel.xml .idea/**/contentModel.xml
@ -58,9 +53,6 @@ go.work
# When using Gradle or Maven with auto-import, you should exclude module files, # When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using # since they will be recreated, and may cause churn. Uncomment if using
# auto-import. # auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml # .idea/modules.xml
# .idea/*.iml # .idea/*.iml
# .idea/modules # .idea/modules
@ -88,9 +80,6 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin # Cursive Clojure plugin
.idea/replstate.xml .idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ) # Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml com_crashlytics_export_strings.xml
crashlytics.properties crashlytics.properties
@ -103,69 +92,21 @@ fabric.properties
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
### GoLand+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
# AWS User-specific
# Generated files
# Sensitive or high-churn files
# Gradle
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
# Mongo Explorer plugin
# File-based project format
# IntelliJ
# mpeltonen/sbt-idea plugin
# JIRA plugin
# Cursive Clojure plugin
# SonarLint plugin
# Crashlytics plugin (for Android Studio and IntelliJ)
# Editor-based Rest Client
# Android studio 3.1+ serialized cache file
### Intellij+all Patch ### ### Intellij+all Patch ###
# Ignore everything but code style settings and run configurations # Ignores the whole .idea folder and all .iml files
# that are supposed to be shared within teams. # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Linux ### ### Linux ###
*~ *~
@ -191,7 +132,6 @@ fabric.properties
# Icon must end with two \r # Icon must end with two \r
Icon Icon
# Thumbnails # Thumbnails
._* ._*
@ -211,236 +151,6 @@ Network Trash Folder
Temporary Items Temporary Items
.apdisk .apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### PyCharm+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
# AWS User-specific
# Generated files
# Sensitive or high-churn files
# Gradle
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
# Mongo Explorer plugin
# File-based project format
# IntelliJ
# mpeltonen/sbt-idea plugin
# JIRA plugin
# Cursive Clojure plugin
# SonarLint plugin
# Crashlytics plugin (for Android Studio and IntelliJ)
# Editor-based Rest Client
# Android studio 3.1+ serialized cache file
### PyCharm+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### Windows ### ### Windows ###
# Windows thumbnail cache files # Windows thumbnail cache files
Thumbs.db Thumbs.db
@ -467,4 +177,12 @@ $RECYCLE.BIN/
# Windows shortcuts # Windows shortcuts
*.lnk *.lnk
# End of https://www.toptal.com/developers/gitignore/api/goland+all,intellij+all,go,windows,linux,macos,python,pycharm+all # End of https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
cmd/relay/*.json
hy_linux
.vscode
/build/
config*.json

View file

@ -1,3 +1,127 @@
# Changelog # Changelog
https://v2.hysteria.network/docs/Changelog/ ## 1.3.1
- New `fast_open` option for client to reduce RTT when dialing TCP connections
- Fix a bug where the HTTP proxy would not close connections properly
- Minor performance improvements here and there
## 1.3.0
- Connection migration: clients can now seamlessly switch between networks without losing their connection to the server
- Dynamic port hopping: see https://hysteria.network/docs/port-hopping/ for more information
## 1.2.2
- Fix a bug where the client would crash for IPv6 UDP requests in TProxy mode.
- Fix a bug where the client did not release old UDP sockets when reconnecting.
- Fix a bug where using DoT (DNS over TLS) as resolver would cause the client/server to crash.
- Add `quit_on_disconnect`, `handshake_timeout`, `idle_timeout` options to client config.
- Drop server's legacy protocol (v2) support.
- Updated quic-go to v0.30.0, small performance improvements.
## 1.2.1
- Fix a bug that caused DNS failure when using domain names in the "resolver" option
- Fix a bug where errors in HTTP proxy mode were not logged
- Fix a bug where WeChat protocol was not working properly when obfuscation was not enabled
- New TCP buffer options for tun mode (`tcp_sndbuf`, `tcp_rcvbuf`, `tcp_autotuning`)
## 1.2.0
- Reworked TUN mode
- DoT/DoH/DoQ support for resolver
- IP masking (anonymization)
- FreeBSD builds
## 1.1.0
- Super major CPU performance improvements (~30% to several times faster, depending on the circumstances) by optimizing several data structures in quic-go (changes upstreamed)
## 1.0.5
- `bind_outbound` server option for binding outbound connections to a specific address or interface
- TCP Redirect mode (for Linux)
## 1.0.4
- ~10% CPU usage reduction
- Improve performance when packet loss is high
- New ACL syntax to support protocol/port
## 1.0.3
- New string-based speed (up/down) options
- Server SOCKS5 outbound domain pass-through
- Linux s390x build
- Updated quic-go to v0.27.0
## 1.0.2
- Added an option for DNS resolution preference `resolve_preference`
## 1.0.1
- Fix server SOCKS5 outbound bug
- Fix incorrect UDP fragmentation handling
## 1.0.0
- Protocol v3: UDP fragmentation support
- Fix SOCKS5 UDP timeout issue
- SOCKS5 outbound support
## 0.9.7
- CLI improvements (cobra)
- Fix broken UDP TProxy mode
- Re-enable PMTUD on Windows & Linux
## 0.9.6
- Disable quic-go PMTUD due to broken implementation
- Fix zero initMaxDatagramSize in brutal CC
- Client retry
## 0.9.5
- Client connect & disconnect log
- Warning when no auth or obfs is set
- Multi-password & cmd auth support
## 0.9.4
- fsnotify-based auto keypair reloading
- ACL country code support
## 0.9.3
- CC optimizations
- Set buffer correctly for faketcp mode
- "wechat-video" protocol
## 0.9.2
- Updated quic-go to v0.24.0
- Reduced obfs overhead by reusing buffers
## 0.9.1
- faketcp implementation
- DNS `resolver` option in config
## 0.9.0
- Auto keypair reloading
- SOCKS5 listen address no longer needs a specific IP
- Multi-relay support
- IPv6 only mode for server
## 0.8.6
- Added an option for customizing ALPN `alpn`
- Removed ACL support from TPROXY & TUN modes
## 0.8.5
- Added an option to disable MTU discovery `disable_mtu_discovery`

View file

@ -1,5 +1,7 @@
FROM golang:1-alpine AS builder FROM golang:1-alpine AS builder
LABEL maintainer="mritd <mritd@linux.com>"
# GOPROXY is disabled by default, use: # GOPROXY is disabled by default, use:
# docker build --build-arg GOPROXY="https://goproxy.io" ... # docker build --build-arg GOPROXY="https://goproxy.io" ...
# to enable GOPROXY. # to enable GOPROXY.
@ -12,13 +14,15 @@ COPY . /go/src/github.com/apernet/hysteria
WORKDIR /go/src/github.com/apernet/hysteria WORKDIR /go/src/github.com/apernet/hysteria
RUN set -ex \ RUN set -ex \
&& apk add git build-base bash python3 \ && apk add git build-base bash \
&& python hyperbole.py build -r \ && ./build.sh \
&& mv ./build/hysteria-* /go/bin/hysteria && mv ./build/hysteria-* /go/bin/hysteria
# multi-stage builds to create the final image # multi-stage builds to create the final image
FROM alpine AS dist FROM alpine AS dist
LABEL maintainer="mritd <mritd@linux.com>"
# set up nsswitch.conf for Go's "netgo" implementation # set up nsswitch.conf for Go's "netgo" implementation
# - https://github.com/golang/go/blob/go1.9.1/src/net/conf.go#L194-L275 # - https://github.com/golang/go/blob/go1.9.1/src/net/conf.go#L194-L275
# - docker run --rm debian:stretch grep '^hosts:' /etc/nsswitch.conf # - docker run --rm debian:stretch grep '^hosts:' /etc/nsswitch.conf
@ -36,4 +40,4 @@ RUN set -ex \
COPY --from=builder /go/bin/hysteria /usr/local/bin/hysteria COPY --from=builder /go/bin/hysteria /usr/local/bin/hysteria
ENTRYPOINT ["hysteria"] ENTRYPOINT ["hysteria"]

View file

@ -1,7 +1,30 @@
Copyright 2023 Toby License
==================
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: Hysteria itself, including all codes under this directory, is licensed under the MIT License.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. ```
The MIT License (MIT)
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. Copyright (c) 2021 Toby
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
```
However, when building with `-tags gpl`, the produced executable shall be distributed under GPLv3.

View file

@ -1,153 +0,0 @@
# Hysteria 2 Protocol Specification
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".
## Requirements Language
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).
## Underlying Protocol & Wire Format
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/).
All multibyte numbers use Big Endian format.
All variable-length integers ("varints") are encoded/decoded as defined in QUIC (RFC 9000).
## Authentication & HTTP/3 masquerading
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.
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.
An actual Hysteria client, upon connection, MUST send the following HTTP/3 request to the server:
```
:method: POST
:path: /auth
:host: hysteria
Hysteria-Auth: [string]
Hysteria-CC-RX: [uint]
Hysteria-Padding: [string]
```
`Hysteria-Auth`: Authentication credentials.
`Hysteria-CC-RX`: Client's maximum receive rate in bytes per second. A value of 0 indicates unknown.
`Hysteria-Padding`: A random padding string of variable length.
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):
```
:status: 233 HyOK
Hysteria-UDP: [true/false]
Hysteria-CC-RX: [uint/"auto"]
Hysteria-Padding: [string]
```
`Hysteria-UDP`: Whether the server supports UDP relay.
`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.
`Hysteria-Padding`: A random padding string of variable length.
See the Congestion Control section for more information on how to use the `Hysteria-CC-RX` values.
`Hysteria-Padding` is optional and is only intended to obfuscate the request/response pattern. It SHOULD be ignored by both sides.
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.
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.
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.
## Proxy Requests
### TCP
For each TCP connection, the client MUST create a new QUIC bidirectional stream and send the following TCPRequest message:
```
[varint] 0x401 (TCPRequest ID)
[varint] Address length
[bytes] Address string (host:port)
[varint] Padding length
[bytes] Random padding
```
The server MUST respond with a TCPResponse message:
```
[uint8] Status (0x00 = OK, 0x01 = Error)
[varint] Message length
[bytes] Message string
[varint] Padding length
[bytes] Random padding
```
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.
### UDP
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):
```
[uint32] Session ID
[uint16] Packet ID
[uint8] Fragment ID
[uint8] Fragment count
[varint] Address length
[bytes] Address string (host:port)
[bytes] Payload
```
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.).
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.
If a server does not support UDP relay, it SHOULD silently discard all UDP messages received from the client.
#### Fragmentation
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.
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.
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.
## Congestion Control
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.
Three special cases are:
- 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.
- If the server responds with 0, it has no bandwidth limit. The client MAY transmit at any rate it wants.
- 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.
## "Salamander" Obfuscation
The Hysteria protocol supports an optional obfuscation layer codenamed "Salamander".
"Salamander" encapsulates all QUIC packets in the following format:
```
[8 bytes] Salt
[bytes] Payload
```
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.
```
hash = BLAKE2b-256(key + salt)
```
The hash is then used to obfuscate the payload using the following algorithm:
```
for i in range(0, len(payload)):
payload[i] ^= hash[i % 32]
```
The deobfuscator MUST use the same algorithms to calculate the salted hash and deobfuscate the payload. Any invalid packet MUST be discarded.

101
README.md
View file

@ -1,60 +1,87 @@
# ![Hysteria 2](logo.svg) # ![Logo](docs/logos/readme.png)
[![License][1]][2] [![Release][3]][4] [![Telegram][5]][6] [![Discussions][7]][8] [![License][1]][2] [![Release][3]][4] [![Telegram][5]][6] [![Discussions][7]][8]
[1]: https://img.shields.io/badge/license-MIT-blue [1]: https://img.shields.io/badge/license-MIT-blue
[2]: LICENSE.md [2]: LICENSE.md
[3]: https://img.shields.io/github/v/release/apernet/hysteria?style=flat-square [3]: https://img.shields.io/github/v/release/apernet/hysteria?style=flat-square
[4]: https://github.com/apernet/hysteria/releases [4]: https://github.com/apernet/hysteria/releases
[5]: https://img.shields.io/badge/chat-Telegram-blue?style=flat-square [5]: https://img.shields.io/badge/chat-Telegram-blue?style=flat-square
[6]: https://t.me/hysteria_github [6]: https://t.me/hysteria_github
[7]: https://img.shields.io/github/discussions/apernet/hysteria?style=flat-square [7]: https://img.shields.io/github/discussions/apernet/hysteria?style=flat-square
[8]: https://github.com/apernet/hysteria/discussions [8]: https://github.com/apernet/hysteria/discussions
<h2 style="text-align: center;">Hysteria is a powerful, lightning fast and censorship resistant proxy.</h2> ![AperNet](docs/logos/AperNetLogo.png)
### [Get Started](https://v2.hysteria.network/) An [Aperture Internet Laboratory](https://apernet.io/) project
### [中文文档](https://v2.hysteria.network/zh/) ----------
### [Hysteria 1.x (legacy)](https://v1.hysteria.network/) Hysteria is a feature-packed proxy & relay tool optimized for lossy, unstable connections (e.g. satellite networks,
congested public Wi-Fi, connecting to foreign servers from China) powered by a customized protocol based on QUIC.
--- ## Use cases
<div class="feature-grid"> - Censorship circumvention
<div> - Boosting slow connections
<h3>🛠️ Jack of all trades</h3> - Bypassing commercial/academic/corporate firewalls
<p>Wide range of modes including SOCKS5, HTTP Proxy, TCP/UDP Forwarding, Linux TProxy, TUN - with more features being added constantly.</p> - Bypassing ISP throttling
</div> - ...
<div> ## Modes
<h3>⚡ Blazing fast</h3>
<p>Powered by a customized QUIC protocol, Hysteria is designed to deliver unparalleled performance over unreliable and lossy networks.</p>
</div>
<div> - SOCKS5 proxy (TCP & UDP)
<h3>✊ Censorship resistant</h3> - HTTP/HTTPS proxy
<p>The protocol masquerades as standard HTTP/3 traffic, making it very difficult for censors to detect and block without widespread collateral damage.</p> - TCP/UDP relay
</div> - TCP/UDP TPROXY (Linux)
- TCP REDIRECT (Linux)
<div> - TUN (TAP on Windows)
<h3>💻 Cross-platform</h3> - Still growing...
<p>We have builds for every major platform and architecture. Deploy anywhere & use everywhere. Not to mention the long list of 3rd party apps.</p>
</div>
<div> ## **[Documentation](https://hysteria.network/)**
<h3>🔗 Easy integration</h3>
<p>With built-in support for custom authentication, traffic statistics & access control, Hysteria is easy to integrate into your infrastructure.</p>
</div>
<div>
<h3>🤗 Chill and supportive</h3>
<p>We have well-documented specifications and code for developers to contribute and/or build their own apps. And a helpful community, too.</p>
</div>
</div>
--- ----------
**If you find Hysteria useful, consider giving it a ⭐️!** Hysteria 是一个功能丰富的,专为恶劣网络环境(如卫星网络、拥挤的公共 Wi-Fi、从中国连接境外服务器等进行优化的双边加速工具基于修改版的 QUIC 协议。
[![Star History Chart](https://api.star-history.com/svg?repos=apernet/hysteria&type=Date)](https://star-history.com/#apernet/hysteria&Date) ## 常见用例
- 绕过网络审查
- 提升传输速度
- 绕过商业/学校/企业防火墙
- 绕过运营商 QoS 限速
## 模式
- SOCKS5 代理 (TCP & UDP)
- HTTP/HTTPS 代理
- TCP/UDP 转发
- TCP/UDP TPROXY 透明代理 (Linux)
- TCP REDIRECT 透明代理 (Linux)
- TUN (Windows 下为 TAP)
- 仍在增加中...
## **[中文文档](https://hysteria.network/zh/)**
----------
## Benchmarks
![Bench](docs/bench/bench.png)
----------
**Donations are greatly appreciated!** Contact me if you would like your name listed as a sponsor.
**欢迎大佬捐赠!** 如希望挂名请在捐赠后联系我。
<a href="https://nowpayments.io/donation?api_key=EJH83FM-FDC40ZW-QGDZRR4-A7SC67S" target="_blank">
<img src="https://nowpayments.io/images/embeds/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>

156
Taskfile.yaml Normal file
View file

@ -0,0 +1,156 @@
version: '3'
tasks:
clean:
cmds:
- rm -rf dist
- mkdir -p dist
build-hysteria:
label: build-{{.TASK}}
vars:
BUILD_COMMIT:
sh: git rev-parse HEAD
BUILD_DATE:
sh: date "+%Y%m%d%H%M%S"
dir: ./app/cmd/
cmds:
- |
GOOS={{.GOOS}} GOARCH={{.GOARCH}} GOARM={{.GOARM}} GOMIPS={{.GOMIPS}} \
go build -trimpath -o ../../dist/hysteria-{{.TASK}} -ldflags \
"-w -s -X main.appCommit={{.BUILD_COMMIT}} -X main.appDate={{.BUILD_DATE}}"
linux-386:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: 386
}
linux-amd64:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: amd64
}
linux-amd64-v3:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: amd64,
GOAMD64: v3
}
linux-armv5:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: arm,
GOARM: 5
}
linux-armv6:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: arm,
GOARM: 6
}
linux-armv7:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: arm,
GOARM: 7
}
linux-armv8:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: arm64
}
linux-mips-hardfloat:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: mips,
GOMIPS: hardfloat
}
linux-mipsle-softfloat:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: mipsle,
GOMIPS: softfloat
}
linux-mipsle-hardfloat:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: mipsle,
GOMIPS: hardfloat
}
linux-mips64:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: mips64
}
linux-mips64le:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: linux,
GOARCH: mips64le
}
darwin-amd64:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: darwin,
GOARCH: amd64
}
darwin-arm64:
cmds:
- task: build-hysteria
vars: {
TASK: "{{.TASK}}",
GOOS: darwin,
GOARCH: arm64
}
default:
cmds:
- task: clean
- task: linux-386
- task: linux-amd64
- task: linux-amd64-v3
- task: linux-armv5
- task: linux-armv6
- task: linux-armv7
- task: linux-armv8
- task: linux-mips-hardfloat
- task: linux-mipsle-softfloat
- task: linux-mipsle-hardfloat
- task: linux-mips64
- task: linux-mips64le
- task: darwin-amd64
- task: darwin-arm64

View file

@ -1,7 +0,0 @@
Copyright 2023 Toby
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.

97
app/auth/external.go Normal file
View file

@ -0,0 +1,97 @@
package auth
import (
"bytes"
"encoding/json"
"io/ioutil"
"net"
"net/http"
"os/exec"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
type CmdAuthProvider struct {
Cmd string
}
func (p *CmdAuthProvider) Auth(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
cmd := exec.Command(p.Cmd, addr.String(), string(auth), strconv.Itoa(int(sSend)), strconv.Itoa(int(sRecv)))
out, err := cmd.Output()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
return false, strings.TrimSpace(string(out))
} else {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to execute auth command")
return false, "internal error"
}
} else {
return true, strings.TrimSpace(string(out))
}
}
type HTTPAuthProvider struct {
Client *http.Client
URL string
}
type authReq struct {
Addr string `json:"addr"`
Payload []byte `json:"payload"`
Send uint64 `json:"send"`
Recv uint64 `json:"recv"`
}
type authResp struct {
OK bool `json:"ok"`
Msg string `json:"msg"`
}
func (p *HTTPAuthProvider) Auth(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
jbs, err := json.Marshal(&authReq{
Addr: addr.String(),
Payload: auth,
Send: sSend,
Recv: sRecv,
})
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to marshal auth request")
return false, "internal error"
}
resp, err := p.Client.Post(p.URL, "application/json", bytes.NewBuffer(jbs))
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to send auth request")
return false, "internal error"
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logrus.WithFields(logrus.Fields{
"code": resp.StatusCode,
}).Error("Invalid status code from auth server")
return false, "internal error"
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to read auth response")
return false, "internal error"
}
var ar authResp
err = json.Unmarshal(data, &ar)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to unmarshal auth response")
return false, "internal error"
}
return ar.OK, ar.Msg
}

59
app/auth/funcs.go Normal file
View file

@ -0,0 +1,59 @@
package auth
import (
"errors"
"net"
"net/http"
"time"
"github.com/apernet/hysteria/core/cs"
"github.com/yosuke-furukawa/json5/encoding/json5"
)
func PasswordAuthFunc(rawMsg json5.RawMessage) (cs.ConnectFunc, error) {
var pwds []string
err := json5.Unmarshal(rawMsg, &pwds)
if err != nil {
// not a string list, legacy format?
var pwdConfig map[string]string
err = json5.Unmarshal(rawMsg, &pwdConfig)
if err != nil || len(pwdConfig["password"]) == 0 {
// still no, invalid config
return nil, errors.New("invalid config")
}
// yes it is
pwds = []string{pwdConfig["password"]}
}
return func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
for _, pwd := range pwds {
if string(auth) == pwd {
return true, "Welcome"
}
}
return false, "Wrong password"
}, nil
}
func ExternalAuthFunc(rawMsg json5.RawMessage) (cs.ConnectFunc, error) {
var extConfig map[string]string
err := json5.Unmarshal(rawMsg, &extConfig)
if err != nil {
return nil, errors.New("invalid config")
}
if len(extConfig["http"]) != 0 {
hp := &HTTPAuthProvider{
Client: &http.Client{
Timeout: 10 * time.Second,
},
URL: extConfig["http"],
}
return hp.Auth, nil
} else if len(extConfig["cmd"]) != 0 {
cp := &CmdAuthProvider{
Cmd: extConfig["cmd"],
}
return cp.Auth, nil
} else {
return nil, errors.New("invalid config")
}
}

74
app/cmd/acme.go Normal file
View file

@ -0,0 +1,74 @@
package main
import (
"context"
"crypto/tls"
"os"
"path/filepath"
"runtime"
"go.uber.org/zap"
"github.com/caddyserver/certmagic"
)
func acmeTLSConfig(domains []string, email string, disableHTTP bool, disableTLSALPN bool,
altHTTPPort int, altTLSALPNPort int,
) (*tls.Config, error) {
cfg := &certmagic.Config{
RenewalWindowRatio: certmagic.DefaultRenewalWindowRatio,
KeySource: certmagic.DefaultKeyGenerator,
Storage: &certmagic.FileStorage{Path: dataDir()},
Logger: zap.NewNop(),
}
issuer := certmagic.NewACMEIssuer(cfg, certmagic.ACMEIssuer{
CA: certmagic.LetsEncryptProductionCA,
TestCA: certmagic.LetsEncryptStagingCA,
Email: email,
Agreed: true,
DisableHTTPChallenge: disableHTTP,
DisableTLSALPNChallenge: disableTLSALPN,
AltHTTPPort: altHTTPPort,
AltTLSALPNPort: altTLSALPNPort,
Logger: zap.NewNop(),
})
cfg.Issuers = []certmagic.Issuer{issuer}
cache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
return cfg, nil
},
Logger: zap.NewNop(),
})
cfg = certmagic.New(cache, *cfg)
err := cfg.ManageSync(context.Background(), domains)
if err != nil {
return nil, err
}
return cfg.TLSConfig(), nil
}
func homeDir() string {
home := os.Getenv("HOME")
if home == "" && runtime.GOOS == "windows" {
drive := os.Getenv("HOMEDRIVE")
path := os.Getenv("HOMEPATH")
home = drive + path
if drive == "" || path == "" {
home = os.Getenv("USERPROFILE")
}
}
if home == "" {
home = "."
}
return home
}
func dataDir() string {
baseDir := filepath.Join(homeDir(), ".local", "share")
if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
baseDir = xdgData
}
return filepath.Join(baseDir, "certmagic")
}

File diff suppressed because it is too large Load diff

117
app/cmd/client_gpl.go Normal file
View file

@ -0,0 +1,117 @@
//go:build gpl
// +build gpl
package main
import (
"io"
"net"
"strings"
"time"
"github.com/apernet/hysteria/app/tun"
"github.com/docker/go-units"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"github.com/apernet/hysteria/core/cs"
"github.com/sirupsen/logrus"
)
const license = `Hysteria is a feature-packed proxy & relay utility optimized for lossy, unstable connections.
Copyright (C) 2022 Toby
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
`
func startTUN(config *clientConfig, client *cs.Client, errChan chan error) {
timeout := time.Duration(config.TUN.Timeout) * time.Second
if timeout == 0 {
timeout = 300 * time.Second
}
var err error
var tcpSendBufferSize, tcpReceiveBufferSize int64
if config.TUN.TCPSendBufferSize != "" {
tcpSendBufferSize, err = units.RAMInBytes(config.TUN.TCPSendBufferSize)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
"tcp-sndbuf": config.TUN.TCPSendBufferSize,
}).Fatal("Failed to parse tcp-sndbuf in the TUN config")
}
if (tcpSendBufferSize != 0 && tcpSendBufferSize < tcp.MinBufferSize) || tcpSendBufferSize > tcp.MaxBufferSize {
logrus.WithFields(logrus.Fields{
"tcp-sndbuf": config.TUN.TCPSendBufferSize,
}).Fatal("Invalid tcp-sndbuf in the TUN config")
}
}
if config.TUN.TCPReceiveBufferSize != "" {
tcpReceiveBufferSize, err = units.RAMInBytes(config.TUN.TCPReceiveBufferSize)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
"tcp-rcvbuf": config.TUN.TCPReceiveBufferSize,
}).Fatal("Failed to parse tcp-rcvbuf in the TUN config")
}
if (tcpReceiveBufferSize != 0 && tcpReceiveBufferSize < tcp.MinBufferSize) || tcpReceiveBufferSize > tcp.MaxBufferSize {
logrus.WithFields(logrus.Fields{
"error": err,
"tcp-rcvbuf": config.TUN.TCPReceiveBufferSize,
}).Fatal("Invalid tcp-rcvbuf in the TUN config")
}
}
tunServer, err := tun.NewServer(client, timeout,
config.TUN.Name, config.TUN.MTU,
int(tcpSendBufferSize), int(tcpReceiveBufferSize), config.TUN.TCPModerateReceiveBuffer)
if err != nil {
logrus.WithField("error", err).Fatal("Failed to initialize TUN server")
}
tunServer.RequestFunc = func(addr net.Addr, reqAddr string) {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Debugf("TUN %s request", strings.ToUpper(addr.Network()))
}
tunServer.ErrorFunc = func(addr net.Addr, reqAddr string, err error) {
if err != nil {
if err == io.EOF {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Debugf("TUN %s EOF", strings.ToUpper(addr.Network()))
} else if err == cs.ErrClosed && strings.HasPrefix(addr.Network(), "udp") {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Debugf("TUN %s closed for timeout", strings.ToUpper(addr.Network()))
} else if nErr, ok := err.(net.Error); ok && nErr.Timeout() && strings.HasPrefix(addr.Network(), "tcp") {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Debugf("TUN %s closed for timeout", strings.ToUpper(addr.Network()))
} else {
logrus.WithFields(logrus.Fields{
"error": err,
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Infof("TUN %s error", strings.ToUpper(addr.Network()))
}
}
}
logrus.WithField("interface", config.TUN.Name).Info("TUN up and running")
errChan <- tunServer.ListenAndServe()
}

36
app/cmd/client_nongpl.go Normal file
View file

@ -0,0 +1,36 @@
//go:build !gpl
// +build !gpl
package main
import (
"github.com/apernet/hysteria/core/cs"
"github.com/sirupsen/logrus"
)
const license = `The MIT License (MIT)
Copyright (c) 2021 Toby
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
`
func startTUN(config *clientConfig, client *cs.Client, errChan chan error) {
logrus.Fatalln("TUN mode is only available in GPL builds. Please rebuild hysteria with -tags gpl")
}

View file

@ -1,204 +0,0 @@
package cmd
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/spf13/viper"
)
// TestClientConfig tests the parsing of the client config
func TestClientConfig(t *testing.T) {
viper.SetConfigFile("client_test.yaml")
err := viper.ReadInConfig()
assert.NoError(t, err)
var config clientConfig
err = viper.Unmarshal(&config)
assert.NoError(t, err)
assert.Equal(t, config, clientConfig{
Server: "example.com",
Auth: "weak_ahh_password",
Transport: clientConfigTransport{
Type: "udp",
UDP: clientConfigTransportUDP{
HopInterval: 30 * time.Second,
},
},
Obfs: clientConfigObfs{
Type: "salamander",
Salamander: clientConfigObfsSalamander{
Password: "cry_me_a_r1ver",
},
},
TLS: clientConfigTLS{
SNI: "another.example.com",
Insecure: true,
PinSHA256: "114515DEADBEEF",
CA: "custom_ca.crt",
},
QUIC: clientConfigQUIC{
InitStreamReceiveWindow: 1145141,
MaxStreamReceiveWindow: 1145142,
InitConnectionReceiveWindow: 1145143,
MaxConnectionReceiveWindow: 1145144,
MaxIdleTimeout: 10 * time.Second,
KeepAlivePeriod: 4 * time.Second,
DisablePathMTUDiscovery: true,
Sockopts: clientConfigQUICSockopts{
BindInterface: stringRef("eth0"),
FirewallMark: uint32Ref(1234),
FdControlUnixSocket: stringRef("test.sock"),
},
},
Bandwidth: clientConfigBandwidth{
Up: "200 mbps",
Down: "1 gbps",
},
FastOpen: true,
Lazy: true,
SOCKS5: &socks5Config{
Listen: "127.0.0.1:1080",
Username: "anon",
Password: "bro",
DisableUDP: true,
},
HTTP: &httpConfig{
Listen: "127.0.0.1:8080",
Username: "qqq",
Password: "bruh",
Realm: "martian",
},
TCPForwarding: []tcpForwardingEntry{
{
Listen: "127.0.0.1:8088",
Remote: "internal.example.com:80",
},
},
UDPForwarding: []udpForwardingEntry{
{
Listen: "127.0.0.1:5353",
Remote: "internal.example.com:53",
Timeout: 50 * time.Second,
},
},
TCPTProxy: &tcpTProxyConfig{
Listen: "127.0.0.1:2500",
},
UDPTProxy: &udpTProxyConfig{
Listen: "127.0.0.1:2501",
Timeout: 20 * time.Second,
},
TCPRedirect: &tcpRedirectConfig{
Listen: "127.0.0.1:3500",
},
TUN: &tunConfig{
Name: "hytun",
MTU: 1500,
Timeout: 60 * time.Second,
Address: struct {
IPv4 string `mapstructure:"ipv4"`
IPv6 string `mapstructure:"ipv6"`
}{IPv4: "100.100.100.101/30", IPv6: "2001::ffff:ffff:ffff:fff1/126"},
Route: &struct {
Strict bool `mapstructure:"strict"`
IPv4 []string `mapstructure:"ipv4"`
IPv6 []string `mapstructure:"ipv6"`
IPv4Exclude []string `mapstructure:"ipv4Exclude"`
IPv6Exclude []string `mapstructure:"ipv6Exclude"`
}{
Strict: true,
IPv4: []string{"0.0.0.0/0"},
IPv6: []string{"2000::/3"},
IPv4Exclude: []string{"192.0.2.1/32"},
IPv6Exclude: []string{"2001:db8::1/128"},
},
},
})
}
// TestClientConfigURI tests URI-related functions of clientConfig
func TestClientConfigURI(t *testing.T) {
tests := []struct {
uri string
uriOK bool
config *clientConfig
}{
{
uri: "hysteria2://god@zilla.jp/",
uriOK: true,
config: &clientConfig{
Server: "zilla.jp",
Auth: "god",
},
},
{
uri: "hysteria2://john:wick@continental.org:4443/",
uriOK: true,
config: &clientConfig{
Server: "continental.org:4443",
Auth: "john:wick",
},
},
{
uri: "hysteria2://saul@better.call:7000-10000,20000/",
uriOK: true,
config: &clientConfig{
Server: "better.call:7000-10000,20000",
Auth: "saul",
},
},
{
uri: "hysteria2://noauth.com/?insecure=1&obfs=salamander&obfs-password=66ccff&pinSHA256=deadbeef&sni=crap.cc",
uriOK: true,
config: &clientConfig{
Server: "noauth.com",
Auth: "",
Obfs: clientConfigObfs{
Type: "salamander",
Salamander: clientConfigObfsSalamander{
Password: "66ccff",
},
},
TLS: clientConfigTLS{
SNI: "crap.cc",
Insecure: true,
PinSHA256: "deadbeef",
},
},
},
{
uri: "invalid.bs",
uriOK: false,
config: nil,
},
{
uri: "https://www.google.com/search?q=test",
uriOK: false,
config: nil,
},
}
for _, test := range tests {
t.Run(test.uri, func(t *testing.T) {
// Test parseURI
nc := &clientConfig{Server: test.uri}
assert.Equal(t, nc.parseURI(), test.uriOK)
if test.uriOK {
assert.Equal(t, nc, test.config)
}
// Test URI generation
if test.config != nil {
assert.Equal(t, test.config.URI(), test.uri)
}
})
}
}
func stringRef(s string) *string {
return &s
}
func uint32Ref(i uint32) *uint32 {
return &i
}

View file

@ -1,85 +0,0 @@
server: example.com
auth: weak_ahh_password
transport:
type: udp
udp:
hopInterval: 30s
obfs:
type: salamander
salamander:
password: cry_me_a_r1ver
tls:
sni: another.example.com
insecure: true
pinSHA256: 114515DEADBEEF
ca: custom_ca.crt
quic:
initStreamReceiveWindow: 1145141
maxStreamReceiveWindow: 1145142
initConnReceiveWindow: 1145143
maxConnReceiveWindow: 1145144
maxIdleTimeout: 10s
keepAlivePeriod: 4s
disablePathMTUDiscovery: true
sockopts:
bindInterface: eth0
fwmark: 1234
fdControlUnixSocket: test.sock
bandwidth:
up: 200 mbps
down: 1 gbps
fastOpen: true
lazy: true
socks5:
listen: 127.0.0.1:1080
username: anon
password: bro
disableUDP: true
http:
listen: 127.0.0.1:8080
username: qqq
password: bruh
realm: martian
tcpForwarding:
- listen: 127.0.0.1:8088
remote: internal.example.com:80
udpForwarding:
- listen: 127.0.0.1:5353
remote: internal.example.com:53
timeout: 50s
tcpTProxy:
listen: 127.0.0.1:2500
udpTProxy:
listen: 127.0.0.1:2501
timeout: 20s
tcpRedirect:
listen: 127.0.0.1:3500
tun:
name: "hytun"
mtu: 1500
timeout: 1m
address:
ipv4: 100.100.100.101/30
ipv6: 2001::ffff:ffff:ffff:fff1/126
route:
strict: true
ipv4: [ 0.0.0.0/0 ]
ipv6: [ "2000::/3" ]
ipv4Exclude: [ 192.0.2.1/32 ]
ipv6Exclude: [ "2001:db8::1/128" ]

67
app/cmd/completion.go Normal file
View file

@ -0,0 +1,67 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions:
Bash:
$ source <(%[1]s completion bash)
# To load completions for each session, execute once:
# Linux:
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
# macOS:
$ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
Zsh:
# If shell completion is not already enabled in your environment,
# you will need to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
# You will need to start a new shell for this setup to take effect.
fish:
$ %[1]s completion fish | source
# To load completions for each session, execute once:
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
PowerShell:
PS> %[1]s completion powershell | Out-String | Invoke-Expression
# To load completions for every new session, run:
PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile.
`, rootCmd.Name()),
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
_ = cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
_ = cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
_ = cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
_ = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}

381
app/cmd/config.go Normal file
View file

@ -0,0 +1,381 @@
package main
import (
"errors"
"fmt"
"regexp"
"strconv"
"github.com/sirupsen/logrus"
"github.com/yosuke-furukawa/json5/encoding/json5"
)
const (
mbpsToBps = 125000
minSpeedBPS = 16384
DefaultALPN = "hysteria"
DefaultStreamReceiveWindow = 16777216 // 16 MB
DefaultConnectionReceiveWindow = DefaultStreamReceiveWindow * 5 / 2 // 40 MB
DefaultMaxIncomingStreams = 1024
DefaultMMDBFilename = "GeoLite2-Country.mmdb"
ServerMaxIdleTimeoutSec = 60
DefaultClientIdleTimeoutSec = 20
DefaultClientHopIntervalSec = 10
)
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
type serverConfig struct {
Listen string `json:"listen"`
Protocol string `json:"protocol"`
ACME struct {
Domains []string `json:"domains"`
Email string `json:"email"`
DisableHTTPChallenge bool `json:"disable_http"`
DisableTLSALPNChallenge bool `json:"disable_tlsalpn"`
AltHTTPPort int `json:"alt_http_port"`
AltTLSALPNPort int `json:"alt_tlsalpn_port"`
} `json:"acme"`
CertFile string `json:"cert"`
KeyFile string `json:"key"`
// Optional below
Up string `json:"up"`
UpMbps int `json:"up_mbps"`
Down string `json:"down"`
DownMbps int `json:"down_mbps"`
DisableUDP bool `json:"disable_udp"`
ACL string `json:"acl"`
MMDB string `json:"mmdb"`
Obfs string `json:"obfs"`
Auth struct {
Mode string `json:"mode"`
Config json5.RawMessage `json:"config"`
} `json:"auth"`
ALPN string `json:"alpn"`
PrometheusListen string `json:"prometheus_listen"`
ReceiveWindowConn uint64 `json:"recv_window_conn"`
ReceiveWindowClient uint64 `json:"recv_window_client"`
MaxConnClient int `json:"max_conn_client"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery"`
Resolver string `json:"resolver"`
ResolvePreference string `json:"resolve_preference"`
SOCKS5Outbound struct {
Server string `json:"server"`
User string `json:"user"`
Password string `json:"password"`
} `json:"socks5_outbound"`
BindOutbound struct {
Address string `json:"address"`
Device string `json:"device"`
} `json:"bind_outbound"`
}
func (c *serverConfig) Speed() (uint64, uint64, error) {
var up, down uint64
if len(c.Up) > 0 {
up = stringToBps(c.Up)
if up == 0 {
return 0, 0, errors.New("invalid speed format")
}
} else {
up = uint64(c.UpMbps) * mbpsToBps
}
if len(c.Down) > 0 {
down = stringToBps(c.Down)
if down == 0 {
return 0, 0, errors.New("invalid speed format")
}
} else {
down = uint64(c.DownMbps) * mbpsToBps
}
return up, down, nil
}
func (c *serverConfig) Check() error {
if len(c.Listen) == 0 {
return errors.New("missing listen address")
}
if len(c.ACME.Domains) == 0 && (len(c.CertFile) == 0 || len(c.KeyFile) == 0) {
return errors.New("need either ACME info or cert/key files")
}
if up, down, err := c.Speed(); err != nil || (up != 0 && up < minSpeedBPS) || (down != 0 && down < minSpeedBPS) {
return errors.New("invalid speed")
}
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
(c.ReceiveWindowClient != 0 && c.ReceiveWindowClient < 65536) {
return errors.New("invalid receive window size")
}
if c.MaxConnClient < 0 {
return errors.New("invalid max connections per client")
}
return nil
}
func (c *serverConfig) Fill() {
if len(c.ALPN) == 0 {
c.ALPN = DefaultALPN
}
if c.ReceiveWindowConn == 0 {
c.ReceiveWindowConn = DefaultStreamReceiveWindow
}
if c.ReceiveWindowClient == 0 {
c.ReceiveWindowClient = DefaultConnectionReceiveWindow
}
if c.MaxConnClient == 0 {
c.MaxConnClient = DefaultMaxIncomingStreams
}
if len(c.MMDB) == 0 {
c.MMDB = DefaultMMDBFilename
}
}
func (c *serverConfig) String() string {
return fmt.Sprintf("%+v", *c)
}
type Relay struct {
Listen string `json:"listen"`
Remote string `json:"remote"`
Timeout int `json:"timeout"`
}
func (r *Relay) Check() error {
if len(r.Listen) == 0 {
return errors.New("missing relay listen address")
}
if len(r.Remote) == 0 {
return errors.New("missing relay remote address")
}
if r.Timeout != 0 && r.Timeout < 4 {
return errors.New("invalid relay timeout")
}
return nil
}
type clientConfig struct {
Server string `json:"server"`
Protocol string `json:"protocol"`
Up string `json:"up"`
UpMbps int `json:"up_mbps"`
Down string `json:"down"`
DownMbps int `json:"down_mbps"`
// Optional below
Retry int `json:"retry"`
RetryInterval int `json:"retry_interval"`
QuitOnDisconnect bool `json:"quit_on_disconnect"`
HandshakeTimeout int `json:"handshake_timeout"`
IdleTimeout int `json:"idle_timeout"`
HopInterval int `json:"hop_interval"`
SOCKS5 struct {
Listen string `json:"listen"`
Timeout int `json:"timeout"`
DisableUDP bool `json:"disable_udp"`
User string `json:"user"`
Password string `json:"password"`
} `json:"socks5"`
HTTP struct {
Listen string `json:"listen"`
Timeout int `json:"timeout"`
User string `json:"user"`
Password string `json:"password"`
Cert string `json:"cert"`
Key string `json:"key"`
} `json:"http"`
TUN struct {
Name string `json:"name"`
Timeout int `json:"timeout"`
MTU uint32 `json:"mtu"`
TCPSendBufferSize string `json:"tcp_sndbuf"`
TCPReceiveBufferSize string `json:"tcp_rcvbuf"`
TCPModerateReceiveBuffer bool `json:"tcp_autotuning"`
} `json:"tun"`
TCPRelays []Relay `json:"relay_tcps"`
TCPRelay Relay `json:"relay_tcp"` // deprecated, but we still support it for backward compatibility
UDPRelays []Relay `json:"relay_udps"`
UDPRelay Relay `json:"relay_udp"` // deprecated, but we still support it for backward compatibility
TCPTProxy struct {
Listen string `json:"listen"`
Timeout int `json:"timeout"`
} `json:"tproxy_tcp"`
UDPTProxy struct {
Listen string `json:"listen"`
Timeout int `json:"timeout"`
} `json:"tproxy_udp"`
TCPRedirect struct {
Listen string `json:"listen"`
Timeout int `json:"timeout"`
} `json:"redirect_tcp"`
ACL string `json:"acl"`
MMDB string `json:"mmdb"`
Obfs string `json:"obfs"`
Auth []byte `json:"auth"`
AuthString string `json:"auth_str"`
ALPN string `json:"alpn"`
ServerName string `json:"server_name"`
Insecure bool `json:"insecure"`
CustomCA string `json:"ca"`
ReceiveWindowConn uint64 `json:"recv_window_conn"`
ReceiveWindow uint64 `json:"recv_window"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery"`
FastOpen bool `json:"fast_open"`
Resolver string `json:"resolver"`
ResolvePreference string `json:"resolve_preference"`
}
func (c *clientConfig) Speed() (uint64, uint64, error) {
var up, down uint64
if len(c.Up) > 0 {
up = stringToBps(c.Up)
if up == 0 {
return 0, 0, errors.New("invalid speed format")
}
} else {
up = uint64(c.UpMbps) * mbpsToBps
}
if len(c.Down) > 0 {
down = stringToBps(c.Down)
if down == 0 {
return 0, 0, errors.New("invalid speed format")
}
} else {
down = uint64(c.DownMbps) * mbpsToBps
}
return up, down, nil
}
func (c *clientConfig) Check() error {
if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && len(c.TUN.Name) == 0 &&
len(c.TCPRelay.Listen) == 0 && len(c.UDPRelay.Listen) == 0 &&
len(c.TCPRelays) == 0 && len(c.UDPRelays) == 0 &&
len(c.TCPTProxy.Listen) == 0 && len(c.UDPTProxy.Listen) == 0 &&
len(c.TCPRedirect.Listen) == 0 {
return errors.New("please enable at least one mode")
}
if c.HandshakeTimeout != 0 && c.HandshakeTimeout < 2 {
return errors.New("invalid handshake timeout")
}
if c.IdleTimeout != 0 && c.IdleTimeout < 4 {
return errors.New("invalid idle timeout")
}
if c.HopInterval != 0 && c.HopInterval < 8 {
return errors.New("invalid hop interval")
}
if c.SOCKS5.Timeout != 0 && c.SOCKS5.Timeout < 4 {
return errors.New("invalid SOCKS5 timeout")
}
if c.HTTP.Timeout != 0 && c.HTTP.Timeout < 4 {
return errors.New("invalid HTTP timeout")
}
if c.TUN.Timeout != 0 && c.TUN.Timeout < 4 {
return errors.New("invalid TUN timeout")
}
if len(c.TCPRelay.Listen) > 0 && len(c.TCPRelay.Remote) == 0 {
return errors.New("missing TCP relay remote address")
}
if len(c.UDPRelay.Listen) > 0 && len(c.UDPRelay.Remote) == 0 {
return errors.New("missing UDP relay remote address")
}
if c.TCPRelay.Timeout != 0 && c.TCPRelay.Timeout < 4 {
return errors.New("invalid TCP relay timeout")
}
if c.UDPRelay.Timeout != 0 && c.UDPRelay.Timeout < 4 {
return errors.New("invalid UDP relay timeout")
}
for _, r := range c.TCPRelays {
if err := r.Check(); err != nil {
return err
}
}
for _, r := range c.UDPRelays {
if err := r.Check(); err != nil {
return err
}
}
if c.TCPTProxy.Timeout != 0 && c.TCPTProxy.Timeout < 4 {
return errors.New("invalid TCP TProxy timeout")
}
if c.UDPTProxy.Timeout != 0 && c.UDPTProxy.Timeout < 4 {
return errors.New("invalid UDP TProxy timeout")
}
if c.TCPRedirect.Timeout != 0 && c.TCPRedirect.Timeout < 4 {
return errors.New("invalid TCP Redirect timeout")
}
if len(c.Server) == 0 {
return errors.New("missing server address")
}
if up, down, err := c.Speed(); err != nil || up < minSpeedBPS || down < minSpeedBPS {
return errors.New("invalid speed")
}
if (c.ReceiveWindowConn != 0 && c.ReceiveWindowConn < 65536) ||
(c.ReceiveWindow != 0 && c.ReceiveWindow < 65536) {
return errors.New("invalid receive window size")
}
if len(c.TCPRelay.Listen) > 0 {
logrus.Warn("'relay_tcp' is deprecated, consider using 'relay_tcps' instead")
}
if len(c.UDPRelay.Listen) > 0 {
logrus.Warn("'relay_udp' is deprecated, consider using 'relay_udps' instead")
}
return nil
}
func (c *clientConfig) Fill() {
if len(c.ALPN) == 0 {
c.ALPN = DefaultALPN
}
if c.ReceiveWindowConn == 0 {
c.ReceiveWindowConn = DefaultStreamReceiveWindow
}
if c.ReceiveWindow == 0 {
c.ReceiveWindow = DefaultConnectionReceiveWindow
}
if len(c.MMDB) == 0 {
c.MMDB = DefaultMMDBFilename
}
if c.IdleTimeout == 0 {
c.IdleTimeout = DefaultClientIdleTimeoutSec
}
if c.HopInterval == 0 {
c.HopInterval = DefaultClientHopIntervalSec
}
}
func (c *clientConfig) String() string {
return fmt.Sprintf("%+v", *c)
}
func stringToBps(s string) uint64 {
if s == "" {
return 0
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}

34
app/cmd/config_test.go Normal file
View file

@ -0,0 +1,34 @@
package main
import "testing"
func Test_stringToBps(t *testing.T) {
tests := []struct {
name string
s string
want uint64
}{
{name: "bps 1", s: "8 bps", want: 1},
{name: "bps 2", s: "3 bps", want: 0},
{name: "Bps", s: "9991Bps", want: 9991},
{name: "KBps", s: "10 KBps", want: 10240},
{name: "Kbps", s: "10 Kbps", want: 1280},
{name: "MBps", s: "10 MBps", want: 10485760},
{name: "Mbps", s: "10 Mbps", want: 1310720},
{name: "GBps", s: "10 GBps", want: 10737418240},
{name: "Gbps", s: "10 Gbps", want: 1342177280},
{name: "TBps", s: "10 TBps", want: 10995116277760},
{name: "Tbps", s: "10 Tbps", want: 1374389534720},
{name: "invalid 1", s: "6699E Kbps", want: 0},
{name: "invalid 2", s: "400 Bsp", want: 0},
{name: "invalid 3", s: "9 GBbps", want: 0},
{name: "invalid 4", s: "Mbps", want: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := stringToBps(tt.s); got != tt.want {
t.Errorf("stringToBps() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,18 +0,0 @@
package cmd
import (
"fmt"
)
type configError struct {
Field string
Err error
}
func (e configError) Error() string {
return fmt.Sprintf("invalid config: %s: %s", e.Field, e.Err)
}
func (e configError) Unwrap() error {
return e.Err
}

43
app/cmd/ipmasker.go Normal file
View file

@ -0,0 +1,43 @@
package main
import (
"net"
)
type ipMasker struct {
IPv4Mask net.IPMask
IPv6Mask net.IPMask
}
// Mask masks an address with the configured CIDR.
// addr can be "host:port" or just host.
func (m *ipMasker) Mask(addr string) string {
if m.IPv4Mask == nil && m.IPv6Mask == nil {
return addr
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
// just host
host, port = addr, ""
}
ip := net.ParseIP(host)
if ip == nil {
// not an IP address, return as is
return addr
}
if ip4 := ip.To4(); ip4 != nil && m.IPv4Mask != nil {
// IPv4
host = ip4.Mask(m.IPv4Mask).String()
} else if ip6 := ip.To16(); ip6 != nil && m.IPv6Mask != nil {
// IPv6
host = ip6.Mask(m.IPv6Mask).String()
}
if port != "" {
return net.JoinHostPort(host, port)
} else {
return host
}
}
var defaultIPMasker = &ipMasker{}

95
app/cmd/kploader.go Normal file
View file

@ -0,0 +1,95 @@
package main
import (
"crypto/tls"
"sync"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
type keypairLoader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}
func newKeypairLoader(certPath, keyPath string) (*keypairLoader, error) {
loader := &keypairLoader{
certPath: certPath,
keyPath: keyPath,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
loader.cert = &cert
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
switch event.Op {
case fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Chmod:
logrus.WithFields(logrus.Fields{
"file": event.Name,
}).Info("Keypair change detected, reloading...")
if err := loader.load(); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to reload keypair")
} else {
logrus.Info("Keypair successfully reloaded")
}
case fsnotify.Remove:
_ = watcher.Add(event.Name) // Workaround for vim
// https://github.com/fsnotify/fsnotify/issues/92
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to watch keypair files for changes")
}
}
}()
err = watcher.Add(certPath)
if err != nil {
_ = watcher.Close()
return nil, err
}
err = watcher.Add(keyPath)
if err != nil {
_ = watcher.Close()
return nil, err
}
return loader, nil
}
func (kpr *keypairLoader) load() error {
cert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return err
}
kpr.certMu.Lock()
kpr.cert = &cert
kpr.certMu.Unlock()
return nil
}
func (kpr *keypairLoader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.certMu.RLock()
defer kpr.certMu.RUnlock()
return kpr.cert, nil
}
}

208
app/cmd/main.go Normal file
View file

@ -0,0 +1,208 @@
package main
import (
"fmt"
"io/ioutil"
"math/rand"
"net"
"os"
"regexp"
"strings"
"time"
nested "github.com/antonfisher/nested-logrus-formatter"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
logo = `
`
desc = "A TCP/UDP relay & SOCKS5/HTTP proxy tool optimized for poor network environments"
authors = "Aperture Internet Laboratory <https://github.com/apernet>"
)
var (
appVersion = "Unknown"
appCommit = "Unknown"
appDate = "Unknown"
)
var rootCmd = &cobra.Command{
Use: "hysteria",
Long: fmt.Sprintf("%s%s\n\nVersion:\t%s\nBuildDate:\t%s\nCommitHash:\t%s\nAuthors:\t%s", logo, desc, appVersion, appDate, appCommit, authors),
Example: "./hysteria server --config /etc/hysteria.json",
Version: fmt.Sprintf("%s %s %s", appVersion, appDate, appCommit),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
rand.Seed(time.Now().UnixNano())
// log config
logrus.SetOutput(os.Stdout)
if lvl, err := logrus.ParseLevel(viper.GetString("log-level")); err == nil {
logrus.SetLevel(lvl)
} else {
logrus.SetLevel(logrus.DebugLevel)
}
if strings.ToLower(viper.GetString("log-format")) == "json" {
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: viper.GetString("log-timestamp"),
})
} else {
logrus.SetFormatter(&nested.Formatter{
FieldsOrder: []string{
"version", "url",
"config", "file", "mode", "protocol",
"cert", "key",
"addr", "src", "dst", "session", "action", "interface",
"tcp-sndbuf", "tcp-rcvbuf",
"retry", "interval",
"code", "msg", "error",
},
TimestampFormat: viper.GetString("log-timestamp"),
})
}
// license
if viper.GetBool("license") {
fmt.Printf("%s\n", license)
os.Exit(0)
}
// ip mask config
v4m := viper.GetUint("log-ipv4-mask")
if v4m > 0 && v4m < 32 {
defaultIPMasker.IPv4Mask = net.CIDRMask(int(v4m), 32)
}
v6m := viper.GetUint("log-ipv6-mask")
if v6m > 0 && v6m < 128 {
defaultIPMasker.IPv6Mask = net.CIDRMask(int(v6m), 128)
}
// check update
if !viper.GetBool("no-check") {
go checkUpdate()
}
},
Run: func(cmd *cobra.Command, args []string) {
clientCmd.Run(cmd, args)
},
}
var clientCmd = &cobra.Command{
Use: "client",
Short: "Run as client mode",
Example: "./hysteria client --config /etc/hysteria/client.json",
Run: func(cmd *cobra.Command, args []string) {
cbs, err := ioutil.ReadFile(viper.GetString("config"))
if err != nil {
logrus.WithFields(logrus.Fields{
"file": viper.GetString("config"),
"error": err,
}).Fatal("Failed to read configuration")
}
// client mode
cc, err := parseClientConfig(cbs)
if err != nil {
logrus.WithFields(logrus.Fields{
"file": viper.GetString("config"),
"error": err,
}).Fatal("Failed to parse client configuration")
}
client(cc)
},
}
var serverCmd = &cobra.Command{
Use: "server",
Short: "Run as server mode",
Example: "./hysteria server --config /etc/hysteria/server.json",
Run: func(cmd *cobra.Command, args []string) {
cbs, err := ioutil.ReadFile(viper.GetString("config"))
if err != nil {
logrus.WithFields(logrus.Fields{
"file": viper.GetString("config"),
"error": err,
}).Fatal("Failed to read configuration")
}
// server mode
sc, err := parseServerConfig(cbs)
if err != nil {
logrus.WithFields(logrus.Fields{
"file": viper.GetString("config"),
"error": err,
}).Fatal("Failed to parse server configuration")
}
server(sc)
},
}
// fakeFlags replace the old flag format with the new format(eg: `-config` ->> `--config`)
func fakeFlags() {
var args []string
fr, _ := regexp.Compile(`^-[a-zA-Z]{2,}`)
for _, arg := range os.Args {
if fr.MatchString(arg) {
args = append(args, "-"+arg)
} else {
args = append(args, arg)
}
}
os.Args = args
}
func init() {
// compatible with old flag format
fakeFlags()
// compatible windows double click
cobra.MousetrapHelpText = ""
// disable cmd sorting
cobra.EnableCommandSorting = false
// add global flags
rootCmd.PersistentFlags().StringP("config", "c", "./config.json", "config file")
rootCmd.PersistentFlags().String("mmdb-url", "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb", "mmdb download url")
rootCmd.PersistentFlags().String("log-level", "debug", "log level")
rootCmd.PersistentFlags().String("log-timestamp", time.RFC3339, "log timestamp format")
rootCmd.PersistentFlags().String("log-format", "txt", "log output format (txt/json)")
rootCmd.PersistentFlags().Uint("log-ipv4-mask", 0, "mask IPv4 addresses in log using a CIDR mask")
rootCmd.PersistentFlags().Uint("log-ipv6-mask", 0, "mask IPv6 addresses in log using a CIDR mask")
rootCmd.PersistentFlags().Bool("no-check", false, "disable update check")
rootCmd.PersistentFlags().Bool("license", false, "show license and exit")
// add to root cmd
rootCmd.AddCommand(clientCmd, serverCmd, completionCmd)
// bind flag
_ = viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
_ = viper.BindPFlag("mmdb-url", rootCmd.PersistentFlags().Lookup("mmdb-url"))
_ = viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level"))
_ = viper.BindPFlag("log-timestamp", rootCmd.PersistentFlags().Lookup("log-timestamp"))
_ = viper.BindPFlag("log-format", rootCmd.PersistentFlags().Lookup("log-format"))
_ = viper.BindPFlag("log-ipv4-mask", rootCmd.PersistentFlags().Lookup("log-ipv4-mask"))
_ = viper.BindPFlag("log-ipv6-mask", rootCmd.PersistentFlags().Lookup("log-ipv6-mask"))
_ = viper.BindPFlag("no-check", rootCmd.PersistentFlags().Lookup("no-check"))
_ = viper.BindPFlag("license", rootCmd.PersistentFlags().Lookup("license"))
// bind env
_ = viper.BindEnv("config", "HYSTERIA_CONFIG")
_ = viper.BindEnv("mmdb-url", "HYSTERIA_MMDB_URL")
_ = viper.BindEnv("log-level", "HYSTERIA_LOG_LEVEL", "LOGGING_LEVEL")
_ = viper.BindEnv("log-timestamp", "HYSTERIA_LOG_TIMESTAMP", "LOGGING_TIMESTAMP_FORMAT")
_ = viper.BindEnv("log-format", "HYSTERIA_LOG_FORMAT", "LOGGING_FORMATTER")
_ = viper.BindEnv("log-ipv4-mask", "HYSTERIA_LOG_IPV4_MASK", "LOGGING_IPV4_MASK")
_ = viper.BindEnv("log-ipv6-mask", "HYSTERIA_LOG_IPV6_MASK", "LOGGING_IPV6_MASK")
_ = viper.BindEnv("no-check", "HYSTERIA_NO_CHECK", "HYSTERIA_NO_CHECK_UPDATE")
viper.AutomaticEnv()
}
func main() {
cobra.CheckErr(rootCmd.Execute())
}

49
app/cmd/mmdb.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"io"
"net/http"
"os"
"github.com/oschwald/geoip2-golang"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
func downloadMMDB(filename string) error {
resp, err := http.Get(viper.GetString("mmdb-url"))
if err != nil {
return err
}
defer resp.Body.Close()
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
return err
}
func loadMMDBReader(filename string) (*geoip2.Reader, error) {
if _, err := os.Stat(filename); err != nil {
if os.IsNotExist(err) {
logrus.Info("GeoLite2 database not found, downloading...")
if err := downloadMMDB(filename); err != nil {
return nil, err
}
logrus.WithFields(logrus.Fields{
"file": filename,
}).Info("GeoLite2 database downloaded")
return geoip2.Open(filename)
} else {
// some other error
return nil, err
}
} else {
// file exists, just open it
return geoip2.Open(filename)
}
}

View file

@ -1,63 +0,0 @@
package cmd
import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/apernet/hysteria/core/v2/client"
)
// pingCmd represents the ping command
var pingCmd = &cobra.Command{
Use: "ping address",
Short: "Ping mode",
Long: "Perform a TCP ping to a specified remote address through the proxy server. Can be used as a simple connectivity test.",
Run: runPing,
}
func init() {
rootCmd.AddCommand(pingCmd)
}
func runPing(cmd *cobra.Command, args []string) {
logger.Info("ping mode")
if len(args) != 1 {
logger.Fatal("must specify one and only one address")
}
addr := args[0]
if err := viper.ReadInConfig(); err != nil {
logger.Fatal("failed to read client config", zap.Error(err))
}
var config clientConfig
if err := viper.Unmarshal(&config); err != nil {
logger.Fatal("failed to parse client config", zap.Error(err))
}
hyConfig, err := config.Config()
if err != nil {
logger.Fatal("failed to load client config", zap.Error(err))
}
c, info, err := client.NewClient(hyConfig)
if err != nil {
logger.Fatal("failed to initialize client", zap.Error(err))
}
defer c.Close()
logger.Info("connected to server",
zap.Bool("udpEnabled", info.UDPEnabled),
zap.Uint64("tx", info.Tx))
logger.Info("connecting", zap.String("addr", addr))
start := time.Now()
conn, err := c.TCP(addr)
if err != nil {
logger.Fatal("failed to connect", zap.Error(err), zap.String("time", time.Since(start).String()))
}
defer conn.Close()
logger.Info("connected", zap.String("time", time.Since(start).String()))
}

123
app/cmd/resolver.go Normal file
View file

@ -0,0 +1,123 @@
package main
import (
"crypto/tls"
"errors"
"net"
"net/url"
"strings"
"github.com/apernet/hysteria/core/utils"
rdns "github.com/folbricht/routedns"
)
var errInvalidSyntax = errors.New("invalid syntax")
func setResolver(dns string) error {
if net.ParseIP(dns) != nil {
// Just an IP address, treat as UDP 53
dns = "udp://" + net.JoinHostPort(dns, "53")
}
var r rdns.Resolver
if strings.HasPrefix(dns, "udp://") {
// Standard UDP DNS resolver
dns = strings.TrimPrefix(dns, "udp://")
if dns == "" {
return errInvalidSyntax
}
if _, _, err := utils.SplitHostPort(dns); err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "53")
}
client, err := rdns.NewDNSClient("dns-udp", dns, "udp", rdns.DNSClientOptions{})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "tcp://") {
// Standard TCP DNS resolver
dns = strings.TrimPrefix(dns, "tcp://")
if dns == "" {
return errInvalidSyntax
}
if _, _, err := utils.SplitHostPort(dns); err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "53")
}
client, err := rdns.NewDNSClient("dns-tcp", dns, "tcp", rdns.DNSClientOptions{})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "https://") {
// DoH resolver
if dohURL, err := url.Parse(dns); err != nil {
return err
} else {
// Need to set bootstrap address to avoid loopback DNS lookup
dohIPAddr, err := net.ResolveIPAddr("ip", dohURL.Hostname())
if err != nil {
return err
}
client, err := rdns.NewDoHClient("doh", dns, rdns.DoHClientOptions{
BootstrapAddr: dohIPAddr.String(),
})
if err != nil {
return err
}
r = client
}
} else if strings.HasPrefix(dns, "tls://") {
// DoT resolver
dns = strings.TrimPrefix(dns, "tls://")
if dns == "" {
return errInvalidSyntax
}
dotHost, _, err := utils.SplitHostPort(dns)
if err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "853")
}
// Need to set bootstrap address to avoid loopback DNS lookup
dotIPAddr, err := net.ResolveIPAddr("ip", dotHost)
if err != nil {
return err
}
client, err := rdns.NewDoTClient("dot", dns, rdns.DoTClientOptions{
BootstrapAddr: dotIPAddr.String(),
TLSConfig: new(tls.Config),
})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "quic://") {
// DoQ resolver
dns = strings.TrimPrefix(dns, "quic://")
if dns == "" {
return errInvalidSyntax
}
doqHost, _, err := utils.SplitHostPort(dns)
if err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "853")
}
// Need to set bootstrap address to avoid loopback DNS lookup
doqIPAddr, err := net.ResolveIPAddr("ip", doqHost)
if err != nil {
return err
}
client, err := rdns.NewDoQClient("doq", dns, rdns.DoQClientOptions{
BootstrapAddr: doqIPAddr.String(),
})
if err != nil {
return err
}
r = client
} else {
return errInvalidSyntax
}
cache := rdns.NewCache("cache", r, rdns.CacheOptions{})
net.DefaultResolver = rdns.NewNetResolver(cache)
return nil
}

View file

@ -1,176 +0,0 @@
package cmd
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
appLogo = `
`
appDesc = "a powerful, lightning fast and censorship resistant proxy"
appAuthors = "Aperture Internet Laboratory <https://github.com/apernet>"
appLogLevelEnv = "HYSTERIA_LOG_LEVEL"
appLogFormatEnv = "HYSTERIA_LOG_FORMAT"
appDisableUpdateCheckEnv = "HYSTERIA_DISABLE_UPDATE_CHECK"
appACMEDirEnv = "HYSTERIA_ACME_DIR"
)
var (
// These values will be injected by the build system
appVersion = "Unknown"
appDate = "Unknown"
appType = "Unknown" // aka channel
appToolchain = "Unknown"
appCommit = "Unknown"
appPlatform = "Unknown"
appArch = "Unknown"
libVersion = "Unknown"
appVersionLong = fmt.Sprintf("Version:\t%s\n"+
"BuildDate:\t%s\n"+
"BuildType:\t%s\n"+
"Toolchain:\t%s\n"+
"CommitHash:\t%s\n"+
"Platform:\t%s\n"+
"Architecture:\t%s\n"+
"Libraries:\tquic-go=%s",
appVersion, appDate, appType, appToolchain, appCommit, appPlatform, appArch, libVersion)
appAboutLong = fmt.Sprintf("%s\n%s\n%s\n\n%s", appLogo, appDesc, appAuthors, appVersionLong)
)
var logger *zap.Logger
// Flags
var (
cfgFile string
logLevel string
logFormat string
disableUpdateCheck bool
)
var rootCmd = &cobra.Command{
Use: "hysteria",
Short: appDesc,
Long: appAboutLong,
Run: runClient, // Default to client mode
}
var logLevelMap = map[string]zapcore.Level{
"debug": zapcore.DebugLevel,
"info": zapcore.InfoLevel,
"warn": zapcore.WarnLevel,
"error": zapcore.ErrorLevel,
}
var logFormatMap = map[string]zapcore.EncoderConfig{
"console": {
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
MessageKey: "msg",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.RFC3339TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
},
"json": {
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
MessageKey: "msg",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochMillisTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
},
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
initFlags()
cobra.MousetrapHelpText = "" // Disable the mousetrap so Windows users can run the exe directly by double-clicking
cobra.OnInitialize(initConfig)
cobra.OnInitialize(initLogger) // initLogger must come after initConfig as it depends on config
}
func initFlags() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", envOrDefaultString(appLogLevelEnv, "info"), "log level")
rootCmd.PersistentFlags().StringVarP(&logFormat, "log-format", "f", envOrDefaultString(appLogFormatEnv, "console"), "log format")
rootCmd.PersistentFlags().BoolVar(&disableUpdateCheck, "disable-update-check", envOrDefaultBool(appDisableUpdateCheckEnv, false), "disable update check")
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.SupportedExts = append([]string{"yaml", "yml"}, viper.SupportedExts...)
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.hysteria")
viper.AddConfigPath("/etc/hysteria/")
}
}
func initLogger() {
level, ok := logLevelMap[strings.ToLower(logLevel)]
if !ok {
fmt.Printf("unsupported log level: %s\n", logLevel)
os.Exit(1)
}
enc, ok := logFormatMap[strings.ToLower(logFormat)]
if !ok {
fmt.Printf("unsupported log format: %s\n", logFormat)
os.Exit(1)
}
c := zap.Config{
Level: zap.NewAtomicLevelAt(level),
DisableCaller: true,
DisableStacktrace: true,
Encoding: strings.ToLower(logFormat),
EncoderConfig: enc,
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
var err error
logger, err = c.Build()
if err != nil {
fmt.Printf("failed to initialize logger: %s\n", err)
os.Exit(1)
}
}
func envOrDefaultString(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
func envOrDefaultBool(key string, def bool) bool {
if v := os.Getenv(key); v != "" {
b, _ := strconv.ParseBool(v)
return b
}
return def
}

File diff suppressed because it is too large Load diff

View file

@ -1,189 +0,0 @@
package cmd
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/spf13/viper"
)
// TestServerConfig tests the parsing of the server config
func TestServerConfig(t *testing.T) {
viper.SetConfigFile("server_test.yaml")
err := viper.ReadInConfig()
assert.NoError(t, err)
var config serverConfig
err = viper.Unmarshal(&config)
assert.NoError(t, err)
assert.Equal(t, config, serverConfig{
Listen: ":8443",
Obfs: serverConfigObfs{
Type: "salamander",
Salamander: serverConfigObfsSalamander{
Password: "cry_me_a_r1ver",
},
},
TLS: &serverConfigTLS{
Cert: "some.crt",
Key: "some.key",
SNIGuard: "strict",
},
ACME: &serverConfigACME{
Domains: []string{
"sub1.example.com",
"sub2.example.com",
},
Email: "haha@cringe.net",
CA: "zero",
ListenHost: "127.0.0.9",
Dir: "random_dir",
Type: "dns",
HTTP: serverConfigACMEHTTP{
AltPort: 8888,
},
TLS: serverConfigACMETLS{
AltPort: 44333,
},
DNS: serverConfigACMEDNS{
Name: "gomommy",
Config: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
DisableHTTP: true,
DisableTLSALPN: true,
AltHTTPPort: 8080,
AltTLSALPNPort: 4433,
},
QUIC: serverConfigQUIC{
InitStreamReceiveWindow: 77881,
MaxStreamReceiveWindow: 77882,
InitConnectionReceiveWindow: 77883,
MaxConnectionReceiveWindow: 77884,
MaxIdleTimeout: 999 * time.Second,
MaxIncomingStreams: 256,
DisablePathMTUDiscovery: true,
},
Bandwidth: serverConfigBandwidth{
Up: "500 mbps",
Down: "100 mbps",
},
IgnoreClientBandwidth: true,
SpeedTest: true,
DisableUDP: true,
UDPIdleTimeout: 120 * time.Second,
Auth: serverConfigAuth{
Type: "password",
Password: "goofy_ahh_password",
UserPass: map[string]string{
"yolo": "swag",
"lol": "kek",
"foo": "bar",
},
HTTP: serverConfigAuthHTTP{
URL: "http://127.0.0.1:5000/auth",
Insecure: true,
},
Command: "/etc/some_command",
},
Resolver: serverConfigResolver{
Type: "udp",
TCP: serverConfigResolverTCP{
Addr: "123.123.123.123:5353",
Timeout: 4 * time.Second,
},
UDP: serverConfigResolverUDP{
Addr: "4.6.8.0:53",
Timeout: 2 * time.Second,
},
TLS: serverConfigResolverTLS{
Addr: "dot.yolo.com:8853",
Timeout: 10 * time.Second,
SNI: "server1.yolo.net",
Insecure: true,
},
HTTPS: serverConfigResolverHTTPS{
Addr: "cringe.ahh.cc",
Timeout: 5 * time.Second,
SNI: "real.stuff.net",
Insecure: true,
},
},
Sniff: serverConfigSniff{
Enable: true,
Timeout: 1 * time.Second,
RewriteDomain: true,
TCPPorts: "80,443,1000-2000",
UDPPorts: "443",
},
ACL: serverConfigACL{
File: "chnroute.txt",
Inline: []string{
"lmao(ok)",
"kek(cringe,boba,tea)",
},
GeoIP: "some.dat",
GeoSite: "some_site.dat",
GeoUpdateInterval: 168 * time.Hour,
},
Outbounds: []serverConfigOutboundEntry{
{
Name: "goodstuff",
Type: "direct",
Direct: serverConfigOutboundDirect{
Mode: "64",
BindIPv4: "2.4.6.8",
BindIPv6: "0:0:0:0:0:ffff:0204:0608",
BindDevice: "eth233",
FastOpen: true,
},
},
{
Name: "badstuff",
Type: "socks5",
SOCKS5: serverConfigOutboundSOCKS5{
Addr: "shady.proxy.ru:1080",
Username: "hackerman",
Password: "Elliot Alderson",
},
},
{
Name: "weirdstuff",
Type: "http",
HTTP: serverConfigOutboundHTTP{
URL: "https://eyy.lmao:4443/goofy",
Insecure: true,
},
},
},
TrafficStats: serverConfigTrafficStats{
Listen: ":9999",
Secret: "its_me_mario",
},
Masquerade: serverConfigMasquerade{
Type: "proxy",
File: serverConfigMasqueradeFile{
Dir: "/www/masq",
},
Proxy: serverConfigMasqueradeProxy{
URL: "https://some.site.net",
RewriteHost: true,
Insecure: true,
},
String: serverConfigMasqueradeString{
Content: "aint nothin here",
Headers: map[string]string{
"content-type": "text/plain",
"custom-haha": "lol",
},
StatusCode: 418,
},
ListenHTTP: ":80",
ListenHTTPS: ":443",
ForceHTTPS: true,
},
})
}

View file

@ -1,144 +0,0 @@
listen: :8443
obfs:
type: salamander
salamander:
password: cry_me_a_r1ver
tls:
cert: some.crt
key: some.key
sniGuard: strict
acme:
domains:
- sub1.example.com
- sub2.example.com
email: haha@cringe.net
ca: zero
listenHost: 127.0.0.9
dir: random_dir
type: dns
http:
altPort: 8888
tls:
altPort: 44333
dns:
name: gomommy
config:
key1: value1
key2: value2
disableHTTP: true
disableTLSALPN: true
altHTTPPort: 8080
altTLSALPNPort: 4433
quic:
initStreamReceiveWindow: 77881
maxStreamReceiveWindow: 77882
initConnReceiveWindow: 77883
maxConnReceiveWindow: 77884
maxIdleTimeout: 999s
maxIncomingStreams: 256
disablePathMTUDiscovery: true
bandwidth:
up: 500 mbps
down: 100 mbps
ignoreClientBandwidth: true
speedTest: true
disableUDP: true
udpIdleTimeout: 120s
auth:
type: password
password: goofy_ahh_password
userpass:
yolo: swag
lol: kek
foo: bar
http:
url: http://127.0.0.1:5000/auth
insecure: true
command: /etc/some_command
resolver:
type: udp
tcp:
addr: 123.123.123.123:5353
timeout: 4s
udp:
addr: 4.6.8.0:53
timeout: 2s
tls:
addr: dot.yolo.com:8853
timeout: 10s
sni: server1.yolo.net
insecure: true
https:
addr: cringe.ahh.cc
timeout: 5s
sni: real.stuff.net
insecure: true
sniff:
enable: true
timeout: 1s
rewriteDomain: true
tcpPorts: 80,443,1000-2000
udpPorts: 443
acl:
file: chnroute.txt
inline:
- lmao(ok)
- kek(cringe,boba,tea)
geoip: some.dat
geosite: some_site.dat
geoUpdateInterval: 168h
outbounds:
- name: goodstuff
type: direct
direct:
mode: 64
bindIPv4: 2.4.6.8
bindIPv6: 0:0:0:0:0:ffff:0204:0608
bindDevice: eth233
fastOpen: true
- name: badstuff
type: socks5
socks5:
addr: shady.proxy.ru:1080
username: hackerman
password: Elliot Alderson
- name: weirdstuff
type: http
http:
url: https://eyy.lmao:4443/goofy
insecure: true
trafficStats:
listen: :9999
secret: its_me_mario
masquerade:
type: proxy
file:
dir: /www/masq
proxy:
url: https://some.site.net
rewriteHost: true
insecure: true
string:
content: aint nothin here
headers:
content-type: text/plain
custom-haha: lol
statusCode: 418
listenHTTP: :80
listenHTTPS: :443
forceHTTPS: true

View file

@ -1,55 +0,0 @@
package cmd
import (
"fmt"
"github.com/apernet/hysteria/app/v2/internal/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)
var (
noText bool
withQR bool
)
// shareCmd represents the share command
var shareCmd = &cobra.Command{
Use: "share",
Short: "Generate share URI",
Long: "Generate a hysteria2:// URI from a client config for sharing",
Run: runShare,
}
func init() {
initShareFlags()
rootCmd.AddCommand(shareCmd)
}
func initShareFlags() {
shareCmd.Flags().BoolVar(&noText, "notext", false, "do not show URI as text")
shareCmd.Flags().BoolVar(&withQR, "qr", false, "show URI as QR code")
}
func runShare(cmd *cobra.Command, args []string) {
if err := viper.ReadInConfig(); err != nil {
logger.Fatal("failed to read client config", zap.Error(err))
}
var config clientConfig
if err := viper.Unmarshal(&config); err != nil {
logger.Fatal("failed to parse client config", zap.Error(err))
}
if _, err := config.Config(); err != nil {
logger.Fatal("failed to load client config", zap.Error(err))
}
u := config.URI()
if !noText {
fmt.Println(u)
}
if withQR {
utils.PrintQR(u)
}
}

View file

@ -1,178 +0,0 @@
package cmd
import (
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/apernet/hysteria/core/v2/client"
hyErrors "github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/extras/v2/outbounds"
"github.com/apernet/hysteria/extras/v2/outbounds/speedtest"
)
var (
skipDownload bool
skipUpload bool
dataSize uint32
useBytes bool
speedtestAddr = fmt.Sprintf("%s:%d", outbounds.SpeedtestDest, 0)
)
// speedtestCmd represents the speedtest command
var speedtestCmd = &cobra.Command{
Use: "speedtest",
Short: "Speed test mode",
Long: "Perform a speed test through the proxy server. The server must have speed test support enabled.",
Run: runSpeedtest,
}
func init() {
initSpeedtestFlags()
rootCmd.AddCommand(speedtestCmd)
}
func initSpeedtestFlags() {
speedtestCmd.Flags().BoolVar(&skipDownload, "skip-download", false, "Skip download test")
speedtestCmd.Flags().BoolVar(&skipUpload, "skip-upload", false, "Skip upload test")
speedtestCmd.Flags().Uint32Var(&dataSize, "data-size", 1024*1024*100, "Data size for download and upload tests")
speedtestCmd.Flags().BoolVar(&useBytes, "use-bytes", false, "Use bytes per second instead of bits per second")
}
func runSpeedtest(cmd *cobra.Command, args []string) {
logger.Info("speed test mode")
if err := viper.ReadInConfig(); err != nil {
logger.Fatal("failed to read client config", zap.Error(err))
}
var config clientConfig
if err := viper.Unmarshal(&config); err != nil {
logger.Fatal("failed to parse client config", zap.Error(err))
}
hyConfig, err := config.Config()
if err != nil {
logger.Fatal("failed to load client config", zap.Error(err))
}
c, info, err := client.NewClient(hyConfig)
if err != nil {
logger.Fatal("failed to initialize client", zap.Error(err))
}
defer c.Close()
logger.Info("connected to server",
zap.Bool("udpEnabled", info.UDPEnabled),
zap.Uint64("tx", info.Tx))
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(signalChan)
runChan := make(chan struct{}, 1)
go func() {
if !skipDownload {
runDownloadTest(c)
}
if !skipUpload {
runUploadTest(c)
}
runChan <- struct{}{}
}()
select {
case <-signalChan:
logger.Info("received signal, shutting down gracefully")
case <-runChan:
logger.Info("speed test complete")
}
}
func runDownloadTest(c client.Client) {
logger.Info("performing download test")
downConn, err := c.TCP(speedtestAddr)
if err != nil {
if errors.As(err, &hyErrors.DialError{}) {
logger.Fatal("failed to connect (server may not support speed test)", zap.Error(err))
} else {
logger.Fatal("failed to connect", zap.Error(err))
}
}
defer downConn.Close()
downClient := &speedtest.Client{Conn: downConn}
currentTotal := uint32(0)
err = downClient.Download(dataSize, func(d time.Duration, b uint32, done bool) {
if !done {
currentTotal += b
logger.Info("downloading",
zap.Uint32("bytes", b),
zap.String("progress", fmt.Sprintf("%.2f%%", float64(currentTotal)/float64(dataSize)*100)),
zap.String("speed", formatSpeed(b, d, useBytes)))
} else {
logger.Info("download complete",
zap.Uint32("bytes", b),
zap.String("speed", formatSpeed(b, d, useBytes)))
}
})
if err != nil {
logger.Fatal("download test failed", zap.Error(err))
}
logger.Info("download test complete")
}
func runUploadTest(c client.Client) {
logger.Info("performing upload test")
upConn, err := c.TCP(speedtestAddr)
if err != nil {
if errors.As(err, &hyErrors.DialError{}) {
logger.Fatal("failed to connect (server may not support speed test)", zap.Error(err))
} else {
logger.Fatal("failed to connect", zap.Error(err))
}
}
defer upConn.Close()
upClient := &speedtest.Client{Conn: upConn}
currentTotal := uint32(0)
err = upClient.Upload(dataSize, func(d time.Duration, b uint32, done bool) {
if !done {
currentTotal += b
logger.Info("uploading",
zap.Uint32("bytes", b),
zap.String("progress", fmt.Sprintf("%.2f%%", float64(currentTotal)/float64(dataSize)*100)),
zap.String("speed", formatSpeed(b, d, useBytes)))
} else {
logger.Info("upload complete",
zap.Uint32("bytes", b),
zap.String("speed", formatSpeed(b, d, useBytes)))
}
})
if err != nil {
logger.Fatal("upload test failed", zap.Error(err))
}
logger.Info("upload test complete")
}
func formatSpeed(bytes uint32, duration time.Duration, useBytes bool) string {
speed := float64(bytes) / duration.Seconds()
var units []string
if useBytes {
units = []string{"B/s", "KB/s", "MB/s", "GB/s"}
} else {
units = []string{"bps", "Kbps", "Mbps", "Gbps"}
speed *= 8
}
unitIndex := 0
for speed > 1000 && unitIndex < len(units)-1 {
speed /= 1000
unitIndex++
}
return fmt.Sprintf("%.2f %s", speed, units[unitIndex])
}

View file

@ -1,88 +1,49 @@
package cmd package main
import ( import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"time" "time"
"github.com/spf13/cobra" "github.com/sirupsen/logrus"
"go.uber.org/zap"
"github.com/apernet/hysteria/app/v2/internal/utils"
"github.com/apernet/hysteria/core/v2/client"
) )
const ( const githubAPIURL = "https://api.github.com/repos/apernet/hysteria/releases/latest"
updateCheckInterval = 24 * time.Hour
)
// checkUpdateCmd represents the checkUpdate command type releaseInfo struct {
var checkUpdateCmd = &cobra.Command{ URL string `json:"html_url"`
Use: "check-update", TagName string `json:"tag_name"`
Short: "Check for updates", CreatedAt string `json:"created_at"`
Long: "Check for updates.", PublishedAt string `json:"published_at"`
Run: runCheckUpdate,
} }
func init() { func checkUpdate() {
rootCmd.AddCommand(checkUpdateCmd) sv := strings.Split(appVersion, "-")[0]
info, err := fetchLatestRelease()
if err == nil && info.TagName != sv {
logrus.WithFields(logrus.Fields{
"version": info.TagName,
"url": info.URL,
}).Info("New version available")
}
} }
func runCheckUpdate(cmd *cobra.Command, args []string) { func fetchLatestRelease() (*releaseInfo, error) {
logger.Info("checking for updates", hc := &http.Client{
zap.String("version", appVersion), Timeout: time.Second * 20,
zap.String("platform", appPlatform), }
zap.String("arch", appArch), resp, err := hc.Get(githubAPIURL)
zap.String("channel", appType),
)
checker := utils.NewServerUpdateChecker(appVersion, appPlatform, appArch, appType)
resp, err := checker.Check()
if err != nil { if err != nil {
logger.Fatal("failed to check for updates", zap.Error(err)) return nil, err
} }
if resp.HasUpdate { defer resp.Body.Close()
logger.Info("update available", body, err := ioutil.ReadAll(resp.Body)
zap.String("version", resp.LatestVersion), if err != nil {
zap.String("url", resp.URL), return nil, err
zap.Bool("urgent", resp.Urgent),
)
} else {
logger.Info("no update available")
}
}
// runCheckUpdateServer is the background update checking routine for server mode
func runCheckUpdateServer() {
checker := utils.NewServerUpdateChecker(appVersion, appPlatform, appArch, appType)
checkUpdateRoutine(checker)
}
// runCheckUpdateClient is the background update checking routine for client mode
func runCheckUpdateClient(hyClient client.Client) {
checker := utils.NewClientUpdateChecker(appVersion, appPlatform, appArch, appType, hyClient)
checkUpdateRoutine(checker)
}
func checkUpdateRoutine(checker *utils.UpdateChecker) {
ticker := time.NewTicker(updateCheckInterval)
for {
logger.Debug("checking for updates",
zap.String("version", appVersion),
zap.String("platform", appPlatform),
zap.String("arch", appArch),
zap.String("channel", appType),
)
resp, err := checker.Check()
if err != nil {
logger.Debug("failed to check for updates", zap.Error(err))
} else if resp.HasUpdate {
logger.Info("update available",
zap.String("version", resp.LatestVersion),
zap.String("url", resp.URL),
zap.Bool("urgent", resp.Urgent),
)
} else {
logger.Debug("no update available")
}
<-ticker.C
} }
var info releaseInfo
err = json.Unmarshal(body, &info)
return &info, err
} }

View file

@ -1,23 +0,0 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "Show version",
Long: "Show version.",
Run: runVersion,
}
func init() {
rootCmd.AddCommand(versionCmd)
}
func runVersion(cmd *cobra.Command, args []string) {
fmt.Println(appAboutLong)
}

View file

@ -1,92 +1,99 @@
module github.com/apernet/hysteria/app/v2 module github.com/apernet/hysteria/app
go 1.23 go 1.18
toolchain go1.24.2
require ( require (
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000 github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/apernet/hysteria/extras/v2 v2.0.0-00010101000000-000000000000 github.com/apernet/hysteria/core v0.0.0-00010101000000-000000000000
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad
github.com/caddyserver/certmagic v0.17.2 github.com/caddyserver/certmagic v0.17.2
github.com/libdns/cloudflare v0.1.1 github.com/docker/go-units v0.5.0
github.com/libdns/duckdns v0.2.0 github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819
github.com/libdns/gandi v1.0.3 github.com/elazarl/goproxy/ext v0.0.0-20221015165544-a0805db90819
github.com/libdns/godaddy v1.0.3 github.com/folbricht/routedns v0.1.6-0.20220806202012-361f5b35b4c3
github.com/libdns/namedotcom v0.3.3 github.com/fsnotify/fsnotify v1.6.0
github.com/libdns/vultr v1.0.0 github.com/lucas-clemente/quic-go v0.31.0
github.com/mdp/qrterminal/v3 v3.1.1 github.com/oschwald/geoip2-golang v1.8.0
github.com/mholt/acmez v1.0.4 github.com/prometheus/client_golang v1.14.0
github.com/sagernet/sing v0.3.2 github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0 github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.9.0 github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 github.com/xjasonlyu/tun2socks/v2 v2.4.1
go.uber.org/zap v1.24.0 github.com/yosuke-furukawa/json5 v0.1.1
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 go.uber.org/zap v1.23.0
golang.org/x/sys v0.25.0 gvisor.dev/gvisor v0.0.0-20220405222207-795f4f0139bb
) )
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 // indirect
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cloudflare/circl v1.3.9 // indirect github.com/coreos/go-iptables v0.6.0 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/database64128/tfo-go/v2 v2.2.2 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/btree v1.0.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/gopacket v1.1.19 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.6 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/jtacoma/uritemplates v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/miekg/dns v1.1.59 // indirect github.com/magiconair/properties v1.8.6 // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mholt/acmez v1.0.4 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pion/dtls/v2 v2.1.3 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport v0.13.0 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/refraction-networking/utls v1.6.6 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/spf13/afero v1.9.2 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.4.1 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
github.com/vultr/govultr/v3 v3.6.4 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/mock v0.5.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/crypto v0.26.0 // indirect golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 // indirect
golang.org/x/net v0.28.0 // indirect golang.org/x/text v0.4.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/tools v0.1.12 // indirect
golang.org/x/text v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
golang.org/x/tools v0.22.0 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/protobuf v1.34.1 // indirect golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
) )
replace github.com/apernet/hysteria/core/v2 => ../core replace github.com/apernet/hysteria/core => ../core/
replace github.com/apernet/hysteria/extras/v2 => ../extras replace github.com/lucas-clemente/quic-go => github.com/apernet/quic-go v0.31.1-0.20221126080508-c4a37bf8f6d7
replace github.com/LiamHaworth/go-tproxy => github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88
replace github.com/elazarl/goproxy => github.com/apernet/goproxy v0.0.0-20221124043924-155acfaf278f

View file

@ -38,59 +38,77 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 h1:vX+gnvBc56EbWYrmlhYbFYRaeikAke1GL84N4BEYOFE=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ=
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f h1:uVh0qpEslrWjgzx9vOcyCqsOY3c9kofDZ1n+qaw35ZY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f/go.mod h1:xkkq9D4ygcldQQhKS/w9CadiCKwCngU7K9E3DaKahpM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 h1:9/jM7e+kVALd7Jfu1c27dcEpT/Fd/Gzq2OsQjKjakKI= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431/go.mod h1:I/47OIGG5H/IfAm+nz2c6hm6b/NkEhpvptAoiPcY7jQ= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad h1:QzQ2sKpc9o42HNRR8ukM5uMC/RzR2HgZd/Nvaqol2C0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad/go.mod h1:S5IydyLSN/QAfvY+r2GoomPJ6hidtXWm/Ad18sJVssk= github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0= github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88 h1:YNsl7PMiU9x/0CleMHJ7GUdS8y1aRTFwTxdSmLLEijQ=
github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88/go.mod h1:uxH+nFzlJug5OHjPYmzKwvVVb9wOToeGuLNVeerwWtc=
github.com/apernet/goproxy v0.0.0-20221124043924-155acfaf278f h1:v3Bn97M5KWzdVajNphf3PxoHdsRF/RzBVovIsH/DEvY=
github.com/apernet/goproxy v0.0.0-20221124043924-155acfaf278f/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/apernet/quic-go v0.31.1-0.20221126080508-c4a37bf8f6d7 h1:wvamuH2V9V2JP60DMYrsn3k9nNkcXHttV4KQ8W3Ah1Y=
github.com/apernet/quic-go v0.31.1-0.20221126080508-c4a37bf8f6d7/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE= github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE= github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/elazarl/goproxy/ext v0.0.0-20221015165544-a0805db90819 h1:PBc3oUutXxwCibSLQCmpunGvruDnoS6kdnaL7a0xwKY=
github.com/elazarl/goproxy/ext v0.0.0-20221015165544-a0805db90819/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/folbricht/routedns v0.1.6-0.20220806202012-361f5b35b4c3 h1:5SBA5PxbbVW2CnI5eZ3E0MDnCLiik4em0LfxtkN9gsU=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/folbricht/routedns v0.1.6-0.20220806202012-361f5b35b4c3/go.mod h1:K34wIQLA7ouMpm4H5xs5BLzWYq4vtwrwoH4nU0GlAsw=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -102,6 +120,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -116,10 +136,13 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -129,10 +152,11 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -146,121 +170,152 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM=
github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtacoma/uritemplates v1.0.0 h1:xwx5sBF7pPAb0Uj8lDC1Q/aBPpOFyQza7OC705ZlLCo=
github.com/jtacoma/uritemplates v1.0.0/go.mod h1:IhIICdE9OcvgUnGwTtJxgBQ+VrTrti5PcbLVSJianO8=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/libdns/duckdns v0.2.0 h1:vd3pE09G2qTx1Zh1o3LmrivWSByD3Z5FbL7csX5vDgE= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/libdns/duckdns v0.2.0/go.mod h1:jCQ/7+qvhLK39+28qXvKEYGBBvmHBCmIwNqdJTCUmVs= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/libdns/gandi v1.0.3 h1:FIvipWOg/O4zi75fPRmtcolRKqI6MgrbpFy2p5KYdUk= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/libdns/gandi v1.0.3/go.mod h1:G6dw58Xnji2xX+lb+uZxGbtmfxKllm1CGHE2bOPG3WA= github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
github.com/libdns/godaddy v1.0.3 h1:PX1FOYDQ1HGQzz8mVOmtwm3aa6Sv5MwCkNzivUUTA44= github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
github.com/libdns/godaddy v1.0.3/go.mod h1:vuKWUXnvblDvcaiRwutOoLl7DuB21x8tI06owsF/JTM= github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/libdns/vultr v1.0.0 h1:W8B4+k2bm9ro3bZLSZV9hMOQI+uO6Svu+GmD+Olz7ZI=
github.com/libdns/vultr v1.0.0/go.mod h1:8K1HJExcbeHS4YPkFHRZpqpXZzZ+DZAA0m0VikJgEqk=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdp/qrterminal/v3 v3.1.1 h1:cIPwg3QU0OIm9+ce/lRfWXhPwEjOSKwk3HBwL3HBTyc=
github.com/mdp/qrterminal/v3 v3.1.1/go.mod h1:5lJlXe7Jdr8wlPDdcsJttv1/knsRgzXASyr4dcGZqNU=
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80= github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY= github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -269,25 +324,24 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM= github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a h1:BOqgJ4jku0LHPDoR51RD8Mxmo0LHxCzJT/M9MemYdHo=
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM= github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
github.com/vultr/govultr/v3 v3.6.4 h1:unvY9eXlBw667ECQZDbBDOIaWB8wkk6Bx+yB0IMKXJ4= github.com/xjasonlyu/tun2socks/v2 v2.4.1 h1:56wNC5f4TOfdZ4aXPhpdlTwwRXlLq/r3kEEOa354h78=
github.com/vultr/govultr/v3 v3.6.4/go.mod h1:rt9v2x114jZmmLAE/h5N5jnxTmsK9ewwS2oQZ0UBQzM= github.com/xjasonlyu/tun2socks/v2 v2.4.1/go.mod h1:BM8QVYh5NxLk2s2WW6Csw7uCaKC3RcfqeaunRT4T9Fo=
github.com/yosuke-furukawa/json5 v0.1.1 h1:0F9mNwTvOuDNH243hoPqvf+dxa5QsKnZzU20uNsh3ZI=
github.com/yosuke-furukawa/json5 v0.1.1/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -295,31 +349,27 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -330,8 +380,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -356,12 +406,11 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -369,10 +418,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -389,15 +438,20 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -407,8 +461,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -420,28 +474,27 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -450,6 +503,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -457,34 +512,40 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 h1:E1pm64FqQa4v8dHd/bAneyMkR4hk8LTJhoSlc5mc1cM=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -505,7 +566,6 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -533,15 +593,21 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 h1:kgBK1EGuTIYbwoKROmsoV0FQp08gnCcVa110A4Unqhk=
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -630,21 +696,32 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220405222207-795f4f0139bb h1:AFXQ8bzBy3bvv18rPD8bjDGzylB0PZrMJWQKuXxvR7Y=
gvisor.dev/gvisor v0.0.0-20220405222207-795f4f0139bb/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -653,7 +730,5 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

91
app/http/server.go Normal file
View file

@ -0,0 +1,91 @@
package http
import (
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/apernet/hysteria/core/transport"
"github.com/apernet/hysteria/core/utils"
"github.com/elazarl/goproxy/ext/auth"
"github.com/apernet/hysteria/core/acl"
"github.com/apernet/hysteria/core/cs"
"github.com/elazarl/goproxy"
)
func NewProxyHTTPServer(hyClient *cs.Client, transport *transport.ClientTransport, idleTimeout time.Duration,
aclEngine *acl.Engine,
basicAuthFunc func(user, password string) bool,
newDialFunc func(reqAddr string, action acl.Action, arg string),
proxyErrorFunc func(reqAddr string, err error),
) (*goproxy.ProxyHttpServer, error) {
proxy := goproxy.NewProxyHttpServer()
proxy.Logger = &nopLogger{}
proxy.NonproxyHandler = http.NotFoundHandler()
proxy.Tr = &http.Transport{
Dial: func(network, addr string) (conn net.Conn, err error) {
defer func() {
if err != nil {
proxyErrorFunc(addr, err)
}
}()
// Parse addr string
host, port, err := utils.SplitHostPort(addr)
if err != nil {
return nil, err
}
// ACL
action, arg := acl.ActionProxy, ""
var ipAddr *net.IPAddr
var resErr error
if aclEngine != nil {
action, arg, _, ipAddr, resErr = aclEngine.ResolveAndMatch(host, port, false)
// Doesn't always matter if the resolution fails, as we may send it through HyClient
}
newDialFunc(addr, action, arg)
// Handle according to the action
switch action {
case acl.ActionDirect:
if resErr != nil {
return nil, resErr
}
return transport.DialTCP(&net.TCPAddr{
IP: ipAddr.IP,
Port: int(port),
Zone: ipAddr.Zone,
})
case acl.ActionProxy:
return hyClient.DialTCP(addr)
case acl.ActionBlock:
return nil, errors.New("blocked by ACL")
case acl.ActionHijack:
hijackIPAddr, err := transport.ResolveIPAddr(arg)
if err != nil {
return nil, err
}
return transport.DialTCP(&net.TCPAddr{
IP: hijackIPAddr.IP,
Port: int(port),
Zone: hijackIPAddr.Zone,
})
default:
return nil, fmt.Errorf("unknown action %d", action)
}
},
IdleConnTimeout: idleTimeout,
// Disable HTTP2 support? ref: https://github.com/elazarl/goproxy/issues/361
}
proxy.ConnectDial = nil
if basicAuthFunc != nil {
auth.ProxyBasic(proxy, "hysteria", basicAuthFunc)
}
return proxy, nil
}
type nopLogger struct{}
func (n *nopLogger) Printf(format string, v ...interface{}) {}

View file

@ -1,62 +0,0 @@
package forwarding
import (
"io"
"net"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPTunnel struct {
HyClient client.Client
Remote string
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr net.Addr)
Error(addr net.Addr, err error)
}
func (t *TCPTunnel) Serve(listener net.Listener) error {
for {
conn, err := listener.Accept()
if err != nil {
return err
}
go t.handle(conn)
}
}
func (t *TCPTunnel) handle(conn net.Conn) {
defer conn.Close()
if t.EventLogger != nil {
t.EventLogger.Connect(conn.RemoteAddr())
}
var closeErr error
defer func() {
if t.EventLogger != nil {
t.EventLogger.Error(conn.RemoteAddr(), closeErr)
}
}()
rc, err := t.HyClient.TCP(t.Remote)
if err != nil {
closeErr = err
return
}
defer rc.Close()
// Start forwarding
copyErrChan := make(chan error, 2)
go func() {
_, copyErr := io.Copy(rc, conn)
copyErrChan <- copyErr
}()
go func() {
_, copyErr := io.Copy(conn, rc)
copyErrChan <- copyErr
}()
closeErr = <-copyErrChan
}

View file

@ -1,39 +0,0 @@
package forwarding
import (
"crypto/rand"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/app/v2/internal/utils_test"
)
func TestTCPTunnel(t *testing.T) {
// Start the tunnel
l, err := net.Listen("tcp", "127.0.0.1:34567")
assert.NoError(t, err)
defer l.Close()
tunnel := &TCPTunnel{
HyClient: &utils_test.MockEchoHyClient{},
}
go tunnel.Serve(l)
for i := 0; i < 10; i++ {
conn, err := net.Dial("tcp", "127.0.0.1:34567")
assert.NoError(t, err)
data := make([]byte, 1024)
_, _ = rand.Read(data)
_, err = conn.Write(data)
assert.NoError(t, err)
recv := make([]byte, 1024)
_, err = conn.Read(recv)
assert.NoError(t, err)
assert.Equal(t, data, recv)
_ = conn.Close()
}
}

View file

@ -1,180 +0,0 @@
package forwarding
import (
"net"
"sync"
"sync/atomic"
"time"
"github.com/apernet/hysteria/core/v2/client"
)
const (
udpBufferSize = 4096
defaultTimeout = 60 * time.Second
idleCleanupInterval = 1 * time.Second
)
type atomicTime struct {
v atomic.Value
}
func newAtomicTime(t time.Time) *atomicTime {
a := &atomicTime{}
a.Set(t)
return a
}
func (t *atomicTime) Set(new time.Time) {
t.v.Store(new)
}
func (t *atomicTime) Get() time.Time {
return t.v.Load().(time.Time)
}
type sessionEntry struct {
HyConn client.HyUDPConn
Last *atomicTime
Timeout bool // true if the session is closed due to timeout
}
func (e *sessionEntry) Feed(data []byte, addr string) error {
e.Last.Set(time.Now())
return e.HyConn.Send(data, addr)
}
func (e *sessionEntry) ReceiveLoop(pc net.PacketConn, addr net.Addr) error {
for {
data, _, err := e.HyConn.Receive()
if err != nil {
return err
}
_, err = pc.WriteTo(data, addr)
if err != nil {
return err
}
e.Last.Set(time.Now())
}
}
type UDPTunnel struct {
HyClient client.Client
Remote string
Timeout time.Duration
EventLogger UDPEventLogger
m map[string]*sessionEntry // addr -> HyConn
mutex sync.RWMutex
}
type UDPEventLogger interface {
Connect(addr net.Addr)
Error(addr net.Addr, err error)
}
func (t *UDPTunnel) Serve(pc net.PacketConn) error {
t.m = make(map[string]*sessionEntry)
stopCh := make(chan struct{})
go t.idleCleanupLoop(stopCh)
defer close(stopCh)
defer t.cleanup(false)
buf := make([]byte, udpBufferSize)
for {
n, addr, err := pc.ReadFrom(buf)
if err != nil {
return err
}
t.feed(pc, addr, buf[:n])
}
}
func (t *UDPTunnel) idleCleanupLoop(stopCh <-chan struct{}) {
ticker := time.NewTicker(idleCleanupInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
t.cleanup(true)
case <-stopCh:
return
}
}
}
func (t *UDPTunnel) cleanup(idleOnly bool) {
// We use RLock here as we are only scanning the map, not deleting from it.
t.mutex.RLock()
defer t.mutex.RUnlock()
timeout := t.Timeout
if timeout == 0 {
timeout = defaultTimeout
}
now := time.Now()
for _, entry := range t.m {
if !idleOnly || now.Sub(entry.Last.Get()) > timeout {
entry.Timeout = true
_ = entry.HyConn.Close()
// Closing the connection here will cause the ReceiveLoop to exit,
// and the session will be removed from the map there.
}
}
}
func (t *UDPTunnel) feed(pc net.PacketConn, addr net.Addr, data []byte) {
t.mutex.RLock()
entry := t.m[addr.String()]
t.mutex.RUnlock()
// Create a new session if not exists
if entry == nil {
if t.EventLogger != nil {
t.EventLogger.Connect(addr)
}
hyConn, err := t.HyClient.UDP()
if err != nil {
if t.EventLogger != nil {
t.EventLogger.Error(addr, err)
}
return
}
entry = &sessionEntry{
HyConn: hyConn,
Last: newAtomicTime(time.Now()),
}
// Start the receive loop for this session
// Local <- Remote
go func() {
err := entry.ReceiveLoop(pc, addr)
if !entry.Timeout {
_ = hyConn.Close()
if t.EventLogger != nil {
t.EventLogger.Error(addr, err)
}
} else {
// Connection already closed by timeout cleanup,
// no need to close again here.
// Use nil error to indicate timeout.
if t.EventLogger != nil {
t.EventLogger.Error(addr, nil)
}
}
// Remove the session from the map
t.mutex.Lock()
delete(t.m, addr.String())
t.mutex.Unlock()
}()
// Insert the session into the map
t.mutex.Lock()
t.m[addr.String()] = entry
t.mutex.Unlock()
}
// Feed the message to the session
_ = entry.Feed(data, t.Remote)
}

View file

@ -1,39 +0,0 @@
package forwarding
import (
"crypto/rand"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/app/v2/internal/utils_test"
)
func TestUDPTunnel(t *testing.T) {
// Start the tunnel
l, err := net.ListenPacket("udp", "127.0.0.1:34567")
assert.NoError(t, err)
defer l.Close()
tunnel := &UDPTunnel{
HyClient: &utils_test.MockEchoHyClient{},
}
go tunnel.Serve(l)
for i := 0; i < 10; i++ {
conn, err := net.Dial("udp", "127.0.0.1:34567")
assert.NoError(t, err)
data := make([]byte, 1024)
_, _ = rand.Read(data)
_, err = conn.Write(data)
assert.NoError(t, err)
recv := make([]byte, 1024)
_, err = conn.Read(recv)
assert.NoError(t, err)
assert.Equal(t, data, recv)
_ = conn.Close()
}
}

View file

@ -1,301 +0,0 @@
package http
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/apernet/hysteria/core/v2/client"
)
const (
httpClientTimeout = 10 * time.Second
)
// Server is an HTTP server using a Hysteria client as outbound.
type Server struct {
HyClient client.Client
AuthFunc func(username, password string) bool // nil = no authentication
AuthRealm string
EventLogger EventLogger
httpClient *http.Client
}
type EventLogger interface {
ConnectRequest(addr net.Addr, reqAddr string)
ConnectError(addr net.Addr, reqAddr string, err error)
HTTPRequest(addr net.Addr, reqURL string)
HTTPError(addr net.Addr, reqURL string, err error)
}
func (s *Server) Serve(listener net.Listener) error {
for {
conn, err := listener.Accept()
if err != nil {
return err
}
go s.dispatch(conn)
}
}
func (s *Server) dispatch(conn net.Conn) {
bufReader := bufio.NewReader(conn)
for {
req, err := http.ReadRequest(bufReader)
if err != nil {
// Connection error or invalid request
_ = conn.Close()
return
}
if s.AuthFunc != nil {
authOK := false
// Check the Proxy-Authorization header
pAuth := req.Header.Get("Proxy-Authorization")
if strings.HasPrefix(pAuth, "Basic ") {
userPass, err := base64.URLEncoding.DecodeString(pAuth[6:])
if err == nil {
userPassParts := strings.SplitN(string(userPass), ":", 2)
if len(userPassParts) == 2 {
authOK = s.AuthFunc(userPassParts[0], userPassParts[1])
}
}
}
if !authOK {
// Proxy authentication required
_ = sendProxyAuthRequired(conn, req, s.AuthRealm)
_ = conn.Close()
return
}
}
if req.Method == http.MethodConnect {
if bufReader.Buffered() > 0 {
// There is still data in the buffered reader.
// We need to get it out and put it into a cachedConn,
// so that handleConnect can read it.
data := make([]byte, bufReader.Buffered())
_, err := io.ReadFull(bufReader, data)
if err != nil {
// Read from buffer failed, is this possible?
_ = conn.Close()
return
}
cachedConn := &cachedConn{
Conn: conn,
Buffer: *bytes.NewBuffer(data),
}
s.handleConnect(cachedConn, req)
} else {
// No data in the buffered reader, we can just pass the original connection.
s.handleConnect(conn, req)
}
// handleConnect will take over the connection,
// i.e. it will not return until the connection is closed.
// When it returns, there will be no more requests from this connection,
// so we simply exit the loop.
return
} else {
// handleRequest on the other hand handles one request at a time,
// and returns when the request is done. It returns a bool indicating
// whether the connection should be kept alive, but itself never closes
// the connection.
keepAlive := s.handleRequest(conn, req)
if !keepAlive {
_ = conn.Close()
return
}
}
}
}
// cachedConn is a net.Conn wrapper that first Read()s from a buffer,
// and then from the underlying net.Conn when the buffer is drained.
type cachedConn struct {
net.Conn
Buffer bytes.Buffer
}
func (c *cachedConn) Read(b []byte) (int, error) {
if c.Buffer.Len() > 0 {
n, err := c.Buffer.Read(b)
if err == io.EOF {
// Buffer is drained, hide it from the caller
err = nil
}
return n, err
}
return c.Conn.Read(b)
}
func (s *Server) handleConnect(conn net.Conn, req *http.Request) {
defer conn.Close()
port := req.URL.Port()
if port == "" {
// HTTP defaults to port 80
port = "80"
}
reqAddr := net.JoinHostPort(req.URL.Hostname(), port)
// Connect request & error log
if s.EventLogger != nil {
s.EventLogger.ConnectRequest(conn.RemoteAddr(), reqAddr)
}
var closeErr error
defer func() {
if s.EventLogger != nil {
s.EventLogger.ConnectError(conn.RemoteAddr(), reqAddr, closeErr)
}
}()
// Dial
rConn, err := s.HyClient.TCP(reqAddr)
if err != nil {
_ = sendSimpleResponse(conn, req, http.StatusBadGateway)
closeErr = err
return
}
defer rConn.Close()
// Send 200 OK response and start relaying
_ = sendSimpleResponse(conn, req, http.StatusOK)
copyErrChan := make(chan error, 2)
go func() {
_, err := io.Copy(rConn, conn)
copyErrChan <- err
}()
go func() {
_, err := io.Copy(conn, rConn)
copyErrChan <- err
}()
closeErr = <-copyErrChan
}
func (s *Server) handleRequest(conn net.Conn, req *http.Request) bool {
// Some clients use Connection, some use Proxy-Connection
// https://www.oreilly.com/library/view/http-the-definitive/1565925092/re40.html
keepAlive := req.ProtoAtLeast(1, 1) &&
(strings.ToLower(req.Header.Get("Proxy-Connection")) == "keep-alive" ||
strings.ToLower(req.Header.Get("Connection")) == "keep-alive")
req.RequestURI = "" // Outgoing request should not have RequestURI
removeHopByHopHeaders(req.Header)
removeExtraHTTPHostPort(req)
if req.URL.Scheme == "" || req.URL.Host == "" {
_ = sendSimpleResponse(conn, req, http.StatusBadRequest)
return false
}
// Request & error log
if s.EventLogger != nil {
s.EventLogger.HTTPRequest(conn.RemoteAddr(), req.URL.String())
}
var closeErr error
defer func() {
if s.EventLogger != nil {
s.EventLogger.HTTPError(conn.RemoteAddr(), req.URL.String(), closeErr)
}
}()
if s.httpClient == nil {
s.initHTTPClient()
}
// Do the request and send the response back
resp, err := s.httpClient.Do(req)
if err != nil {
closeErr = err
_ = sendSimpleResponse(conn, req, http.StatusBadGateway)
return false
}
removeHopByHopHeaders(resp.Header)
if keepAlive {
resp.Header.Set("Connection", "keep-alive")
resp.Header.Set("Proxy-Connection", "keep-alive")
resp.Header.Set("Keep-Alive", "timeout=60")
}
closeErr = resp.Write(conn)
return closeErr == nil && keepAlive
}
func (s *Server) initHTTPClient() {
s.httpClient = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// HyClient doesn't support context for now
return s.HyClient.TCP(addr)
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: httpClientTimeout,
}
}
func removeHopByHopHeaders(header http.Header) {
header.Del("Proxy-Connection") // Not in RFC but common
// https://www.ietf.org/rfc/rfc2616.txt
header.Del("Connection")
header.Del("Keep-Alive")
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
header.Del("TE")
header.Del("Trailers")
header.Del("Transfer-Encoding")
header.Del("Upgrade")
}
func removeExtraHTTPHostPort(req *http.Request) {
host := req.Host
if host == "" {
host = req.URL.Host
}
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
host = pHost
}
req.Host = host
req.URL.Host = host
}
// sendSimpleResponse sends a simple HTTP response with the given status code.
func sendSimpleResponse(conn net.Conn, req *http.Request, statusCode int) error {
resp := &http.Response{
StatusCode: statusCode,
Status: http.StatusText(statusCode),
Proto: req.Proto,
ProtoMajor: req.ProtoMajor,
ProtoMinor: req.ProtoMinor,
Header: http.Header{},
}
// Remove the "Content-Length: 0" header, some clients (e.g. ffmpeg) may not like it.
resp.ContentLength = -1
// Also, prevent the "Connection: close" header.
resp.Close = false
resp.Uncompressed = true
return resp.Write(conn)
}
// sendProxyAuthRequired sends a 407 Proxy Authentication Required response.
func sendProxyAuthRequired(conn net.Conn, req *http.Request, realm string) error {
resp := &http.Response{
StatusCode: http.StatusProxyAuthRequired,
Status: http.StatusText(http.StatusProxyAuthRequired),
Proto: req.Proto,
ProtoMajor: req.ProtoMajor,
ProtoMinor: req.ProtoMinor,
Header: http.Header{},
}
resp.Header.Set("Proxy-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
return resp.Write(conn)
}

View file

@ -1,59 +0,0 @@
package http
import (
"errors"
"net"
"net/http"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/core/v2/client"
)
const (
testCertFile = "test.crt"
testKeyFile = "test.key"
)
type mockHyClient struct{}
func (c *mockHyClient) TCP(addr string) (net.Conn, error) {
return net.Dial("tcp", addr)
}
func (c *mockHyClient) UDP() (client.HyUDPConn, error) {
return nil, errors.New("not implemented")
}
func (c *mockHyClient) Close() error {
return nil
}
func TestServer(t *testing.T) {
// Start the server
l, err := net.Listen("tcp", "127.0.0.1:18080")
assert.NoError(t, err)
defer l.Close()
s := &Server{
HyClient: &mockHyClient{},
}
go s.Serve(l)
// Start a test HTTP & HTTPS server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("control is an illusion"))
})
go http.ListenAndServe("127.0.0.1:18081", nil)
go http.ListenAndServeTLS("127.0.0.1:18082", testCertFile, testKeyFile, nil)
// Run the Python test script
cmd := exec.Command("python", "server_test.py")
// Suppress HTTPS warning text from Python
cmd.Env = append(cmd.Env, "PYTHONWARNINGS=ignore:Unverified HTTPS request")
out, err := cmd.CombinedOutput()
assert.NoError(t, err)
assert.Equal(t, "OK", strings.TrimSpace(string(out)))
}

View file

@ -1,24 +0,0 @@
import requests
proxies = {
"http": "http://127.0.0.1:18080",
"https": "http://127.0.0.1:18080",
}
def test_http(it):
for i in range(it):
r = requests.get("http://127.0.0.1:18081", proxies=proxies)
assert r.status_code == 200 and r.text == "control is an illusion"
def test_https(it):
for i in range(it):
r = requests.get("https://127.0.0.1:18082", proxies=proxies, verify=False)
assert r.status_code == 200 and r.text == "control is an illusion"
if __name__ == "__main__":
test_http(10)
test_https(10)
print("OK")

View file

@ -1,23 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDwTCCAqmgAwIBAgIUMeefneiCXWS2ovxNN+fJcdrOIfAwDQYJKoZIhvcNAQEL
BQAwcDELMAkGA1UEBhMCVFcxEzARBgNVBAgMClNvbWUtU3RhdGUxGTAXBgNVBAoM
EFJhbmRvbSBTdHVmZiBMTEMxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3
DQEJARYOcG9vcGVyQHNoaXQuY2MwHhcNMjMwNDI3MDAyMDQ1WhcNMzMwNDI0MDAy
MDQ1WjBwMQswCQYDVQQGEwJUVzETMBEGA1UECAwKU29tZS1TdGF0ZTEZMBcGA1UE
CgwQUmFuZG9tIFN0dWZmIExMQzESMBAGA1UEAwwJbG9jYWxob3N0MR0wGwYJKoZI
hvcNAQkBFg5wb29wZXJAc2hpdC5jYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAOU9/4AT/6fDKyEyZMMLFzUEVC8ZDJHoKZ+3g65ZFQLxRKqlEdhvOwq4
ZsxYF0sceUPDAsdrT+km0l1jAvq6u82n6xQQ60HpKe6hOvDX7KS0dPcKa+nfEa0W
DKamBB+TzxB2dBfBNS1oUU74nBb7ttpJiKnOpRJ0/J+CwslvhJzq04AUXC/W1CtW
CbZBg1JjY0fCN+Oy1WjEqMtRSB6k5Ipk40a8NcsqReBOMZChR8elruZ09sIlA6tf
jICOKToDVBmkjJ8m/GnxfV8MeLoK83M2VA73njsS6q9qe9KDVgIVQmifwi6JUb7N
o0A6f2Z47AWJmvq4goHJtnQ3fyoeIsMCAwEAAaNTMFEwHQYDVR0OBBYEFPrBsm6v
M29fKA3is22tK8yHYQaDMB8GA1UdIwQYMBaAFPrBsm6vM29fKA3is22tK8yHYQaD
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJvOwj0Tf8l9AWvf
1ZLyW0K3m5oJAoUayjlLP9q7KHgJHWd4QXxg4ApUDo523m4Own3FwtN06KCMqlxc
luDJi27ghRzZ8bpB9fUujikC1rs1oWYRz/K+JSO1VItan+azm9AQRj+nNepjUiT4
FjvRif+inC4392tcKuwrqiUFmLIggtFZdsLeKUL+hRGCRjY4BZw0d1sjjPtyVNUD
UMVO8pxlCV0NU4Nmt3vulD4YshAXM+Y8yX/vPRnaNGoRrbRgCg2VORRGaZVjQMHD
OLMvqM7pFKnVg0uiSbQ3xbQJ8WeX620zKI0So2+kZt9HoI+46gd7BdNfl7mmd6K7
ydYKuI8=
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA5T3/gBP/p8MrITJkwwsXNQRULxkMkegpn7eDrlkVAvFEqqUR
2G87CrhmzFgXSxx5Q8MCx2tP6SbSXWMC+rq7zafrFBDrQekp7qE68NfspLR09wpr
6d8RrRYMpqYEH5PPEHZ0F8E1LWhRTvicFvu22kmIqc6lEnT8n4LCyW+EnOrTgBRc
L9bUK1YJtkGDUmNjR8I347LVaMSoy1FIHqTkimTjRrw1yypF4E4xkKFHx6Wu5nT2
wiUDq1+MgI4pOgNUGaSMnyb8afF9Xwx4ugrzczZUDveeOxLqr2p70oNWAhVCaJ/C
LolRvs2jQDp/ZnjsBYma+riCgcm2dDd/Kh4iwwIDAQABAoIBABjiU/vJL/U8AFCI
MdviNlCw+ZprM6wa8Xm+5/JjBR7epb+IT5mY6WXOgoon/c9PdfJfFswi3/fFGQy+
FLK21nAKjEAPXho3fy/CHK3MIon2dMPkQ7aNWlPZkuH8H3J2DwIQeaWieW1GZ50U
64yrIjwrw0P7hHuua0W9YfuPuWt29YpW5g6ilSRE0kdTzoB6TgMzlVRj6RWbxWLX
erwYFesSpLPiQrozK2yywlQsvRV2AxTlf5woJyRTyCqcao5jNZOJJl0mqeGKNKbu
1iYGtZl9aj1XIRxUt+JB2IMKNJasygIp+GRLUDCHKh8RVFwRlVaSNcWbfLDuyNWW
T3lUEjECgYEA84mrs4TLuPfklsQM4WPBdN/2Ud1r0Zn/W8icHcVc/DCFXbcV4aPA
g4yyyyEkyTac2RSbSp+rfUk/pJcG6CVjwaiRIPehdtcLIUP34EdIrwPrPT7/uWVA
o/Hp1ANSILecknQXeE1qDlHVeGAq2k3vAQH2J0m7lMfar7QCBTMTMHcCgYEA8PkO
Uj9+/LoHod2eb4raH29wntis31X5FX/C/8HlmFmQplxfMxpRckzDYQELdHvDggNY
ZQo6pdE22MjCu2bk9AHa2ukMyieWm/mPe46Upr1YV2o5cWnfFFNa/LP2Ii/dWY5V
rFNsHFnrnwcWymX7OKo0Xb8xYnKhKZJAFwSpXxUCgYBPMjXj6wtU20g6vwZxRT9k
AnDXrmmhf7LK5jHefJAAcsbr8t3qwpWYMejypZSQ2nGnJkxZuBLMa0WHAJX+aCpI
j8iiL+USAFxeNPwmswev4lZdVF9Uqtiad9DSYUIT4aHI/nejZ4lVnscMnjlRRIa0
jS6/F/soJtW2zZLangFfgQKBgCOSAAUwDkSsCThhiGOasXv2bT9laI9HF4+O3m/2
ZTfJ8Mo91GesuN0Qa77D8rbtFfz5FXFEw0d6zIfPir8y/xTtuSqbQCIPGfJIMl/g
uhyq0oGE0pnlMOLFMyceQXTmb9wqYIchgVHmDBvbZgfWafEBXt1/vYB0v0ltpzw+
menJAoGBAI0hx3+mrFgA+xJBEk4oexAlro1qbNWoR7BCmLQtd49jG3eZQu4JxWH2
kh58AIXzLl0X9t4pfMYasYL6jBGvw+AqNdo2krpiL7MWEE8w8FP/wibzqmuloziB
T7BZuCZjpcAM0IxLmQeeUK0LF0mihcqvssxveaet46mj7QoA7bGQ
-----END RSA PRIVATE KEY-----

View file

@ -1,12 +0,0 @@
with-expecter: true
dir: internal/mocks
outpkg: mocks
packages:
net:
interfaces:
Listener:
config:
mockname: MockListener
Conn:
config:
mockname: MockConn

View file

@ -1,427 +0,0 @@
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
import (
net "net"
mock "github.com/stretchr/testify/mock"
time "time"
)
// MockConn is an autogenerated mock type for the Conn type
type MockConn struct {
mock.Mock
}
type MockConn_Expecter struct {
mock *mock.Mock
}
func (_m *MockConn) EXPECT() *MockConn_Expecter {
return &MockConn_Expecter{mock: &_m.Mock}
}
// Close provides a mock function with given fields:
func (_m *MockConn) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// MockConn_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
type MockConn_Close_Call struct {
*mock.Call
}
// Close is a helper method to define mock.On call
func (_e *MockConn_Expecter) Close() *MockConn_Close_Call {
return &MockConn_Close_Call{Call: _e.mock.On("Close")}
}
func (_c *MockConn_Close_Call) Run(run func()) *MockConn_Close_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockConn_Close_Call) Return(_a0 error) *MockConn_Close_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConn_Close_Call) RunAndReturn(run func() error) *MockConn_Close_Call {
_c.Call.Return(run)
return _c
}
// LocalAddr provides a mock function with given fields:
func (_m *MockConn) LocalAddr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for LocalAddr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// MockConn_LocalAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LocalAddr'
type MockConn_LocalAddr_Call struct {
*mock.Call
}
// LocalAddr is a helper method to define mock.On call
func (_e *MockConn_Expecter) LocalAddr() *MockConn_LocalAddr_Call {
return &MockConn_LocalAddr_Call{Call: _e.mock.On("LocalAddr")}
}
func (_c *MockConn_LocalAddr_Call) Run(run func()) *MockConn_LocalAddr_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockConn_LocalAddr_Call) Return(_a0 net.Addr) *MockConn_LocalAddr_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConn_LocalAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_LocalAddr_Call {
_c.Call.Return(run)
return _c
}
// Read provides a mock function with given fields: b
func (_m *MockConn) Read(b []byte) (int, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for Read")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
return rf(b)
}
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(b)
} else {
r0 = ret.Get(0).(int)
}
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(b)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockConn_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
type MockConn_Read_Call struct {
*mock.Call
}
// Read is a helper method to define mock.On call
// - b []byte
func (_e *MockConn_Expecter) Read(b interface{}) *MockConn_Read_Call {
return &MockConn_Read_Call{Call: _e.mock.On("Read", b)}
}
func (_c *MockConn_Read_Call) Run(run func(b []byte)) *MockConn_Read_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]byte))
})
return _c
}
func (_c *MockConn_Read_Call) Return(n int, err error) *MockConn_Read_Call {
_c.Call.Return(n, err)
return _c
}
func (_c *MockConn_Read_Call) RunAndReturn(run func([]byte) (int, error)) *MockConn_Read_Call {
_c.Call.Return(run)
return _c
}
// RemoteAddr provides a mock function with given fields:
func (_m *MockConn) RemoteAddr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for RemoteAddr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// MockConn_RemoteAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoteAddr'
type MockConn_RemoteAddr_Call struct {
*mock.Call
}
// RemoteAddr is a helper method to define mock.On call
func (_e *MockConn_Expecter) RemoteAddr() *MockConn_RemoteAddr_Call {
return &MockConn_RemoteAddr_Call{Call: _e.mock.On("RemoteAddr")}
}
func (_c *MockConn_RemoteAddr_Call) Run(run func()) *MockConn_RemoteAddr_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockConn_RemoteAddr_Call) Return(_a0 net.Addr) *MockConn_RemoteAddr_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConn_RemoteAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_RemoteAddr_Call {
_c.Call.Return(run)
return _c
}
// SetDeadline provides a mock function with given fields: t
func (_m *MockConn) SetDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockConn_SetDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDeadline'
type MockConn_SetDeadline_Call struct {
*mock.Call
}
// SetDeadline is a helper method to define mock.On call
// - t time.Time
func (_e *MockConn_Expecter) SetDeadline(t interface{}) *MockConn_SetDeadline_Call {
return &MockConn_SetDeadline_Call{Call: _e.mock.On("SetDeadline", t)}
}
func (_c *MockConn_SetDeadline_Call) Run(run func(t time.Time)) *MockConn_SetDeadline_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(time.Time))
})
return _c
}
func (_c *MockConn_SetDeadline_Call) Return(_a0 error) *MockConn_SetDeadline_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConn_SetDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetDeadline_Call {
_c.Call.Return(run)
return _c
}
// SetReadDeadline provides a mock function with given fields: t
func (_m *MockConn) SetReadDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetReadDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockConn_SetReadDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetReadDeadline'
type MockConn_SetReadDeadline_Call struct {
*mock.Call
}
// SetReadDeadline is a helper method to define mock.On call
// - t time.Time
func (_e *MockConn_Expecter) SetReadDeadline(t interface{}) *MockConn_SetReadDeadline_Call {
return &MockConn_SetReadDeadline_Call{Call: _e.mock.On("SetReadDeadline", t)}
}
func (_c *MockConn_SetReadDeadline_Call) Run(run func(t time.Time)) *MockConn_SetReadDeadline_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(time.Time))
})
return _c
}
func (_c *MockConn_SetReadDeadline_Call) Return(_a0 error) *MockConn_SetReadDeadline_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConn_SetReadDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetReadDeadline_Call {
_c.Call.Return(run)
return _c
}
// SetWriteDeadline provides a mock function with given fields: t
func (_m *MockConn) SetWriteDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetWriteDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockConn_SetWriteDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetWriteDeadline'
type MockConn_SetWriteDeadline_Call struct {
*mock.Call
}
// SetWriteDeadline is a helper method to define mock.On call
// - t time.Time
func (_e *MockConn_Expecter) SetWriteDeadline(t interface{}) *MockConn_SetWriteDeadline_Call {
return &MockConn_SetWriteDeadline_Call{Call: _e.mock.On("SetWriteDeadline", t)}
}
func (_c *MockConn_SetWriteDeadline_Call) Run(run func(t time.Time)) *MockConn_SetWriteDeadline_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(time.Time))
})
return _c
}
func (_c *MockConn_SetWriteDeadline_Call) Return(_a0 error) *MockConn_SetWriteDeadline_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockConn_SetWriteDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetWriteDeadline_Call {
_c.Call.Return(run)
return _c
}
// Write provides a mock function with given fields: b
func (_m *MockConn) Write(b []byte) (int, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for Write")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
return rf(b)
}
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(b)
} else {
r0 = ret.Get(0).(int)
}
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(b)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockConn_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
type MockConn_Write_Call struct {
*mock.Call
}
// Write is a helper method to define mock.On call
// - b []byte
func (_e *MockConn_Expecter) Write(b interface{}) *MockConn_Write_Call {
return &MockConn_Write_Call{Call: _e.mock.On("Write", b)}
}
func (_c *MockConn_Write_Call) Run(run func(b []byte)) *MockConn_Write_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]byte))
})
return _c
}
func (_c *MockConn_Write_Call) Return(n int, err error) *MockConn_Write_Call {
_c.Call.Return(n, err)
return _c
}
func (_c *MockConn_Write_Call) RunAndReturn(run func([]byte) (int, error)) *MockConn_Write_Call {
_c.Call.Return(run)
return _c
}
// 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.
// The first argument is typically a *testing.T value.
func NewMockConn(t interface {
mock.TestingT
Cleanup(func())
}) *MockConn {
mock := &MockConn{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -1,185 +0,0 @@
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
import (
net "net"
mock "github.com/stretchr/testify/mock"
)
// MockListener is an autogenerated mock type for the Listener type
type MockListener struct {
mock.Mock
}
type MockListener_Expecter struct {
mock *mock.Mock
}
func (_m *MockListener) EXPECT() *MockListener_Expecter {
return &MockListener_Expecter{mock: &_m.Mock}
}
// Accept provides a mock function with given fields:
func (_m *MockListener) Accept() (net.Conn, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Accept")
}
var r0 net.Conn
var r1 error
if rf, ok := ret.Get(0).(func() (net.Conn, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() net.Conn); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Conn)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockListener_Accept_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Accept'
type MockListener_Accept_Call struct {
*mock.Call
}
// Accept is a helper method to define mock.On call
func (_e *MockListener_Expecter) Accept() *MockListener_Accept_Call {
return &MockListener_Accept_Call{Call: _e.mock.On("Accept")}
}
func (_c *MockListener_Accept_Call) Run(run func()) *MockListener_Accept_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockListener_Accept_Call) Return(_a0 net.Conn, _a1 error) *MockListener_Accept_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockListener_Accept_Call) RunAndReturn(run func() (net.Conn, error)) *MockListener_Accept_Call {
_c.Call.Return(run)
return _c
}
// Addr provides a mock function with given fields:
func (_m *MockListener) Addr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Addr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// MockListener_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr'
type MockListener_Addr_Call struct {
*mock.Call
}
// Addr is a helper method to define mock.On call
func (_e *MockListener_Expecter) Addr() *MockListener_Addr_Call {
return &MockListener_Addr_Call{Call: _e.mock.On("Addr")}
}
func (_c *MockListener_Addr_Call) Run(run func()) *MockListener_Addr_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockListener_Addr_Call) Return(_a0 net.Addr) *MockListener_Addr_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockListener_Addr_Call) RunAndReturn(run func() net.Addr) *MockListener_Addr_Call {
_c.Call.Return(run)
return _c
}
// Close provides a mock function with given fields:
func (_m *MockListener) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// MockListener_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
type MockListener_Close_Call struct {
*mock.Call
}
// Close is a helper method to define mock.On call
func (_e *MockListener_Expecter) Close() *MockListener_Close_Call {
return &MockListener_Close_Call{Call: _e.mock.On("Close")}
}
func (_c *MockListener_Close_Call) Run(run func()) *MockListener_Close_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockListener_Close_Call) Return(_a0 error) *MockListener_Close_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockListener_Close_Call) RunAndReturn(run func() error) *MockListener_Close_Call {
_c.Call.Return(run)
return _c
}
// 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.
// The first argument is typically a *testing.T value.
func NewMockListener(t interface {
mock.TestingT
Cleanup(func())
}) *MockListener {
mock := &MockListener{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -1,72 +0,0 @@
package proxymux
import (
"net"
"sync"
"github.com/apernet/hysteria/extras/v2/correctnet"
)
type muxManager struct {
listeners map[string]*muxListener
lock sync.Mutex
}
var globalMuxManager *muxManager
func init() {
globalMuxManager = &muxManager{
listeners: make(map[string]*muxListener),
}
}
func (m *muxManager) GetOrCreate(address string) (*muxListener, error) {
key, err := m.canonicalizeAddrPort(address)
if err != nil {
return nil, err
}
m.lock.Lock()
defer m.lock.Unlock()
if ml, ok := m.listeners[key]; ok {
return ml, nil
}
listener, err := correctnet.Listen("tcp", key)
if err != nil {
return nil, err
}
ml := newMuxListener(listener, func() {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.listeners, key)
})
m.listeners[key] = ml
return ml, nil
}
func (m *muxManager) canonicalizeAddrPort(address string) (string, error) {
taddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return "", err
}
return taddr.String(), nil
}
func ListenHTTP(address string) (net.Listener, error) {
ml, err := globalMuxManager.GetOrCreate(address)
if err != nil {
return nil, err
}
return ml.ListenHTTP()
}
func ListenSOCKS(address string) (net.Listener, error) {
ml, err := globalMuxManager.GetOrCreate(address)
if err != nil {
return nil, err
}
return ml.ListenSOCKS()
}

View file

@ -1,110 +0,0 @@
package proxymux
import (
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestListenSOCKS(t *testing.T) {
address := "127.2.39.129:11081"
sl, err := ListenSOCKS(address)
if !assert.NoError(t, err) {
return
}
defer func() {
sl.Close()
}()
hl, err := ListenHTTP(address)
if !assert.NoError(t, err) {
return
}
defer hl.Close()
_, err = ListenSOCKS(address)
if !assert.ErrorIs(t, err, ErrProtocolInUse) {
return
}
sl.Close()
sl, err = ListenSOCKS(address)
if !assert.NoError(t, err) {
return
}
}
func TestListenHTTP(t *testing.T) {
address := "127.2.39.129:11082"
hl, err := ListenHTTP(address)
if !assert.NoError(t, err) {
return
}
defer func() {
hl.Close()
}()
sl, err := ListenSOCKS(address)
if !assert.NoError(t, err) {
return
}
defer sl.Close()
_, err = ListenHTTP(address)
if !assert.ErrorIs(t, err, ErrProtocolInUse) {
return
}
hl.Close()
hl, err = ListenHTTP(address)
if !assert.NoError(t, err) {
return
}
}
func TestRelease(t *testing.T) {
address := "127.2.39.129:11083"
hl, err := ListenHTTP(address)
if !assert.NoError(t, err) {
return
}
sl, err := ListenSOCKS(address)
if !assert.NoError(t, err) {
return
}
if !assert.True(t, globalMuxManager.testAddressExists(address)) {
return
}
_, err = net.Listen("tcp", address)
if !assert.Error(t, err) {
return
}
hl.Close()
sl.Close()
// Wait for muxListener released
time.Sleep(time.Second)
if !assert.False(t, globalMuxManager.testAddressExists(address)) {
return
}
lis, err := net.Listen("tcp", address)
if !assert.NoError(t, err) {
return
}
defer lis.Close()
}
func (m *muxManager) testAddressExists(address string) bool {
m.lock.Lock()
defer m.lock.Unlock()
_, ok := m.listeners[address]
return ok
}

View file

@ -1,320 +0,0 @@
package proxymux
import (
"errors"
"fmt"
"io"
"net"
"sync"
)
func newMuxListener(listener net.Listener, deleteFunc func()) *muxListener {
l := &muxListener{
base: listener,
acceptChan: make(chan net.Conn),
closeChan: make(chan struct{}),
deleteFunc: deleteFunc,
}
go l.acceptLoop()
go l.mainLoop()
return l
}
type muxListener struct {
lock sync.Mutex
base net.Listener
acceptErr error
acceptChan chan net.Conn
closeChan chan struct{}
socksListener *subListener
httpListener *subListener
deleteFunc func()
}
func (l *muxListener) acceptLoop() {
defer close(l.acceptChan)
for {
conn, err := l.base.Accept()
if err != nil {
l.lock.Lock()
l.acceptErr = err
l.lock.Unlock()
return
}
select {
case <-l.closeChan:
return
case l.acceptChan <- conn:
}
}
}
func (l *muxListener) mainLoop() {
defer func() {
l.deleteFunc()
l.base.Close()
close(l.closeChan)
l.lock.Lock()
defer l.lock.Unlock()
if sl := l.httpListener; sl != nil {
close(sl.acceptChan)
l.httpListener = nil
}
if sl := l.socksListener; sl != nil {
close(sl.acceptChan)
l.socksListener = nil
}
}()
for {
var socksCloseChan, httpCloseChan chan struct{}
if l.httpListener != nil {
httpCloseChan = l.httpListener.closeChan
}
if l.socksListener != nil {
socksCloseChan = l.socksListener.closeChan
}
select {
case <-l.closeChan:
return
case conn, ok := <-l.acceptChan:
if !ok {
return
}
go l.dispatch(conn)
case <-socksCloseChan:
l.lock.Lock()
if socksCloseChan == l.socksListener.closeChan {
// not replaced by another ListenSOCKS()
l.socksListener = nil
}
l.lock.Unlock()
if l.checkIdle() {
return
}
case <-httpCloseChan:
l.lock.Lock()
if httpCloseChan == l.httpListener.closeChan {
// not replaced by another ListenHTTP()
l.httpListener = nil
}
l.lock.Unlock()
if l.checkIdle() {
return
}
}
}
}
func (l *muxListener) dispatch(conn net.Conn) {
var b [1]byte
if _, err := io.ReadFull(conn, b[:]); err != nil {
conn.Close()
return
}
l.lock.Lock()
var target *subListener
if b[0] == 5 {
target = l.socksListener
} else {
target = l.httpListener
}
l.lock.Unlock()
if target == nil {
conn.Close()
return
}
wconn := &connWithOneByte{Conn: conn, b: b[0]}
select {
case <-target.closeChan:
case target.acceptChan <- wconn:
}
}
func (l *muxListener) checkIdle() bool {
l.lock.Lock()
defer l.lock.Unlock()
return l.httpListener == nil && l.socksListener == nil
}
func (l *muxListener) getAndClearAcceptError() error {
l.lock.Lock()
defer l.lock.Unlock()
if l.acceptErr == nil {
return nil
}
err := l.acceptErr
l.acceptErr = nil
return err
}
func (l *muxListener) ListenHTTP() (net.Listener, error) {
l.lock.Lock()
defer l.lock.Unlock()
if l.httpListener != nil {
subListenerPendingClosed := false
select {
case <-l.httpListener.closeChan:
subListenerPendingClosed = true
default:
}
if !subListenerPendingClosed {
return nil, OpErr{
Addr: l.base.Addr(),
Protocol: "http",
Op: "bind-protocol",
Err: ErrProtocolInUse,
}
}
l.httpListener = nil
}
select {
case <-l.closeChan:
return nil, net.ErrClosed
default:
}
sl := newSubListener(l.getAndClearAcceptError, l.base.Addr)
l.httpListener = sl
return sl, nil
}
func (l *muxListener) ListenSOCKS() (net.Listener, error) {
l.lock.Lock()
defer l.lock.Unlock()
if l.socksListener != nil {
subListenerPendingClosed := false
select {
case <-l.socksListener.closeChan:
subListenerPendingClosed = true
default:
}
if !subListenerPendingClosed {
return nil, OpErr{
Addr: l.base.Addr(),
Protocol: "socks",
Op: "bind-protocol",
Err: ErrProtocolInUse,
}
}
l.socksListener = nil
}
select {
case <-l.closeChan:
return nil, net.ErrClosed
default:
}
sl := newSubListener(l.getAndClearAcceptError, l.base.Addr)
l.socksListener = sl
return sl, nil
}
func newSubListener(acceptErrorFunc func() error, addrFunc func() net.Addr) *subListener {
return &subListener{
acceptChan: make(chan net.Conn),
acceptErrorFunc: acceptErrorFunc,
closeChan: make(chan struct{}),
addrFunc: addrFunc,
}
}
type subListener struct {
// receive connections or closure from upstream
acceptChan chan net.Conn
// get an error of Accept() from upstream
acceptErrorFunc func() error
// notify upstream that we are closed
closeChan chan struct{}
// Listener.Addr() implementation of base listener
addrFunc func() net.Addr
}
func (l *subListener) Accept() (net.Conn, error) {
select {
case <-l.closeChan:
// closed by ourselves
return nil, net.ErrClosed
case conn, ok := <-l.acceptChan:
if !ok {
// closed by upstream
if acceptErr := l.acceptErrorFunc(); acceptErr != nil {
return nil, acceptErr
}
return nil, net.ErrClosed
}
return conn, nil
}
}
func (l *subListener) Addr() net.Addr {
return l.addrFunc()
}
// Close implements net.Listener.Close.
// Upstream should use close(l.acceptChan) instead.
func (l *subListener) Close() error {
select {
case <-l.closeChan:
return nil
default:
}
close(l.closeChan)
return nil
}
// connWithOneByte is a net.Conn that returns b for the first read
// request, then forwards everything else to Conn.
type connWithOneByte struct {
net.Conn
b byte
bRead bool
}
func (c *connWithOneByte) Read(bs []byte) (int, error) {
if c.bRead {
return c.Conn.Read(bs)
}
if len(bs) == 0 {
return 0, nil
}
c.bRead = true
bs[0] = c.b
return 1, nil
}
type OpErr struct {
Addr net.Addr
Protocol string
Op string
Err error
}
func (m OpErr) Error() string {
return fmt.Sprintf("mux-listen: %s[%s]: %s: %v", m.Addr, m.Protocol, m.Op, m.Err)
}
func (m OpErr) Unwrap() error {
return m.Err
}
var ErrProtocolInUse = errors.New("protocol already in use")

View file

@ -1,154 +0,0 @@
package proxymux
import (
"bytes"
"net"
"sync"
"testing"
"time"
"github.com/apernet/hysteria/app/v2/internal/proxymux/internal/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
//go:generate mockery
func testMockListener(t *testing.T, connChan <-chan net.Conn) net.Listener {
closedChan := make(chan struct{})
mockListener := mocks.NewMockListener(t)
mockListener.EXPECT().Accept().RunAndReturn(func() (net.Conn, error) {
select {
case <-closedChan:
return nil, net.ErrClosed
case conn, ok := <-connChan:
if !ok {
panic("unexpected closed channel (connChan)")
}
return conn, nil
}
})
mockListener.EXPECT().Close().RunAndReturn(func() error {
select {
case <-closedChan:
default:
close(closedChan)
}
return nil
})
return mockListener
}
func testMockConn(t *testing.T, b []byte) net.Conn {
buf := bytes.NewReader(b)
isClosed := false
mockConn := mocks.NewMockConn(t)
mockConn.EXPECT().Read(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
if isClosed {
return 0, net.ErrClosed
}
return buf.Read(b)
})
mockConn.EXPECT().Close().RunAndReturn(func() error {
isClosed = true
return nil
})
return mockConn
}
func TestMuxHTTP(t *testing.T) {
connChan := make(chan net.Conn)
mockListener := testMockListener(t, connChan)
mockConn := testMockConn(t, []byte("CONNECT example.com:443 HTTP/1.1\r\n\r\n"))
mux := newMuxListener(mockListener, func() {})
hl, err := mux.ListenHTTP()
if !assert.NoError(t, err) {
return
}
sl, err := mux.ListenSOCKS()
if !assert.NoError(t, err) {
return
}
connChan <- mockConn
var socksConn, httpConn net.Conn
var socksErr, httpErr error
var wg sync.WaitGroup
wg.Add(2)
go func() {
socksConn, socksErr = sl.Accept()
wg.Done()
}()
go func() {
httpConn, httpErr = hl.Accept()
wg.Done()
}()
time.Sleep(time.Second)
sl.Close()
hl.Close()
wg.Wait()
assert.Nil(t, socksConn)
assert.ErrorIs(t, socksErr, net.ErrClosed)
assert.NotNil(t, httpConn)
httpConn.Close()
assert.NoError(t, httpErr)
// Wait for muxListener released
<-mux.acceptChan
}
func TestMuxSOCKS(t *testing.T) {
connChan := make(chan net.Conn)
mockListener := testMockListener(t, connChan)
mockConn := testMockConn(t, []byte{0x05, 0x02, 0x00, 0x01}) // SOCKS5 Connect Request: NOAUTH+GSSAPI
mux := newMuxListener(mockListener, func() {})
hl, err := mux.ListenHTTP()
if !assert.NoError(t, err) {
return
}
sl, err := mux.ListenSOCKS()
if !assert.NoError(t, err) {
return
}
connChan <- mockConn
var socksConn, httpConn net.Conn
var socksErr, httpErr error
var wg sync.WaitGroup
wg.Add(2)
go func() {
socksConn, socksErr = sl.Accept()
wg.Done()
}()
go func() {
httpConn, httpErr = hl.Accept()
wg.Done()
}()
time.Sleep(time.Second)
sl.Close()
hl.Close()
wg.Wait()
assert.NotNil(t, socksConn)
socksConn.Close()
assert.NoError(t, socksErr)
assert.Nil(t, httpConn)
assert.ErrorIs(t, httpErr, net.ErrClosed)
// Wait for muxListener released
<-mux.acceptChan
}

View file

@ -1,17 +0,0 @@
//go:build !386
// +build !386
package redirect
import (
"syscall"
"unsafe"
)
func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
_, _, e := syscall.Syscall6(syscall.SYS_GETSOCKOPT, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
if e != 0 {
err = e
}
return
}

View file

@ -1,23 +0,0 @@
package redirect
import (
"syscall"
"unsafe"
)
const (
sysGetsockopt = 15
)
// On 386 we cannot call socketcall with syscall.Syscall6, as it always fails with EFAULT.
// Use our own syscall.socketcall hack instead.
func syscall_socketcall(call int, a0, a1, a2, a3, a4, a5 uintptr) (n int, err syscall.Errno)
func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
_, e := syscall_socketcall(sysGetsockopt, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
if e != 0 {
err = e
}
return
}

View file

@ -1,7 +0,0 @@
//go:build gc
// +build gc
#include "textflag.h"
TEXT ·syscall_socketcall(SB),NOSPLIT,$0-36
JMP syscall·socketcall(SB)

View file

@ -1,126 +0,0 @@
package redirect
import (
"encoding/binary"
"errors"
"io"
"net"
"syscall"
"unsafe"
"github.com/apernet/hysteria/core/v2/client"
)
const (
soOriginalDst = 80
soOriginalDstV6 = 80
)
type TCPRedirect struct {
HyClient client.Client
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *TCPRedirect) ListenAndServe(laddr *net.TCPAddr) error {
listener, err := net.ListenTCP("tcp", laddr)
if err != nil {
return err
}
defer listener.Close()
for {
c, err := listener.AcceptTCP()
if err != nil {
return err
}
go r.handle(c)
}
}
func (r *TCPRedirect) handle(conn *net.TCPConn) {
defer conn.Close()
dstAddr, err := getDstAddr(conn)
if err != nil {
// Fail silently if we can't get the original destination.
// Maybe we should print something to the log?
return
}
if r.EventLogger != nil {
r.EventLogger.Connect(conn.RemoteAddr(), dstAddr)
}
var closeErr error
defer func() {
if r.EventLogger != nil {
r.EventLogger.Error(conn.RemoteAddr(), dstAddr, closeErr)
}
}()
rc, err := r.HyClient.TCP(dstAddr.String())
if err != nil {
closeErr = err
return
}
defer rc.Close()
// Start forwarding
copyErrChan := make(chan error, 2)
go func() {
_, copyErr := io.Copy(rc, conn)
copyErrChan <- copyErr
}()
go func() {
_, copyErr := io.Copy(conn, rc)
copyErrChan <- copyErr
}()
closeErr = <-copyErrChan
}
type sockAddr struct {
family uint16
port [2]byte // always big endian regardless of platform
data [24]byte // sockaddr_in or sockaddr_in6
}
func getOriginalDst(fd uintptr) (*sockAddr, error) {
var addr sockAddr
addrSize := uint32(unsafe.Sizeof(addr))
// Try IPv6 first
err := getsockopt(fd, syscall.SOL_IPV6, soOriginalDstV6, unsafe.Pointer(&addr), &addrSize)
if err == nil {
return &addr, nil
}
// Then IPv4
err = getsockopt(fd, syscall.SOL_IP, soOriginalDst, unsafe.Pointer(&addr), &addrSize)
return &addr, err
}
// getDstAddr returns the original destination of a redirected TCP connection.
func getDstAddr(conn *net.TCPConn) (*net.TCPAddr, error) {
rc, err := conn.SyscallConn()
if err != nil {
return nil, err
}
var addr *sockAddr
var err2 error
err = rc.Control(func(fd uintptr) {
addr, err2 = getOriginalDst(fd)
})
if err != nil {
return nil, err
}
if err2 != nil {
return nil, err2
}
switch addr.family {
case syscall.AF_INET:
return &net.TCPAddr{IP: addr.data[:4], Port: int(binary.BigEndian.Uint16(addr.port[:]))}, nil
case syscall.AF_INET6:
return &net.TCPAddr{IP: addr.data[4:20], Port: int(binary.BigEndian.Uint16(addr.port[:]))}, nil
default:
return nil, errors.New("address family not IPv4 or IPv6")
}
}

View file

@ -1,24 +0,0 @@
//go:build !linux
package redirect
import (
"errors"
"net"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPRedirect struct {
HyClient client.Client
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *TCPRedirect) ListenAndServe(laddr *net.TCPAddr) error {
return errors.New("not supported on this platform")
}

View file

@ -1,65 +0,0 @@
import socket
import array
import os
import struct
import sys
def serve(path):
try:
os.unlink(path)
except OSError:
if os.path.exists(path):
raise
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(path)
server.listen()
print(f"Listening on {path}")
try:
while True:
connection, client_address = server.accept()
print(f"Client connected")
try:
# Receiving fd from client
fds = array.array("i")
msg, ancdata, flags, addr = connection.recvmsg(1, socket.CMSG_LEN(struct.calcsize('i')))
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
fd = fds[0]
# We make a call to setsockopt(2) here, so client can verify we have received the fd
# In the real scenario, the server would set things like SO_MARK,
# we use SO_RCVBUF as it doesn't require any special capabilities.
nbytes = struct.pack("i", 2500)
fdsocket = fd_to_socket(fd)
fdsocket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, nbytes)
fdsocket.close()
# The only protocol-like thing specified in the client implementation.
connection.send(b'\x01')
finally:
connection.close()
print("Connection closed")
except KeyboardInterrupt:
print("Exit")
finally:
server.close()
os.unlink(path)
def fd_to_socket(fd):
return socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
if __name__ == "__main__":
if len(sys.argv) < 2:
raise ValueError("unix socket path is required")
serve(sys.argv[1])

View file

@ -1,76 +0,0 @@
package sockopts
import (
"fmt"
"net"
)
type SocketOptions struct {
BindInterface *string
FirewallMark *uint32
FdControlUnixSocket *string
}
// implemented in platform-specific files
var (
bindInterfaceFunc func(c *net.UDPConn, device string) error
firewallMarkFunc func(c *net.UDPConn, fwmark uint32) error
fdControlUnixSocketFunc func(c *net.UDPConn, path string) error
)
func (o *SocketOptions) CheckSupported() (err error) {
if o.BindInterface != nil && bindInterfaceFunc == nil {
return &UnsupportedError{"bindInterface"}
}
if o.FirewallMark != nil && firewallMarkFunc == nil {
return &UnsupportedError{"fwmark"}
}
if o.FdControlUnixSocket != nil && fdControlUnixSocketFunc == nil {
return &UnsupportedError{"fdControlUnixSocket"}
}
return nil
}
type UnsupportedError struct {
Field string
}
func (e *UnsupportedError) Error() string {
return fmt.Sprintf("%s is not supported on this platform", e.Field)
}
func (o *SocketOptions) ListenUDP() (uconn net.PacketConn, err error) {
uconn, err = net.ListenUDP("udp", nil)
if err != nil {
return
}
err = o.applyToUDPConn(uconn.(*net.UDPConn))
if err != nil {
uconn.Close()
uconn = nil
return
}
return
}
func (o *SocketOptions) applyToUDPConn(c *net.UDPConn) error {
if o.BindInterface != nil && bindInterfaceFunc != nil {
err := bindInterfaceFunc(c, *o.BindInterface)
if err != nil {
return fmt.Errorf("failed to bind to interface: %w", err)
}
}
if o.FirewallMark != nil && firewallMarkFunc != nil {
err := firewallMarkFunc(c, *o.FirewallMark)
if err != nil {
return fmt.Errorf("failed to set fwmark: %w", err)
}
}
if o.FdControlUnixSocket != nil && fdControlUnixSocketFunc != nil {
err := fdControlUnixSocketFunc(c, *o.FdControlUnixSocket)
if err != nil {
return fmt.Errorf("failed to send fd to control unix socket: %w", err)
}
}
return nil
}

View file

@ -1,96 +0,0 @@
//go:build linux
package sockopts
import (
"fmt"
"net"
"time"
"golang.org/x/exp/constraints"
"golang.org/x/sys/unix"
)
const (
fdControlUnixTimeout = 3 * time.Second
)
func init() {
bindInterfaceFunc = bindInterfaceImpl
firewallMarkFunc = firewallMarkImpl
fdControlUnixSocketFunc = fdControlUnixSocketImpl
}
func controlUDPConn(c *net.UDPConn, cb func(fd int) error) (err error) {
rconn, err := c.SyscallConn()
if err != nil {
return
}
cerr := rconn.Control(func(fd uintptr) {
err = cb(int(fd))
})
if err != nil {
return
}
if cerr != nil {
err = fmt.Errorf("failed to control fd: %w", cerr)
return
}
return
}
func bindInterfaceImpl(c *net.UDPConn, device string) error {
return controlUDPConn(c, func(fd int) error {
return unix.BindToDevice(fd, device)
})
}
func firewallMarkImpl(c *net.UDPConn, fwmark uint32) error {
return controlUDPConn(c, func(fd int) error {
return unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_MARK, int(fwmark))
})
}
func fdControlUnixSocketImpl(c *net.UDPConn, path string) error {
return controlUDPConn(c, func(fd int) error {
socketFd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0)
if err != nil {
return fmt.Errorf("failed to create unix socket: %w", err)
}
defer unix.Close(socketFd)
var timeout unix.Timeval
timeUsec := fdControlUnixTimeout.Microseconds()
castAssignInteger(timeUsec/1e6, &timeout.Sec)
// Specifying the type explicitly is not necessary here, but it makes GoLand happy.
castAssignInteger[int64](timeUsec%1e6, &timeout.Usec)
_ = unix.SetsockoptTimeval(socketFd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &timeout)
_ = unix.SetsockoptTimeval(socketFd, unix.SOL_SOCKET, unix.SO_SNDTIMEO, &timeout)
err = unix.Connect(socketFd, &unix.SockaddrUnix{Name: path})
if err != nil {
return fmt.Errorf("failed to connect: %w", err)
}
err = unix.Sendmsg(socketFd, nil, unix.UnixRights(fd), nil, 0)
if err != nil {
return fmt.Errorf("failed to send: %w", err)
}
dummy := []byte{1}
n, err := unix.Read(socketFd, dummy)
if err != nil {
return fmt.Errorf("failed to receive: %w", err)
}
if n != 1 {
return fmt.Errorf("socket closed unexpectedly")
}
return nil
})
}
func castAssignInteger[F, T constraints.Integer](from F, to *T) {
*to = T(from)
}

View file

@ -1,53 +0,0 @@
//go:build linux
package sockopts
import (
"net"
"os"
"os/exec"
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"
)
func Test_fdControlUnixSocketImpl(t *testing.T) {
sockPath := "./fd_control_unix_socket_test.sock"
defer os.Remove(sockPath)
// Run test server
cmd := exec.Command("python", "fd_control_unix_socket_test.py", sockPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if !assert.NoError(t, err) {
return
}
defer cmd.Process.Kill()
// Wait for the server to start
time.Sleep(1 * time.Second)
so := SocketOptions{
FdControlUnixSocket: &sockPath,
}
conn, err := so.ListenUDP()
if !assert.NoError(t, err) {
return
}
defer conn.Close()
err = controlUDPConn(conn.(*net.UDPConn), func(fd int) (err error) {
rcvbuf, err := unix.GetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF)
if err != nil {
return
}
// The test server called setsockopt(fd, SOL_SOCKET, SO_RCVBUF, 2500),
// and kernel will double this value for getsockopt().
assert.Equal(t, 5000, rcvbuf)
return
})
assert.NoError(t, err)
}

View file

@ -1,300 +0,0 @@
package socks5
import (
"encoding/binary"
"io"
"net"
"github.com/txthinking/socks5"
"github.com/apernet/hysteria/core/v2/client"
)
const udpBufferSize = 4096
// Server is a SOCKS5 server using a Hysteria client as outbound.
type Server struct {
HyClient client.Client
AuthFunc func(username, password string) bool // nil = no authentication
DisableUDP bool
EventLogger EventLogger
}
type EventLogger interface {
TCPRequest(addr net.Addr, reqAddr string)
TCPError(addr net.Addr, reqAddr string, err error)
UDPRequest(addr net.Addr)
UDPError(addr net.Addr, err error)
}
func (s *Server) Serve(listener net.Listener) error {
for {
conn, err := listener.Accept()
if err != nil {
return err
}
go s.dispatch(conn)
}
}
func (s *Server) dispatch(conn net.Conn) {
ok, _ := s.negotiate(conn)
if !ok {
_ = conn.Close()
return
}
// Negotiation ok, get and handle the request
req, err := socks5.NewRequestFrom(conn)
if err != nil {
_ = conn.Close()
return
}
switch req.Cmd {
case socks5.CmdConnect: // TCP
s.handleTCP(conn, req)
case socks5.CmdUDP: // UDP
if s.DisableUDP {
_ = sendSimpleReply(conn, socks5.RepCommandNotSupported)
_ = conn.Close()
return
}
s.handleUDP(conn, req)
default:
_ = sendSimpleReply(conn, socks5.RepCommandNotSupported)
_ = conn.Close()
}
}
func (s *Server) negotiate(conn net.Conn) (bool, error) {
req, err := socks5.NewNegotiationRequestFrom(conn)
if err != nil {
return false, err
}
var serverMethod byte
if s.AuthFunc != nil {
serverMethod = socks5.MethodUsernamePassword
} else {
serverMethod = socks5.MethodNone
}
// Look for the supported method in the client request
supported := false
for _, m := range req.Methods {
if m == serverMethod {
supported = true
break
}
}
if !supported {
// No supported method found, reject the client
rep := socks5.NewNegotiationReply(socks5.MethodUnsupportAll)
_, err := rep.WriteTo(conn)
return false, err
}
// OK, send the method we chose
rep := socks5.NewNegotiationReply(serverMethod)
_, err = rep.WriteTo(conn)
if err != nil {
return false, err
}
// If we chose the username/password method, authenticate the client
if serverMethod == socks5.MethodUsernamePassword {
req, err := socks5.NewUserPassNegotiationRequestFrom(conn)
if err != nil {
return false, err
}
ok := s.AuthFunc(string(req.Uname), string(req.Passwd))
if ok {
rep := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusSuccess)
_, err := rep.WriteTo(conn)
if err != nil {
return false, err
}
} else {
rep := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusFailure)
_, err := rep.WriteTo(conn)
return false, err
}
}
return true, nil
}
func (s *Server) handleTCP(conn net.Conn, req *socks5.Request) {
defer conn.Close()
addr := req.Address()
// TCP request & error log
if s.EventLogger != nil {
s.EventLogger.TCPRequest(conn.RemoteAddr(), addr)
}
var closeErr error
defer func() {
if s.EventLogger != nil {
s.EventLogger.TCPError(conn.RemoteAddr(), addr, closeErr)
}
}()
// Dial
rConn, err := s.HyClient.TCP(addr)
if err != nil {
_ = sendSimpleReply(conn, socks5.RepHostUnreachable)
closeErr = err
return
}
defer rConn.Close()
// Send reply and start relaying
_ = sendSimpleReply(conn, socks5.RepSuccess)
copyErrChan := make(chan error, 2)
go func() {
_, err := io.Copy(rConn, conn)
copyErrChan <- err
}()
go func() {
_, err := io.Copy(conn, rConn)
copyErrChan <- err
}()
closeErr = <-copyErrChan
}
func (s *Server) handleUDP(conn net.Conn, req *socks5.Request) {
defer conn.Close()
// UDP request & error log
if s.EventLogger != nil {
s.EventLogger.UDPRequest(conn.RemoteAddr())
}
var closeErr error
defer func() {
if s.EventLogger != nil {
s.EventLogger.UDPError(conn.RemoteAddr(), closeErr)
}
}()
// Start UDP relay server
// SOCKS5 UDP requires the server to return the UDP bind address and port in the reply.
// We bind to the same address that our TCP server listens on (but a different port).
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil {
// Is this even possible?
_ = sendSimpleReply(conn, socks5.RepServerFailure)
closeErr = err
return
}
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(host, "0"))
if err != nil {
_ = sendSimpleReply(conn, socks5.RepServerFailure)
closeErr = err
return
}
udpConn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
_ = sendSimpleReply(conn, socks5.RepServerFailure)
closeErr = err
return
}
defer udpConn.Close()
// HyClient UDP session
hyUDP, err := s.HyClient.UDP()
if err != nil {
_ = sendSimpleReply(conn, socks5.RepServerFailure)
closeErr = err
return
}
defer hyUDP.Close()
// Send reply
_ = sendUDPReply(conn, udpConn.LocalAddr().(*net.UDPAddr))
// UDP relay & SOCKS5 connection holder
errChan := make(chan error, 2)
go func() {
err := s.udpServer(udpConn, hyUDP)
errChan <- err
}()
go func() {
_, err := io.Copy(io.Discard, conn)
errChan <- err
}()
closeErr = <-errChan
}
func (s *Server) udpServer(udpConn *net.UDPConn, hyUDP client.HyUDPConn) error {
var clientAddr *net.UDPAddr
buf := make([]byte, udpBufferSize)
// local -> remote
for {
n, cAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
return err
}
d, err := socks5.NewDatagramFromBytes(buf[:n])
if err != nil || d.Frag != 0 {
// Ignore bad packets
// Also we don't support SOCKS5 UDP fragmentation for now
continue
}
if clientAddr == nil {
// Before the first packet, we don't know what IP the client will use to send us packets,
// so we don't know what IP to return packets to.
// We treat whoever sends us the first packet as our client.
clientAddr = cAddr
// Now that we know the client's address, we can start the
// remote -> local direction.
go func() {
for {
bs, from, err := hyUDP.Receive()
if err != nil {
// Close the UDP conn so that the local -> remote direction will exit
_ = udpConn.Close()
return
}
atyp, addr, port, err := socks5.ParseAddress(from)
if err != nil {
continue
}
if atyp == socks5.ATYPDomain {
// socks5.ParseAddress adds a leading byte for domains,
// but socks5.NewDatagram will add it again as it expects a raw domain.
// So we must remove it here.
addr = addr[1:]
}
d := socks5.NewDatagram(atyp, addr, port, bs)
_, _ = udpConn.WriteToUDP(d.Bytes(), clientAddr)
}
}()
} else if !clientAddr.IP.Equal(cAddr.IP) || clientAddr.Port != cAddr.Port {
// Not our client, ignore
continue
}
// Send to remote
_ = hyUDP.Send(d.Data, d.Address())
}
}
// sendSimpleReply sends a SOCKS5 reply with the given reply code.
// It does not contain bind address or port, so it's not suitable for successful UDP requests.
func sendSimpleReply(conn net.Conn, rep byte) error {
p := socks5.NewReply(rep, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
_, err := p.WriteTo(conn)
return err
}
// sendUDPReply sends a SOCKS5 reply with the given reply code and bind address/port.
func sendUDPReply(conn net.Conn, addr *net.UDPAddr) error {
var atyp byte
var bndAddr, bndPort []byte
if ip4 := addr.IP.To4(); ip4 != nil {
atyp = socks5.ATYPIPv4
bndAddr = ip4
} else {
atyp = socks5.ATYPIPv6
bndAddr = addr.IP
}
bndPort = make([]byte, 2)
binary.BigEndian.PutUint16(bndPort, uint16(addr.Port))
p := socks5.NewReply(socks5.RepSuccess, atyp, bndAddr, bndPort)
_, err := p.WriteTo(conn)
return err
}

View file

@ -1,29 +0,0 @@
package socks5
import (
"net"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/app/v2/internal/utils_test"
)
func TestServer(t *testing.T) {
// Start the server
l, err := net.Listen("tcp", "127.0.0.1:11080")
assert.NoError(t, err)
defer l.Close()
s := &Server{
HyClient: &utils_test.MockEchoHyClient{},
}
go s.Serve(l)
// Run the Python test script
cmd := exec.Command("python", "server_test.py")
out, err := cmd.CombinedOutput()
assert.NoError(t, err)
assert.Equal(t, "OK", strings.TrimSpace(string(out)))
}

View file

@ -1,57 +0,0 @@
import socket
import socks
import os
ADDR = "127.0.0.1"
PORT = 11080
def test_tcp(size, count, it, domain=False):
for i in range(it):
s = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
s.set_proxy(socks.SOCKS5, ADDR, PORT)
if domain:
s.connect(("test.tcp.com", 12345))
else:
s.connect(("1.2.3.4", 12345))
for j in range(count):
payload = os.urandom(size)
s.send(payload)
rsp = s.recv(size)
assert rsp == payload
s.close()
def test_udp(size, count, it, domain=False):
for i in range(it):
s = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM)
s.set_proxy(socks.SOCKS5, ADDR, PORT)
for j in range(count):
payload = os.urandom(size)
if domain:
s.sendto(payload, ("test.udp.com", 12345))
else:
s.sendto(payload, ("1.2.3.4", 12345))
rsp, addr = s.recvfrom(size)
assert rsp == payload
if domain:
assert addr == (b"test.udp.com", 12345)
else:
assert addr == ("1.2.3.4", 12345)
s.close()
if __name__ == "__main__":
test_tcp(1024, 1024, 10, domain=False)
test_tcp(1024, 1024, 10, domain=True)
test_udp(1024, 1024, 10, domain=False)
test_udp(1024, 1024, 10, domain=True)
print("OK")

View file

@ -1,69 +0,0 @@
package tproxy
import (
"io"
"net"
"github.com/apernet/go-tproxy"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPTProxy struct {
HyClient client.Client
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *TCPTProxy) ListenAndServe(laddr *net.TCPAddr) error {
listener, err := tproxy.ListenTCP("tcp", laddr)
if err != nil {
return err
}
defer listener.Close()
for {
c, err := listener.Accept()
if err != nil {
return err
}
go r.handle(c)
}
}
func (r *TCPTProxy) handle(conn net.Conn) {
defer conn.Close()
// In TProxy mode, we are masquerading as the remote server.
// So LocalAddr is actually the target the user is trying to connect to,
// and RemoteAddr is the local address.
if r.EventLogger != nil {
r.EventLogger.Connect(conn.RemoteAddr(), conn.LocalAddr())
}
var closeErr error
defer func() {
if r.EventLogger != nil {
r.EventLogger.Error(conn.RemoteAddr(), conn.LocalAddr(), closeErr)
}
}()
rc, err := r.HyClient.TCP(conn.LocalAddr().String())
if err != nil {
closeErr = err
return
}
defer rc.Close()
// Start forwarding
copyErrChan := make(chan error, 2)
go func() {
_, copyErr := io.Copy(rc, conn)
copyErrChan <- copyErr
}()
go func() {
_, copyErr := io.Copy(conn, rc)
copyErrChan <- copyErr
}()
closeErr = <-copyErrChan
}

View file

@ -1,24 +0,0 @@
//go:build !linux
package tproxy
import (
"errors"
"net"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPTProxy struct {
HyClient client.Client
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *TCPTProxy) ListenAndServe(laddr *net.TCPAddr) error {
return errors.New("not supported on this platform")
}

View file

@ -1,140 +0,0 @@
package tproxy
import (
"errors"
"net"
"time"
"github.com/apernet/go-tproxy"
"github.com/apernet/hysteria/core/v2/client"
)
const (
udpBufferSize = 4096
defaultTimeout = 60 * time.Second
)
type UDPTProxy struct {
HyClient client.Client
Timeout time.Duration
EventLogger UDPEventLogger
}
type UDPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *UDPTProxy) ListenAndServe(laddr *net.UDPAddr) error {
conn, err := tproxy.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer conn.Close()
buf := make([]byte, udpBufferSize)
for {
// We will only get the first packet of each src/dst pair here,
// because newPair will create a TProxy connection and take over
// the src/dst pair. Later packets will be sent there instead of here.
n, srcAddr, dstAddr, err := tproxy.ReadFromUDP(conn, buf)
if err != nil {
return err
}
r.newPair(srcAddr, dstAddr, buf[:n])
}
}
func (r *UDPTProxy) newPair(srcAddr, dstAddr *net.UDPAddr, initPkt []byte) {
if r.EventLogger != nil {
r.EventLogger.Connect(srcAddr, dstAddr)
}
var closeErr error
defer func() {
// If closeErr is nil, it means we at least successfully sent the first packet
// and started forwarding, in which case we don't call the error logger.
if r.EventLogger != nil && closeErr != nil {
r.EventLogger.Error(srcAddr, dstAddr, closeErr)
}
}()
conn, err := tproxy.DialUDP("udp", dstAddr, srcAddr)
if err != nil {
closeErr = err
return
}
hyConn, err := r.HyClient.UDP()
if err != nil {
_ = conn.Close()
closeErr = err
return
}
// Send the first packet
err = hyConn.Send(initPkt, dstAddr.String())
if err != nil {
_ = conn.Close()
_ = hyConn.Close()
closeErr = err
return
}
// Start forwarding
go func() {
err := r.forwarding(conn, hyConn, dstAddr.String())
_ = conn.Close()
_ = hyConn.Close()
if r.EventLogger != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
// We don't consider deadline exceeded (timeout) an error
err = nil
}
r.EventLogger.Error(srcAddr, dstAddr, err)
}
}()
}
func (r *UDPTProxy) forwarding(conn *net.UDPConn, hyConn client.HyUDPConn, dst string) error {
errChan := make(chan error, 2)
// Local <- Remote
go func() {
for {
bs, _, err := hyConn.Receive()
if err != nil {
errChan <- err
return
}
_, err = conn.Write(bs)
if err != nil {
errChan <- err
return
}
_ = r.updateConnDeadline(conn)
}
}()
// Local -> Remote
go func() {
buf := make([]byte, udpBufferSize)
for {
_ = r.updateConnDeadline(conn)
n, err := conn.Read(buf)
if n > 0 {
err := hyConn.Send(buf[:n], dst)
if err != nil {
errChan <- err
return
}
}
if err != nil {
errChan <- err
return
}
}
}()
return <-errChan
}
func (r *UDPTProxy) updateConnDeadline(conn *net.UDPConn) error {
if r.Timeout == 0 {
return conn.SetReadDeadline(time.Now().Add(defaultTimeout))
} else {
return conn.SetReadDeadline(time.Now().Add(r.Timeout))
}
}

View file

@ -1,26 +0,0 @@
//go:build !linux
package tproxy
import (
"errors"
"net"
"time"
"github.com/apernet/hysteria/core/v2/client"
)
type UDPTProxy struct {
HyClient client.Client
Timeout time.Duration
EventLogger UDPEventLogger
}
type UDPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *UDPTProxy) ListenAndServe(laddr *net.UDPAddr) error {
return errors.New("not supported on this platform")
}

View file

@ -1,14 +0,0 @@
//go:build !unix && !windows
package tun
import "net"
func isIPv6Supported() bool {
lis, err := net.ListenPacket("udp6", "[::1]:0")
if err != nil {
return false
}
_ = lis.Close()
return true
}

View file

@ -1,16 +0,0 @@
//go:build unix
package tun
import (
"golang.org/x/sys/unix"
)
func isIPv6Supported() bool {
sock, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
if err != nil {
return false
}
_ = unix.Close(sock)
return true
}

View file

@ -1,24 +0,0 @@
//go:build windows
package tun
import (
"golang.org/x/sys/windows"
)
func isIPv6Supported() bool {
var wsaData windows.WSAData
err := windows.WSAStartup(uint32(0x202), &wsaData)
if err != nil {
// Failing silently: it is not our duty to report such errors
return true
}
defer windows.WSACleanup()
sock, err := windows.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
if err != nil {
return false
}
_ = windows.Closesocket(sock)
return true
}

View file

@ -1,77 +0,0 @@
package tun
import (
"github.com/sagernet/sing/common/logger"
"go.uber.org/zap"
)
var _ logger.Logger = (*singLogger)(nil)
type singLogger struct {
tag string
zapLogger *zap.Logger
}
func extractSingExceptions(args []any) {
for i, arg := range args {
if err, ok := arg.(error); ok {
args[i] = err.Error()
}
}
}
func (l *singLogger) Trace(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Debug(l.tag, zap.Any("args", args))
}
func (l *singLogger) Debug(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Debug(l.tag, zap.Any("args", args))
}
func (l *singLogger) Info(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Info(l.tag, zap.Any("args", args))
}
func (l *singLogger) Warn(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Warn(l.tag, zap.Any("args", args))
}
func (l *singLogger) Error(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Error(l.tag, zap.Any("args", args))
}
func (l *singLogger) Fatal(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Fatal(l.tag, zap.Any("args", args))
}
func (l *singLogger) Panic(args ...any) {
if l.zapLogger == nil {
return
}
extractSingExceptions(args)
l.zapLogger.Panic(l.tag, zap.Any("args", args))
}

View file

@ -1,234 +0,0 @@
package tun
import (
"context"
"fmt"
"io"
"net"
"net/netip"
tun "github.com/apernet/sing-tun"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/network"
"go.uber.org/zap"
"github.com/apernet/hysteria/core/v2/client"
)
type Server struct {
HyClient client.Client
EventLogger EventLogger
// for debugging
Logger *zap.Logger
IfName string
MTU uint32
Timeout int64 // in seconds, also applied to TCP in system stack
// required by system stack
Inet4Address []netip.Prefix
Inet6Address []netip.Prefix
// auto route
AutoRoute bool
StructRoute bool
Inet4RouteAddress []netip.Prefix
Inet6RouteAddress []netip.Prefix
Inet4RouteExcludeAddress []netip.Prefix
Inet6RouteExcludeAddress []netip.Prefix
}
type EventLogger interface {
TCPRequest(addr, reqAddr string)
TCPError(addr, reqAddr string, err error)
UDPRequest(addr string)
UDPError(addr string, err error)
}
func (s *Server) Serve() error {
if !isIPv6Supported() {
s.Logger.Warn("tun-pre-check", zap.String("msg", "IPv6 is not supported or enabled on this system, TUN device is created without IPv6 support."))
s.Inet6Address = nil
}
tunOpts := tun.Options{
Name: s.IfName,
Inet4Address: s.Inet4Address,
Inet6Address: s.Inet6Address,
MTU: s.MTU,
GSO: true,
AutoRoute: s.AutoRoute,
StrictRoute: s.StructRoute,
Inet4RouteAddress: s.Inet4RouteAddress,
Inet6RouteAddress: s.Inet6RouteAddress,
Inet4RouteExcludeAddress: s.Inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: s.Inet6RouteExcludeAddress,
Logger: &singLogger{
tag: "tun",
zapLogger: s.Logger,
},
}
tunIf, err := tun.New(tunOpts)
if err != nil {
return fmt.Errorf("failed to create tun interface: %w", err)
}
defer tunIf.Close()
tunStack, err := tun.NewSystem(tun.StackOptions{
Context: context.Background(),
Tun: tunIf,
TunOptions: tunOpts,
UDPTimeout: s.Timeout,
Handler: &tunHandler{s},
Logger: &singLogger{
tag: "tun-stack",
zapLogger: s.Logger,
},
ForwarderBindInterface: true,
InterfaceFinder: &interfaceFinder{},
})
if err != nil {
return fmt.Errorf("failed to create tun stack: %w", err)
}
defer tunStack.Close()
return tunStack.(tun.StackRunner).Run()
}
type tunHandler struct {
*Server
}
var _ tun.Handler = (*tunHandler)(nil)
func (t *tunHandler) NewConnection(ctx context.Context, conn net.Conn, m metadata.Metadata) error {
addr := m.Source.String()
reqAddr := m.Destination.String()
if t.EventLogger != nil {
t.EventLogger.TCPRequest(addr, reqAddr)
}
var closeErr error
defer func() {
if t.EventLogger != nil {
t.EventLogger.TCPError(addr, reqAddr, closeErr)
}
}()
rc, err := t.HyClient.TCP(reqAddr)
if err != nil {
closeErr = err
// the returned err is ignored by caller
return nil
}
defer rc.Close()
// start forwarding
copyErrChan := make(chan error, 3)
go func() {
<-ctx.Done()
copyErrChan <- ctx.Err()
}()
go func() {
_, copyErr := io.Copy(rc, conn)
copyErrChan <- copyErr
}()
go func() {
_, copyErr := io.Copy(conn, rc)
copyErrChan <- copyErr
}()
closeErr = <-copyErrChan
return nil
}
func (t *tunHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, m metadata.Metadata) error {
addr := m.Source.String()
if t.EventLogger != nil {
t.EventLogger.UDPRequest(addr)
}
var closeErr error
defer func() {
if t.EventLogger != nil {
t.EventLogger.UDPError(addr, closeErr)
}
}()
rc, err := t.HyClient.UDP()
if err != nil {
closeErr = err
// the returned err is simply called into NewError again
return nil
}
defer rc.Close()
// start forwarding
copyErrChan := make(chan error, 3)
go func() {
<-ctx.Done()
copyErrChan <- ctx.Err()
}()
// local <- remote
go func() {
for {
bs, from, err := rc.Receive()
if err != nil {
copyErrChan <- err
return
}
var fromAddr metadata.Socksaddr
if ap, perr := netip.ParseAddrPort(from); perr == nil {
fromAddr = metadata.SocksaddrFromNetIP(ap)
} else {
fromAddr.Fqdn = from
}
err = conn.WritePacket(buf.As(bs), fromAddr)
if err != nil {
copyErrChan <- err
return
}
}
}()
// local -> remote
go func() {
buffer := buf.NewPacket()
defer buffer.Release()
for {
buffer.Reset()
addr, err := conn.ReadPacket(buffer)
if err != nil {
copyErrChan <- err
return
}
err = rc.Send(buffer.Bytes(), addr.String())
if err != nil {
copyErrChan <- err
return
}
}
}()
closeErr = <-copyErrChan
return nil
}
func (t *tunHandler) NewError(ctx context.Context, err error) {
// unused
}
type interfaceFinder struct{}
var _ control.InterfaceFinder = (*interfaceFinder)(nil)
func (f *interfaceFinder) InterfaceIndexByName(name string) (int, error) {
ifce, err := net.InterfaceByName(name)
if err != nil {
return -1, err
}
return ifce.Index, nil
}
func (f *interfaceFinder) InterfaceNameByIndex(index int) (string, error) {
ifce, err := net.InterfaceByIndex(index)
if err != nil {
return "", err
}
return ifce.Name, nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,91 +0,0 @@
package url
import (
"reflect"
"testing"
)
func TestParse(t *testing.T) {
type args struct {
rawURL string
}
tests := []struct {
name string
args args
want *URL
wantErr bool
}{
{
name: "no port",
args: args{
rawURL: "hysteria2://ganggang@icecreamsogood/",
},
want: &URL{
Scheme: "hysteria2",
User: User("ganggang"),
Host: "icecreamsogood",
Path: "/",
},
},
{
name: "single port",
args: args{
rawURL: "hysteria2://yesyes@icecreamsogood:8888/",
},
want: &URL{
Scheme: "hysteria2",
User: User("yesyes"),
Host: "icecreamsogood:8888",
Path: "/",
},
},
{
name: "multi port",
args: args{
rawURL: "hysteria2://darkness@laplus.org:8888,9999,11111/",
},
want: &URL{
Scheme: "hysteria2",
User: User("darkness"),
Host: "laplus.org:8888,9999,11111",
Path: "/",
},
},
{
name: "range port",
args: args{
rawURL: "hysteria2://darkness@laplus.org:8888-9999/",
},
want: &URL{
Scheme: "hysteria2",
User: User("darkness"),
Host: "laplus.org:8888-9999",
Path: "/",
},
},
{
name: "both",
args: args{
rawURL: "hysteria2://gawr:gura@atlantis.moe:443,7788-8899,10010/",
},
want: &URL{
Scheme: "hysteria2",
User: UserPassword("gawr", "gura"),
Host: "atlantis.moe:443,7788-8899,10010",
Path: "/",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Parse(tt.args.rawURL)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Parse() got = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,68 +0,0 @@
package utils
import (
"errors"
"fmt"
"strconv"
"strings"
)
const (
Byte = 1
Kilobyte = Byte * 1000
Megabyte = Kilobyte * 1000
Gigabyte = Megabyte * 1000
Terabyte = Gigabyte * 1000
)
// StringToBps converts a string to a bandwidth value in bytes per second.
// E.g. "100 Mbps", "512 kbps", "1g" are all valid.
func StringToBps(s string) (uint64, error) {
s = strings.ToLower(strings.TrimSpace(s))
spl := 0
for i, c := range s {
if c < '0' || c > '9' {
spl = i
break
}
}
if spl == 0 {
// No unit or no value
return 0, errors.New("invalid format")
}
v, err := strconv.ParseUint(s[:spl], 10, 64)
if err != nil {
return 0, err
}
unit := strings.TrimSpace(s[spl:])
switch strings.ToLower(unit) {
case "b", "bps":
return v * Byte / 8, nil
case "k", "kb", "kbps":
return v * Kilobyte / 8, nil
case "m", "mb", "mbps":
return v * Megabyte / 8, nil
case "g", "gb", "gbps":
return v * Gigabyte / 8, nil
case "t", "tb", "tbps":
return v * Terabyte / 8, nil
default:
return 0, errors.New("unsupported unit")
}
}
// ConvBandwidth handles both string and int types for bandwidth.
// When using string, it will be parsed as a bandwidth string with units.
// When using int, it will be parsed as a raw bandwidth in bytes per second.
// It does NOT support float types.
func ConvBandwidth(bw interface{}) (uint64, error) {
switch bwT := bw.(type) {
case string:
return StringToBps(bwT)
case int:
return uint64(bwT), nil
default:
return 0, fmt.Errorf("invalid type %T for bandwidth", bwT)
}
}

View file

@ -1,40 +0,0 @@
package utils
import "testing"
func TestStringToBps(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
want uint64
wantErr bool
}{
{"bps", args{"800 bps"}, 100, false},
{"kbps", args{"800 kbps"}, 100_000, false},
{"mbps", args{"800 mbps"}, 100_000_000, false},
{"gbps", args{"800 gbps"}, 100_000_000_000, false},
{"tbps", args{"800 tbps"}, 100_000_000_000_000, false},
{"mbps simp", args{"100m"}, 12_500_000, false},
{"gbps simp upper", args{"2G"}, 250_000_000, false},
{"invalid 1", args{"damn"}, 0, true},
{"invalid 2", args{"6444"}, 0, true},
{"invalid 3", args{"5.4 mbps"}, 0, true},
{"invalid 4", args{"kbps"}, 0, true},
{"invalid 5", args{"1234 5678 gbps"}, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := StringToBps(tt.args.s)
if (err != nil) != tt.wantErr {
t.Errorf("StringToBps() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("StringToBps() got = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,198 +0,0 @@
package utils
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"os"
"strings"
"sync"
"sync/atomic"
"time"
)
type LocalCertificateLoader struct {
CertFile string
KeyFile string
SNIGuard SNIGuardFunc
lock sync.Mutex
cache atomic.Pointer[localCertificateCache]
}
type SNIGuardFunc func(info *tls.ClientHelloInfo, cert *tls.Certificate) error
// localCertificateCache holds the certificate and its mod times.
// this struct is designed to be read-only.
//
// to update the cache, use LocalCertificateLoader.makeCache and
// update the LocalCertificateLoader.cache field.
type localCertificateCache struct {
certificate *tls.Certificate
certModTime time.Time
keyModTime time.Time
}
func (l *LocalCertificateLoader) InitializeCache() error {
l.lock.Lock()
defer l.lock.Unlock()
cache, err := l.makeCache()
if err != nil {
return err
}
l.cache.Store(cache)
return nil
}
func (l *LocalCertificateLoader) GetCertificate(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := l.getCertificateWithCache()
if err != nil {
return nil, err
}
if l.SNIGuard == nil {
return cert, nil
}
err = l.SNIGuard(info, cert)
if err != nil {
return nil, err
}
return cert, nil
}
func (l *LocalCertificateLoader) checkModTime() (certModTime, keyModTime time.Time, err error) {
fi, err := os.Stat(l.CertFile)
if err != nil {
err = fmt.Errorf("failed to stat certificate file: %w", err)
return
}
certModTime = fi.ModTime()
fi, err = os.Stat(l.KeyFile)
if err != nil {
err = fmt.Errorf("failed to stat key file: %w", err)
return
}
keyModTime = fi.ModTime()
return
}
func (l *LocalCertificateLoader) makeCache() (cache *localCertificateCache, err error) {
c := &localCertificateCache{}
c.certModTime, c.keyModTime, err = l.checkModTime()
if err != nil {
return
}
cert, err := tls.LoadX509KeyPair(l.CertFile, l.KeyFile)
if err != nil {
return
}
c.certificate = &cert
if c.certificate.Leaf == nil {
// certificate.Leaf was left nil by tls.LoadX509KeyPair before Go 1.23
c.certificate.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return
}
}
cache = c
return
}
func (l *LocalCertificateLoader) getCertificateWithCache() (*tls.Certificate, error) {
cache := l.cache.Load()
certModTime, keyModTime, terr := l.checkModTime()
if terr != nil {
if cache != nil {
// use cache when file is temporarily unavailable
return cache.certificate, nil
}
return nil, terr
}
if cache != nil && cache.certModTime.Equal(certModTime) && cache.keyModTime.Equal(keyModTime) {
// cache is up-to-date
return cache.certificate, nil
}
if cache != nil {
if !l.lock.TryLock() {
// another goroutine is updating the cache
return cache.certificate, nil
}
} else {
l.lock.Lock()
}
defer l.lock.Unlock()
if l.cache.Load() != cache {
// another goroutine updated the cache
return l.cache.Load().certificate, nil
}
newCache, err := l.makeCache()
if err != nil {
if cache != nil {
// use cache when loading failed
return cache.certificate, nil
}
return nil, err
}
l.cache.Store(newCache)
return newCache.certificate, nil
}
// getNameFromClientHello returns a normalized form of hello.ServerName.
// If hello.ServerName is empty (i.e. client did not use SNI), then the
// associated connection's local address is used to extract an IP address.
//
// ref: https://github.com/caddyserver/certmagic/blob/3bad5b6bb595b09c14bd86ff0b365d302faaf5e2/handshake.go#L838
func getNameFromClientHello(hello *tls.ClientHelloInfo) string {
normalizedName := func(serverName string) string {
return strings.ToLower(strings.TrimSpace(serverName))
}
localIPFromConn := func(c net.Conn) string {
if c == nil {
return ""
}
localAddr := c.LocalAddr().String()
ip, _, err := net.SplitHostPort(localAddr)
if err != nil {
ip = localAddr
}
if scopeIDStart := strings.Index(ip, "%"); scopeIDStart > -1 {
ip = ip[:scopeIDStart]
}
return ip
}
if name := normalizedName(hello.ServerName); name != "" {
return name
}
return localIPFromConn(hello.Conn)
}
func SNIGuardDNSSAN(info *tls.ClientHelloInfo, cert *tls.Certificate) error {
if len(cert.Leaf.DNSNames) == 0 {
return nil
}
return SNIGuardStrict(info, cert)
}
func SNIGuardStrict(info *tls.ClientHelloInfo, cert *tls.Certificate) error {
hostname := getNameFromClientHello(info)
err := cert.Leaf.VerifyHostname(hostname)
if err != nil {
return fmt.Errorf("sni guard: %w", err)
}
return nil
}

View file

@ -1,139 +0,0 @@
package utils
import (
"crypto/tls"
"log"
"net/http"
"os"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
const (
testListen = "127.82.39.147:12947"
testCAFile = "./testcerts/ca"
testCertFile = "./testcerts/cert"
testKeyFile = "./testcerts/key"
)
func TestCertificateLoaderPathError(t *testing.T) {
assert.NoError(t, os.RemoveAll(testCertFile))
assert.NoError(t, os.RemoveAll(testKeyFile))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardStrict,
}
err := loader.InitializeCache()
var pathErr *os.PathError
assert.ErrorAs(t, err, &pathErr)
}
func TestCertificateLoaderFullChain(t *testing.T) {
assert.NoError(t, generateTestCertificate([]string{"example.com"}, "fullchain"))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardStrict,
}
assert.NoError(t, loader.InitializeCache())
lis, err := tls.Listen("tcp", testListen, &tls.Config{
GetCertificate: loader.GetCertificate,
})
assert.NoError(t, err)
defer lis.Close()
go http.Serve(lis, nil)
assert.Error(t, runTestTLSClient("unmatched-sni.example.com"))
assert.Error(t, runTestTLSClient(""))
assert.NoError(t, runTestTLSClient("example.com"))
}
func TestCertificateLoaderNoSAN(t *testing.T) {
assert.NoError(t, generateTestCertificate(nil, "selfsign"))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardDNSSAN,
}
assert.NoError(t, loader.InitializeCache())
lis, err := tls.Listen("tcp", testListen, &tls.Config{
GetCertificate: loader.GetCertificate,
})
assert.NoError(t, err)
defer lis.Close()
go http.Serve(lis, nil)
assert.NoError(t, runTestTLSClient(""))
}
func TestCertificateLoaderReplaceCertificate(t *testing.T) {
assert.NoError(t, generateTestCertificate([]string{"example.com"}, "fullchain"))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardStrict,
}
assert.NoError(t, loader.InitializeCache())
lis, err := tls.Listen("tcp", testListen, &tls.Config{
GetCertificate: loader.GetCertificate,
})
assert.NoError(t, err)
defer lis.Close()
go http.Serve(lis, nil)
assert.NoError(t, runTestTLSClient("example.com"))
assert.Error(t, runTestTLSClient("2.example.com"))
assert.NoError(t, generateTestCertificate([]string{"2.example.com"}, "fullchain"))
assert.Error(t, runTestTLSClient("example.com"))
assert.NoError(t, runTestTLSClient("2.example.com"))
}
func generateTestCertificate(dnssan []string, certType string) error {
args := []string{
"certloader_test_gencert.py",
"--ca", testCAFile,
"--cert", testCertFile,
"--key", testKeyFile,
"--type", certType,
}
if len(dnssan) > 0 {
args = append(args, "--dnssan", strings.Join(dnssan, ","))
}
cmd := exec.Command("python", args...)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Failed to generate test certificate: %s", out)
return err
}
return nil
}
func runTestTLSClient(sni string) error {
args := []string{
"certloader_test_tlsclient.py",
"--server", testListen,
"--ca", testCAFile,
}
if sni != "" {
args = append(args, "--sni", sni)
}
cmd := exec.Command("python", args...)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Failed to run test TLS client: %s", out)
return err
}
return nil
}

View file

@ -1,134 +0,0 @@
import argparse
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
def create_key():
return ec.generate_private_key(ec.SECP256R1())
def create_certificate(cert_type, subject, issuer, private_key, public_key, dns_san=None):
serial_number = x509.random_serial_number()
not_valid_before = datetime.datetime.now(datetime.UTC)
not_valid_after = not_valid_before + datetime.timedelta(days=365)
subject_name = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, subject.get('C', 'ZZ')),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, subject.get('O', 'No Organization')),
x509.NameAttribute(NameOID.COMMON_NAME, subject.get('CN', 'No CommonName')),
])
issuer_name = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, issuer.get('C', 'ZZ')),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, issuer.get('O', 'No Organization')),
x509.NameAttribute(NameOID.COMMON_NAME, issuer.get('CN', 'No CommonName')),
])
builder = x509.CertificateBuilder()
builder = builder.subject_name(subject_name)
builder = builder.issuer_name(issuer_name)
builder = builder.public_key(public_key)
builder = builder.serial_number(serial_number)
builder = builder.not_valid_before(not_valid_before)
builder = builder.not_valid_after(not_valid_after)
if cert_type == 'root':
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True
)
elif cert_type == 'intermediate':
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=0), critical=True
)
elif cert_type == 'leaf':
builder = builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None), critical=True
)
else:
raise ValueError(f'Invalid cert_type: {cert_type}')
if dns_san:
builder = builder.add_extension(
x509.SubjectAlternativeName([x509.DNSName(d) for d in dns_san.split(',')]),
critical=False
)
return builder.sign(private_key=private_key, algorithm=hashes.SHA256())
def main():
parser = argparse.ArgumentParser(description='Generate HTTPS server certificate.')
parser.add_argument('--ca', required=True,
help='Path to write the X509 CA certificate in PEM format')
parser.add_argument('--cert', required=True,
help='Path to write the X509 certificate in PEM format')
parser.add_argument('--key', required=True,
help='Path to write the private key in PEM format')
parser.add_argument('--dnssan', required=False, default=None,
help='Comma-separated list of DNS SANs')
parser.add_argument('--type', required=True, choices=['selfsign', 'fullchain'],
help='Type of certificate to generate')
args = parser.parse_args()
key = create_key()
public_key = key.public_key()
if args.type == 'selfsign':
subject = {"C": "ZZ", "O": "Certificate", "CN": "Certificate"}
cert = create_certificate(
cert_type='root',
subject=subject,
issuer=subject,
private_key=key,
public_key=public_key,
dns_san=args.dnssan)
with open(args.ca, 'wb') as f:
f.write(cert.public_bytes(Encoding.PEM))
with open(args.cert, 'wb') as f:
f.write(cert.public_bytes(Encoding.PEM))
with open(args.key, 'wb') as f:
f.write(
key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
elif args.type == 'fullchain':
ca_key = create_key()
ca_public_key = ca_key.public_key()
ca_subject = {"C": "ZZ", "O": "Root CA", "CN": "Root CA"}
ca_cert = create_certificate(
cert_type='root',
subject=ca_subject,
issuer=ca_subject,
private_key=ca_key,
public_key=ca_public_key)
intermediate_key = create_key()
intermediate_public_key = intermediate_key.public_key()
intermediate_subject = {"C": "ZZ", "O": "Intermediate CA", "CN": "Intermediate CA"}
intermediate_cert = create_certificate(
cert_type='intermediate',
subject=intermediate_subject,
issuer=ca_subject,
private_key=ca_key,
public_key=intermediate_public_key)
leaf_subject = {"C": "ZZ", "O": "Leaf Certificate", "CN": "Leaf Certificate"}
cert = create_certificate(
cert_type='leaf',
subject=leaf_subject,
issuer=intermediate_subject,
private_key=intermediate_key,
public_key=public_key,
dns_san=args.dnssan)
with open(args.ca, 'wb') as f:
f.write(ca_cert.public_bytes(Encoding.PEM))
with open(args.cert, 'wb') as f:
f.write(cert.public_bytes(Encoding.PEM))
f.write(intermediate_cert.public_bytes(Encoding.PEM))
with open(args.key, 'wb') as f:
f.write(
key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
if __name__ == "__main__":
main()

View file

@ -1,60 +0,0 @@
import argparse
import ssl
import socket
import sys
def check_tls(server, ca_cert, sni, alpn):
try:
host, port = server.split(":")
port = int(port)
if ca_cert:
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=ca_cert)
context.check_hostname = sni is not None
context.verify_mode = ssl.CERT_REQUIRED
else:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
if alpn:
context.set_alpn_protocols([p for p in alpn.split(",")])
with socket.create_connection((host, port)) as sock:
with context.wrap_socket(sock, server_hostname=sni) as ssock:
# Verify handshake and certificate
print(f'Connected to {ssock.version()} using {ssock.cipher()}')
print(f'Server certificate validated and details: {ssock.getpeercert()}')
print("OK")
return 0
except Exception as e:
print(f"Error: {e}")
return 1
def main():
parser = argparse.ArgumentParser(description="Test TLS Server")
parser.add_argument("--server", required=True,
help="Server address to test (e.g., 127.1.2.3:8443)")
parser.add_argument("--ca", required=False, default=None,
help="CA certificate file used to validate the server certificate"
"Omit to use insecure connection")
parser.add_argument("--sni", required=False, default=None,
help="SNI to send in ClientHello")
parser.add_argument("--alpn", required=False, default='h2',
help="ALPN to send in ClientHello")
args = parser.parse_args()
exit_status = check_tls(
server=args.server,
ca_cert=args.ca,
sni=args.sni,
alpn=args.alpn)
sys.exit(exit_status)
if __name__ == "__main__":
main()

View file

@ -1,172 +0,0 @@
package utils
import (
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/apernet/hysteria/extras/v2/outbounds/acl"
"github.com/apernet/hysteria/extras/v2/outbounds/acl/v2geo"
)
const (
geoipFilename = "geoip.dat"
geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
geositeFilename = "geosite.dat"
geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
geoDlTmpPattern = ".hysteria-geoloader.dlpart.*"
geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days
)
var _ acl.GeoLoader = (*GeoLoader)(nil)
// GeoLoader provides the on-demand GeoIP/GeoSite database
// loading functionality required by the ACL engine.
// Empty filenames = automatic download from built-in URLs.
type GeoLoader struct {
GeoIPFilename string
GeoSiteFilename string
UpdateInterval time.Duration
DownloadFunc func(filename, url string)
DownloadErrFunc func(err error)
geoipMap map[string]*v2geo.GeoIP
geositeMap map[string]*v2geo.GeoSite
}
func (l *GeoLoader) shouldDownload(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return true
}
if info.Size() == 0 {
// empty files are loadable by v2geo, but we consider it broken
return true
}
dt := time.Now().Sub(info.ModTime())
if l.UpdateInterval == 0 {
return dt > geoDefaultUpdateInterval
} else {
return dt > l.UpdateInterval
}
}
func (l *GeoLoader) downloadAndCheck(filename, url string, checkFunc func(filename string) error) error {
l.DownloadFunc(filename, url)
resp, err := http.Get(url)
if err != nil {
l.DownloadErrFunc(err)
return err
}
defer resp.Body.Close()
f, err := os.CreateTemp(".", geoDlTmpPattern)
if err != nil {
l.DownloadErrFunc(err)
return err
}
defer os.Remove(f.Name())
_, err = io.Copy(f, resp.Body)
if err != nil {
f.Close()
l.DownloadErrFunc(err)
return err
}
f.Close()
err = checkFunc(f.Name())
if err != nil {
l.DownloadErrFunc(fmt.Errorf("integrity check failed: %w", err))
return err
}
err = os.Rename(f.Name(), filename)
if err != nil {
l.DownloadErrFunc(fmt.Errorf("rename failed: %w", err))
return err
}
return nil
}
func (l *GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) {
if l.geoipMap != nil {
return l.geoipMap, nil
}
autoDL := false
filename := l.GeoIPFilename
if filename == "" {
autoDL = true
filename = geoipFilename
}
if autoDL {
if !l.shouldDownload(filename) {
m, err := v2geo.LoadGeoIP(filename)
if err == nil {
l.geoipMap = m
return m, nil
}
// file is broken, download it again
}
err := l.downloadAndCheck(filename, geoipURL, func(filename string) error {
_, err := v2geo.LoadGeoIP(filename)
return err
})
if err != nil {
// as long as the previous download exists, fallback to it
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
return nil, err
}
}
}
m, err := v2geo.LoadGeoIP(filename)
if err != nil {
return nil, err
}
l.geoipMap = m
return m, nil
}
func (l *GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) {
if l.geositeMap != nil {
return l.geositeMap, nil
}
autoDL := false
filename := l.GeoSiteFilename
if filename == "" {
autoDL = true
filename = geositeFilename
}
if autoDL {
if !l.shouldDownload(filename) {
m, err := v2geo.LoadGeoSite(filename)
if err == nil {
l.geositeMap = m
return m, nil
}
// file is broken, download it again
}
err := l.downloadAndCheck(filename, geositeURL, func(filename string) error {
_, err := v2geo.LoadGeoSite(filename)
return err
})
if err != nil {
// as long as the previous download exists, fallback to it
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
return nil, err
}
}
}
m, err := v2geo.LoadGeoSite(filename)
if err != nil {
return nil, err
}
l.geositeMap = m
return m, nil
}

View file

@ -1,16 +0,0 @@
package utils
import (
"os"
"github.com/mdp/qrterminal/v3"
)
func PrintQR(str string) {
qrterminal.GenerateWithConfig(str, qrterminal.Config{
Level: qrterminal.L,
Writer: os.Stdout,
BlackChar: qrterminal.BLACK,
WhiteChar: qrterminal.WHITE,
})
}

Some files were not shown because too many files have changed in this diff Show more