mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-02 03:57:38 +03:00
hysteria 2 prototype first public release
This commit is contained in:
parent
c0ab06e961
commit
9f54aade8f
139 changed files with 5146 additions and 11657 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +0,0 @@
|
|||
custom: ['https://hysteria.network/docs/donations/']
|
42
.github/ISSUE_TEMPLATE/en_feature_request.yaml
vendored
42
.github/ISSUE_TEMPLATE/en_feature_request.yaml
vendored
|
@ -1,42 +0,0 @@
|
|||
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
136
.github/ISSUE_TEMPLATE/en_newbie.yml
vendored
|
@ -1,136 +0,0 @@
|
|||
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
|
||||
|
43
.github/ISSUE_TEMPLATE/zh_feature_request.yaml
vendored
43
.github/ISSUE_TEMPLATE/zh_feature_request.yaml
vendored
|
@ -1,43 +0,0 @@
|
|||
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
137
.github/ISSUE_TEMPLATE/zh_newbie.yml
vendored
|
@ -1,137 +0,0 @@
|
|||
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
|
||||
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
|
@ -1,10 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
60
.github/workflows/codeql-analysis.yml
vendored
60
.github/workflows/codeql-analysis.yml
vendored
|
@ -1,60 +0,0 @@
|
|||
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
|
||||
|
||||
- name: Run build script
|
||||
run: ./build.sh
|
||||
shell: bash
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
40
.github/workflows/dev-build-master.yml
vendored
40
.github/workflows/dev-build-master.yml
vendored
|
@ -1,40 +0,0 @@
|
|||
name: "Build master"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags-ignore:
|
||||
- 'v*'
|
||||
- 'core/v*'
|
||||
- 'app/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@v4
|
||||
with:
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Run build script
|
||||
env:
|
||||
HY_APP_PLATFORMS: 'darwin/amd64,darwin/amd64-avx,darwin/arm64,windows/amd64,windows/amd64-avx,windows/386,windows/arm64,linux/amd64,linux/amd64-avx,linux/386,linux/arm,linux/armv5,linux/arm64,linux/s390x,linux/mipsle,linux/mipsle-sf,freebsd/amd64,freebsd/amd64-avx,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
|
44
.github/workflows/release-docker.yaml
vendored
44
.github/workflows/release-docker.yaml
vendored
|
@ -1,44 +0,0 @@
|
|||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get tag
|
||||
id: get_tag
|
||||
run: echo "TAG=$(git describe --tags --always --match 'v*')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: tobyxdd/hysteria:latest,tobyxdd/hysteria:${{ steps.get_tag.outputs.TAG }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
65
.github/workflows/release.yml
vendored
65
.github/workflows/release.yml
vendored
|
@ -1,65 +0,0 @@
|
|||
name: Build and release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build and release
|
||||
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@v4
|
||||
with:
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Run build script
|
||||
env:
|
||||
HY_APP_PLATFORMS: 'darwin/amd64,darwin/amd64-avx,darwin/arm64,windows/amd64,windows/amd64-avx,windows/386,windows/arm64,linux/amd64,linux/amd64-avx,linux/386,linux/arm,linux/armv5,linux/arm64,linux/s390x,linux/mipsle,linux/mipsle-sf,freebsd/amd64,freebsd/amd64-avx,freebsd/386,freebsd/arm,freebsd/arm64'
|
||||
run: ./build.sh
|
||||
shell: bash
|
||||
|
||||
- name: Generate hashes
|
||||
run: |
|
||||
cd build
|
||||
for f in $(find . -type f); do
|
||||
sha256sum $f | sudo tee -a hashes.txt
|
||||
done
|
||||
|
||||
- name: Upload
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
./build/hysteria-darwin-amd64
|
||||
./build/hysteria-darwin-amd64-avx
|
||||
./build/hysteria-darwin-arm64
|
||||
./build/hysteria-windows-amd64.exe
|
||||
./build/hysteria-windows-amd64-avx.exe
|
||||
./build/hysteria-windows-386.exe
|
||||
./build/hysteria-windows-arm64.exe
|
||||
./build/hysteria-linux-amd64
|
||||
./build/hysteria-linux-amd64-avx
|
||||
./build/hysteria-linux-386
|
||||
./build/hysteria-linux-arm
|
||||
./build/hysteria-linux-armv5
|
||||
./build/hysteria-linux-arm64
|
||||
./build/hysteria-linux-s390x
|
||||
./build/hysteria-linux-mipsle
|
||||
./build/hysteria-linux-mipsle-sf
|
||||
./build/hysteria-freebsd-amd64
|
||||
./build/hysteria-freebsd-amd64-avx
|
||||
./build/hysteria-freebsd-386
|
||||
./build/hysteria-freebsd-arm
|
||||
./build/hysteria-freebsd-arm64
|
||||
./build/hashes.txt
|
113
.gitignore
vendored
113
.gitignore
vendored
|
@ -1,7 +1,10 @@
|
|||
# Created by https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
|
||||
# Edit at https://www.gitignore.io/?templates=go,linux,macos,windows,intellij+all
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/goland+all,intellij+all,go,windows,linux,macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,intellij+all,go,windows,linux,macos
|
||||
|
||||
### 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
|
||||
*.exe
|
||||
*.exe~
|
||||
|
@ -18,12 +21,11 @@
|
|||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### Go Patch ###
|
||||
/vendor/
|
||||
/Godeps/
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
### Intellij+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
### GoLand+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
|
||||
|
@ -33,6 +35,9 @@
|
|||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
|
@ -53,6 +58,9 @@
|
|||
# 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
|
||||
|
@ -80,6 +88,9 @@ atlassian-ide-plugin.xml
|
|||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
|
@ -92,21 +103,69 @@ fabric.properties
|
|||
# Android studio 3.1+ serialized cache file
|
||||
.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 ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
@ -132,6 +191,7 @@ modules.xml
|
|||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
|
@ -151,6 +211,10 @@ Network Trash Folder
|
|||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
|
@ -177,13 +241,4 @@ $RECYCLE.BIN/
|
|||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.gitignore.io/api/go,linux,macos,windows,intellij+all
|
||||
|
||||
cmd/relay/*.json
|
||||
hy_linux
|
||||
.vscode
|
||||
|
||||
/build/
|
||||
/dist/
|
||||
|
||||
config*.json
|
||||
# End of https://www.toptal.com/developers/gitignore/api/goland+all,intellij+all,go,windows,linux,macos
|
148
CHANGELOG.md
148
CHANGELOG.md
|
@ -1,148 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
## 1.3.4
|
||||
|
||||
- Eliminate unnecessary DNS lookups when using SOCKS5 outbound with ACL disabled
|
||||
- Add a `lazy_start` option to let the client connect to the server only when there is an incoming connection
|
||||
- Fix a bug where TCP redirect didn't work on x86 (32-bit) machines
|
||||
- Fix memory leak when using UDP port hopping
|
||||
- Updated quic-go to v0.33.0
|
||||
|
||||
## 1.3.3
|
||||
|
||||
- Fix a bug that made UDP unusable when using `socks5_outbound`
|
||||
- Set the default value of `retry_interval` to 1 to prevent the client from retrying too often when errors occur
|
||||
- Prompt error if both acme and local cert file are specified in client config
|
||||
- Updated quic-go to v0.32.0, performance improvements
|
||||
|
||||
## 1.3.2
|
||||
|
||||
- Fix a bug where some malformed UDP packets would cause the server to crash
|
||||
- Fix a bug where the server did not have a timeout for SOCKS5 outbound connections
|
||||
- Add build variants: amd64-avx, armv5, mipsle-sf, windows/arm64
|
||||
|
||||
## 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`
|
43
Dockerfile
43
Dockerfile
|
@ -1,43 +0,0 @@
|
|||
FROM golang:1-alpine AS builder
|
||||
|
||||
LABEL maintainer="mritd <mritd@linux.com>"
|
||||
|
||||
# GOPROXY is disabled by default, use:
|
||||
# docker build --build-arg GOPROXY="https://goproxy.io" ...
|
||||
# to enable GOPROXY.
|
||||
ARG GOPROXY=""
|
||||
|
||||
ENV GOPROXY ${GOPROXY}
|
||||
|
||||
COPY . /go/src/github.com/apernet/hysteria
|
||||
|
||||
WORKDIR /go/src/github.com/apernet/hysteria
|
||||
|
||||
RUN set -ex \
|
||||
&& apk add git build-base bash \
|
||||
&& ./build.sh \
|
||||
&& mv ./build/hysteria-* /go/bin/hysteria
|
||||
|
||||
# multi-stage builds to create the final image
|
||||
FROM alpine AS dist
|
||||
|
||||
LABEL maintainer="mritd <mritd@linux.com>"
|
||||
|
||||
# set up nsswitch.conf for Go's "netgo" implementation
|
||||
# - 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
|
||||
RUN if [ ! -e /etc/nsswitch.conf ]; then echo 'hosts: files dns' > /etc/nsswitch.conf; fi
|
||||
|
||||
# bash is used for debugging, tzdata is used to add timezone information.
|
||||
# Install ca-certificates to ensure no CA certificate errors.
|
||||
#
|
||||
# Do not try to add the "--no-cache" option when there are multiple "apk"
|
||||
# commands, this will cause the build process to become very slow.
|
||||
RUN set -ex \
|
||||
&& apk upgrade \
|
||||
&& apk add bash tzdata ca-certificates \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
COPY --from=builder /go/bin/hysteria /usr/local/bin/hysteria
|
||||
|
||||
ENTRYPOINT ["hysteria"]
|
30
LICENSE.md
30
LICENSE.md
|
@ -1,30 +0,0 @@
|
|||
License
|
||||
==================
|
||||
|
||||
Hysteria itself, including all codes under this directory, is licensed under the MIT 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.
|
||||
```
|
||||
|
||||
However, when building with `-tags gpl`, the produced executable shall be distributed under GPLv3.
|
100
README.md
100
README.md
|
@ -1,87 +1,37 @@
|
|||
# 
|
||||
# Hysteria 2 Prototype
|
||||
|
||||
[![License][1]][2] [![Release][3]][4] [![Telegram][5]][6] [![Discussions][7]][8]
|
||||
> **Warning**
|
||||
> The code on this branch is a work-in-progress prototype of what will become Hysteria 2.0. It is currently very unfinished, and unless you know what you are doing, you should stick with the stable 1.x releases for now. **The protocol is also subject to change, so we do not recommend third-party developers use this as a reference for the Hysteria 2 protocol at this time.**
|
||||
|
||||
[1]: https://img.shields.io/badge/license-MIT-blue
|
||||
> **警告**
|
||||
> 此分支的代码是 Hysteria 2.0 的原型,目前仍在开发中,完成度十分有限。除非你十分确定自己在做什么,否则请继续使用稳定的 1.x 版本。**协议也可能会发生变化,因此我们不建议第三方开发者在目前使用此分支作为 Hysteria 2 协议的参考。**
|
||||
|
||||
[2]: LICENSE.md
|
||||
## Build (编译)
|
||||
|
||||
[3]: https://img.shields.io/github/v/release/apernet/hysteria?style=flat-square
|
||||
```bash
|
||||
go build ./app
|
||||
```
|
||||
|
||||
[4]: https://github.com/apernet/hysteria/releases
|
||||
## Usage (使用)
|
||||
|
||||
[5]: https://img.shields.io/badge/chat-Telegram-blue?style=flat-square
|
||||
### Server
|
||||
```bash
|
||||
./app server -c config.yaml
|
||||
```
|
||||
|
||||
[6]: https://t.me/hysteria_github
|
||||
[Example sever config (示例服务器配置)](app/server.example.yaml)
|
||||
|
||||
[7]: https://img.shields.io/github/discussions/apernet/hysteria?style=flat-square
|
||||
### Client
|
||||
```bash
|
||||
./app client -c config.yaml
|
||||
```
|
||||
|
||||
[8]: https://github.com/apernet/hysteria/discussions
|
||||
[Example client config (示例客户端配置)](app/client.example.yaml)
|
||||
|
||||

|
||||
## Test HTTP/3 masquerading (测试 HTTP/3 伪装)
|
||||
|
||||
An [Aperture Internet Laboratory](https://apernet.io/) project
|
||||
```bash
|
||||
chrome --origin-to-force-quic-on=example.com:443
|
||||
```
|
||||
|
||||
----------
|
||||
|
||||
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
|
||||
|
||||
- Censorship circumvention
|
||||
- Boosting slow connections
|
||||
- Bypassing commercial/academic/corporate firewalls
|
||||
- Bypassing ISP throttling
|
||||
- ...
|
||||
|
||||
## Modes
|
||||
|
||||
- SOCKS5 proxy (TCP & UDP)
|
||||
- HTTP/HTTPS proxy
|
||||
- TCP/UDP relay
|
||||
- TCP/UDP TPROXY (Linux)
|
||||
- TCP REDIRECT (Linux)
|
||||
- TUN (TAP on Windows)
|
||||
- Still growing...
|
||||
|
||||
## **[Documentation](https://hysteria.network/)**
|
||||
|
||||
----------
|
||||
|
||||
Hysteria 是一个功能丰富的,专为恶劣网络环境(如卫星网络、拥挤的公共 Wi-Fi、从中国连接境外服务器等)进行优化的双边加速工具,基于修改版的 QUIC 协议。
|
||||
|
||||
## 常见用例
|
||||
|
||||
- 绕过网络审查
|
||||
- 提升传输速度
|
||||
- 绕过商业/学校/企业防火墙
|
||||
- 绕过运营商 QoS 限速
|
||||
|
||||
## 模式
|
||||
|
||||
- SOCKS5 代理 (TCP & UDP)
|
||||
- HTTP/HTTPS 代理
|
||||
- TCP/UDP 转发
|
||||
- TCP/UDP TPROXY 透明代理 (Linux)
|
||||
- TCP REDIRECT 透明代理 (Linux)
|
||||
- TUN (Windows 下为 TAP)
|
||||
- 仍在增加中...
|
||||
|
||||
## **[中文文档](https://hysteria.network/zh/)**
|
||||
|
||||
----------
|
||||
|
||||
## Benchmarks
|
||||
|
||||

|
||||
|
||||
----------
|
||||
|
||||
**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>
|
||||
Then visit `https://example.com:443` in Chrome.
|
362
Taskfile.yaml
362
Taskfile.yaml
|
@ -1,362 +0,0 @@
|
|||
##############################################################################
|
||||
# #
|
||||
# go-task: https://taskfile.dev/installation/ #
|
||||
# #
|
||||
# For the role of 'amd64-v*', please refer to #
|
||||
# https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels. #
|
||||
# #
|
||||
##############################################################################
|
||||
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
BUILD_VERSION:
|
||||
sh: git describe --tags --always --match 'v*'
|
||||
BUILD_COMMIT:
|
||||
sh: git rev-parse HEAD
|
||||
BUILD_DATE:
|
||||
sh: date -u '+%F %T'
|
||||
|
||||
tasks:
|
||||
clean:
|
||||
cmds:
|
||||
- rm -rf dist
|
||||
- mkdir -p dist
|
||||
hash:
|
||||
dir: ./dist
|
||||
cmds:
|
||||
- sha256sum hysteria-* > hashes.txt
|
||||
build-hysteria:
|
||||
label: build-{{.TASK}}
|
||||
dir: ./app/cmd
|
||||
cmds:
|
||||
- |
|
||||
GOOS={{.GOOS}} GOARCH={{.GOARCH}} GOARM={{.GOARM}} GOAMD64={{.GOAMD64}} GOMIPS={{.GOMIPS}} \
|
||||
go build -trimpath -o ../../dist/hysteria-{{.TASK}}{{.BINEXT}} -ldflags \
|
||||
"-w -s -X 'main.appVersion={{.BUILD_VERSION}}' -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-v2:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: linux,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v2
|
||||
}
|
||||
linux-amd64-v3:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: linux,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v3
|
||||
}
|
||||
linux-amd64-v4:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: linux,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v4
|
||||
}
|
||||
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-s390x:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: linux,
|
||||
GOARCH: s390x
|
||||
}
|
||||
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-amd64-v2:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: darwin,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v2
|
||||
}
|
||||
darwin-amd64-v3:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: darwin,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v3
|
||||
}
|
||||
darwin-amd64-v4:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: darwin,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v4
|
||||
}
|
||||
darwin-arm64:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: darwin,
|
||||
GOARCH: arm64
|
||||
}
|
||||
freebsd-386:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: 386
|
||||
}
|
||||
freebsd-amd64:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: amd64
|
||||
}
|
||||
freebsd-amd64-v2:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v2
|
||||
}
|
||||
freebsd-amd64-v3:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v3
|
||||
}
|
||||
freebsd-amd64-v4:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v4
|
||||
}
|
||||
freebsd-arm:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: arm
|
||||
}
|
||||
freebsd-arm64:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
GOOS: freebsd,
|
||||
GOARCH: arm64
|
||||
}
|
||||
windows-386:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
BINEXT: ".exe",
|
||||
GOOS: windows,
|
||||
GOARCH: 386
|
||||
}
|
||||
windows-amd64:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
BINEXT: ".exe",
|
||||
GOOS: windows,
|
||||
GOARCH: amd64
|
||||
}
|
||||
windows-amd64-v2:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
BINEXT: ".exe",
|
||||
GOOS: windows,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v2
|
||||
}
|
||||
windows-amd64-v3:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
BINEXT: ".exe",
|
||||
GOOS: windows,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v3
|
||||
}
|
||||
windows-amd64-v4:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
BINEXT: ".exe",
|
||||
GOOS: windows,
|
||||
GOARCH: amd64,
|
||||
GOAMD64: v4
|
||||
}
|
||||
windows-arm64:
|
||||
cmds:
|
||||
- task: build-hysteria
|
||||
vars: {
|
||||
TASK: "{{.TASK}}",
|
||||
BINEXT: ".exe",
|
||||
GOOS: windows,
|
||||
GOARCH: arm64
|
||||
}
|
||||
default:
|
||||
cmds:
|
||||
- task: clean
|
||||
- task: linux-386
|
||||
- task: linux-amd64
|
||||
- task: linux-amd64-v2
|
||||
- task: linux-amd64-v3
|
||||
- task: linux-amd64-v4
|
||||
- task: linux-armv5
|
||||
- task: linux-armv6
|
||||
- task: linux-armv7
|
||||
- task: linux-armv8
|
||||
- task: linux-s390x
|
||||
- task: linux-mips-hardfloat
|
||||
- task: linux-mipsle-softfloat
|
||||
- task: linux-mipsle-hardfloat
|
||||
- task: linux-mips64
|
||||
- task: linux-mips64le
|
||||
- task: darwin-amd64
|
||||
- task: darwin-amd64-v2
|
||||
- task: darwin-amd64-v3
|
||||
- task: darwin-amd64-v4
|
||||
- task: darwin-arm64
|
||||
- task: freebsd-386
|
||||
- task: freebsd-amd64
|
||||
- task: freebsd-amd64-v2
|
||||
- task: freebsd-amd64-v3
|
||||
- task: freebsd-amd64-v4
|
||||
- task: freebsd-arm
|
||||
- task: freebsd-arm64
|
||||
- task: windows-386
|
||||
- task: windows-amd64
|
||||
- task: windows-amd64-v2
|
||||
- task: windows-amd64-v3
|
||||
- task: windows-amd64-v4
|
||||
- task: windows-arm64
|
||||
- task: hash
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
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")
|
||||
}
|
||||
}
|
29
app/client.example.yaml
Normal file
29
app/client.example.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
server: example.com
|
||||
# sni: other.example.com
|
||||
|
||||
auth: "hello world"
|
||||
|
||||
# tls:
|
||||
# insecure: false
|
||||
# ca: "custom.ca"
|
||||
|
||||
# quic:
|
||||
# initStreamReceiveWindow: 8388608
|
||||
# maxStreamReceiveWindow: 8388608
|
||||
# initConnReceiveWindow: 20971520
|
||||
# maxConnReceiveWindow: 20971520
|
||||
# maxIdleTimeout: 30s
|
||||
# keepAlivePeriod: 10s
|
||||
# disablePathMTUDiscovery: false
|
||||
|
||||
bandwidth:
|
||||
up: "100 mbps"
|
||||
down: "100 mbps"
|
||||
|
||||
# fastOpen: true
|
||||
|
||||
socks5:
|
||||
listen: 127.0.0.1:1080
|
||||
# username: "user"
|
||||
# password: "haha233"
|
||||
# disableUDP: true
|
|
@ -1,74 +0,0 @@
|
|||
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")
|
||||
}
|
|
@ -1,462 +1,216 @@
|
|||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
hyHTTP "github.com/apernet/hysteria/app/http"
|
||||
"github.com/apernet/hysteria/app/redirect"
|
||||
"github.com/apernet/hysteria/app/relay"
|
||||
"github.com/apernet/hysteria/app/socks5"
|
||||
"github.com/apernet/hysteria/app/tproxy"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/apernet/hysteria/core/pktconns"
|
||||
|
||||
"github.com/apernet/hysteria/core/pmtud"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
|
||||
"github.com/apernet/hysteria/core/acl"
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/apernet/hysteria/core/transport"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/apernet/hysteria/app/internal/socks5"
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
)
|
||||
|
||||
var clientPacketConnFuncFactoryMap = map[string]pktconns.ClientPacketConnFuncFactory{
|
||||
"": pktconns.NewClientUDPConnFunc,
|
||||
"udp": pktconns.NewClientUDPConnFunc,
|
||||
"wechat": pktconns.NewClientWeChatConnFunc,
|
||||
"wechat-video": pktconns.NewClientWeChatConnFunc,
|
||||
"faketcp": pktconns.NewClientFakeTCPConnFunc,
|
||||
var clientCmd = &cobra.Command{
|
||||
Use: "client",
|
||||
Short: "Client mode",
|
||||
Run: runClient,
|
||||
}
|
||||
|
||||
func client(config *clientConfig) {
|
||||
logrus.WithField("config", config.String()).Info("Client configuration loaded")
|
||||
config.Fill() // Fill default values
|
||||
// Resolver
|
||||
if len(config.Resolver) > 0 {
|
||||
err := setResolver(config.Resolver)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to set resolver")
|
||||
var modeMap = map[string]func(*viper.Viper, client.Client) error{
|
||||
"socks5": clientSOCKS5,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(clientCmd)
|
||||
}
|
||||
|
||||
func runClient(cmd *cobra.Command, args []string) {
|
||||
logger.Info("client mode")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
logger.Fatal("failed to read client config", zap.Error(err))
|
||||
}
|
||||
config, err := viperToClientConfig()
|
||||
if err != nil {
|
||||
logger.Fatal("failed to parse client config", zap.Error(err))
|
||||
}
|
||||
|
||||
c, err := client.NewClient(config)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to initialize client", zap.Error(err))
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
hasMode := false
|
||||
for mode, f := range modeMap {
|
||||
v := viper.Sub(mode)
|
||||
if v != nil {
|
||||
hasMode = true
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := f(v, c); err != nil {
|
||||
logger.Fatal("failed to run mode", zap.String("mode", mode), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
if !hasMode {
|
||||
logger.Fatal("no mode specified")
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func viperToClientConfig() (*client.Config, error) {
|
||||
// Conn and address
|
||||
addrStr := viper.GetString("server")
|
||||
if addrStr == "" {
|
||||
return nil, configError{Field: "server", Err: errors.New("server address is empty")}
|
||||
}
|
||||
addrStr = completeServerAddrString(addrStr)
|
||||
addr, err := net.ResolveUDPAddr("udp", addrStr)
|
||||
if err != nil {
|
||||
return nil, configError{Field: "server", Err: err}
|
||||
}
|
||||
sni := viper.GetString("sni")
|
||||
if sni == "" {
|
||||
sni = addrStr
|
||||
}
|
||||
// TLS
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{config.ALPN},
|
||||
ServerName: config.ServerName,
|
||||
InsecureSkipVerify: config.Insecure,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
// Load CA
|
||||
if len(config.CustomCA) > 0 {
|
||||
bs, err := ioutil.ReadFile(config.CustomCA)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.CustomCA,
|
||||
}).Fatal("Failed to load CA")
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(bs) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"file": config.CustomCA,
|
||||
}).Fatal("Failed to parse CA")
|
||||
}
|
||||
tlsConfig.RootCAs = cp
|
||||
}
|
||||
// QUIC config
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: config.ReceiveWindowConn,
|
||||
MaxStreamReceiveWindow: config.ReceiveWindowConn,
|
||||
InitialConnectionReceiveWindow: config.ReceiveWindow,
|
||||
MaxConnectionReceiveWindow: config.ReceiveWindow,
|
||||
HandshakeIdleTimeout: time.Duration(config.HandshakeTimeout) * time.Second,
|
||||
MaxIdleTimeout: time.Duration(config.IdleTimeout) * time.Second,
|
||||
KeepAlivePeriod: time.Duration(config.IdleTimeout) * time.Second * 2 / 5,
|
||||
DisablePathMTUDiscovery: config.DisableMTUDiscovery,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery {
|
||||
logrus.Info("Path MTU Discovery is not yet supported on this platform")
|
||||
}
|
||||
// Auth
|
||||
var auth []byte
|
||||
if len(config.Auth) > 0 {
|
||||
auth = config.Auth
|
||||
} else {
|
||||
auth = []byte(config.AuthString)
|
||||
}
|
||||
// Packet conn
|
||||
pktConnFuncFactory := clientPacketConnFuncFactoryMap[config.Protocol]
|
||||
if pktConnFuncFactory == nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"protocol": config.Protocol,
|
||||
}).Fatal("Unsupported protocol")
|
||||
}
|
||||
pktConnFunc := pktConnFuncFactory(config.Obfs, time.Duration(config.HopInterval)*time.Second)
|
||||
// Resolve preference
|
||||
if len(config.ResolvePreference) > 0 {
|
||||
pref, err := transport.ResolvePreferenceFromString(config.ResolvePreference)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to parse the resolve preference")
|
||||
}
|
||||
transport.DefaultClientTransport.ResolvePreference = pref
|
||||
}
|
||||
// ACL
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACL) > 0 {
|
||||
var err error
|
||||
aclEngine, err = acl.LoadFromFile(config.ACL, transport.DefaultClientTransport.ResolveIPAddr,
|
||||
func() (*geoip2.Reader, error) {
|
||||
return loadMMDBReader(config.MMDB)
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.ACL,
|
||||
}).Fatal("Failed to parse ACL")
|
||||
}
|
||||
}
|
||||
// Client
|
||||
var client *cs.Client
|
||||
try := 0
|
||||
up, down, _ := config.Speed()
|
||||
for {
|
||||
try += 1
|
||||
c, err := cs.NewClient(config.Server, auth, tlsConfig, quicConfig, pktConnFunc, up, down, config.FastOpen,
|
||||
config.LazyStart,
|
||||
func(err error) {
|
||||
if config.QuitOnDisconnect {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"addr": config.Server,
|
||||
"error": err,
|
||||
}).Fatal("Connection to server lost, exiting...")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"addr": config.Server,
|
||||
"error": err,
|
||||
}).Error("Connection to server lost, reconnecting...")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Error("Failed to initialize client")
|
||||
if try <= config.Retry || config.Retry < 0 {
|
||||
retryInterval := 1
|
||||
if config.RetryInterval != nil {
|
||||
retryInterval = *config.RetryInterval
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"retry": try,
|
||||
"interval": retryInterval,
|
||||
}).Info("Retrying...")
|
||||
time.Sleep(time.Duration(retryInterval) * time.Second)
|
||||
} else {
|
||||
logrus.Fatal("Out of retries, exiting...")
|
||||
}
|
||||
} else {
|
||||
client = c
|
||||
break
|
||||
}
|
||||
}
|
||||
defer client.Close()
|
||||
if config.LazyStart {
|
||||
logrus.WithField("addr", config.Server).Info("Lazy start enabled, waiting for first connection")
|
||||
} else {
|
||||
logrus.WithField("addr", config.Server).Info("Connected")
|
||||
}
|
||||
|
||||
// Local
|
||||
errChan := make(chan error)
|
||||
if len(config.SOCKS5.Listen) > 0 {
|
||||
go func() {
|
||||
var authFunc func(user, password string) bool
|
||||
if config.SOCKS5.User != "" && config.SOCKS5.Password != "" {
|
||||
authFunc = func(user, password string) bool {
|
||||
return config.SOCKS5.User == user && config.SOCKS5.Password == password
|
||||
}
|
||||
}
|
||||
socks5server, err := socks5.NewServer(client, transport.DefaultClientTransport, config.SOCKS5.Listen,
|
||||
authFunc, time.Duration(config.SOCKS5.Timeout)*time.Second, aclEngine, config.SOCKS5.DisableUDP,
|
||||
func(addr net.Addr, reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
}).Debug("SOCKS5 TCP request")
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
}).Info("SOCKS5 TCP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
}).Debug("SOCKS5 TCP EOF")
|
||||
}
|
||||
},
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Debug("SOCKS5 UDP associate")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Info("SOCKS5 UDP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Debug("SOCKS5 UDP EOF")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize SOCKS5 server")
|
||||
}
|
||||
logrus.WithField("addr", config.SOCKS5.Listen).Info("SOCKS5 server up and running")
|
||||
errChan <- socks5server.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.HTTP.Listen) > 0 {
|
||||
go func() {
|
||||
var authFunc func(user, password string) bool
|
||||
if config.HTTP.User != "" && config.HTTP.Password != "" {
|
||||
authFunc = func(user, password string) bool {
|
||||
return config.HTTP.User == user && config.HTTP.Password == password
|
||||
}
|
||||
}
|
||||
proxy, err := hyHTTP.NewProxyHTTPServer(client, transport.DefaultClientTransport,
|
||||
time.Duration(config.HTTP.Timeout)*time.Second, aclEngine, authFunc,
|
||||
func(reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"action": actionToString(action, arg),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
}).Debug("HTTP request")
|
||||
},
|
||||
func(reqAddr string, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
}).Info("HTTP error")
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize HTTP server")
|
||||
}
|
||||
if config.HTTP.Cert != "" && config.HTTP.Key != "" {
|
||||
logrus.WithField("addr", config.HTTP.Listen).Info("HTTPS server up and running")
|
||||
errChan <- http.ListenAndServeTLS(config.HTTP.Listen, config.HTTP.Cert, config.HTTP.Key, proxy)
|
||||
} else {
|
||||
logrus.WithField("addr", config.HTTP.Listen).Info("HTTP server up and running")
|
||||
errChan <- http.ListenAndServe(config.HTTP.Listen, proxy)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.TUN.Name) != 0 {
|
||||
go startTUN(config, client, errChan)
|
||||
}
|
||||
|
||||
if len(config.TCPRelay.Listen) > 0 {
|
||||
config.TCPRelays = append(config.TCPRelays, Relay{
|
||||
Listen: config.TCPRelay.Listen,
|
||||
Remote: config.TCPRelay.Remote,
|
||||
Timeout: config.TCPRelay.Timeout,
|
||||
})
|
||||
}
|
||||
|
||||
if len(config.TCPRelays) > 0 {
|
||||
for _, tcpr := range config.TCPRelays {
|
||||
go func(tcpr Relay) {
|
||||
rl, err := relay.NewTCPRelay(client, tcpr.Listen, tcpr.Remote,
|
||||
time.Duration(tcpr.Timeout)*time.Second,
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Debug("TCP relay request")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Info("TCP relay error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Debug("TCP relay EOF")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize TCP relay")
|
||||
}
|
||||
logrus.WithField("addr", tcpr.Listen).Info("TCP relay up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}(tcpr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.UDPRelay.Listen) > 0 {
|
||||
config.UDPRelays = append(config.UDPRelays, Relay{
|
||||
Listen: config.UDPRelay.Listen,
|
||||
Remote: config.UDPRelay.Remote,
|
||||
Timeout: config.UDPRelay.Timeout,
|
||||
})
|
||||
}
|
||||
|
||||
if len(config.UDPRelays) > 0 {
|
||||
for _, udpr := range config.UDPRelays {
|
||||
go func(udpr Relay) {
|
||||
rl, err := relay.NewUDPRelay(client, udpr.Listen, udpr.Remote,
|
||||
time.Duration(udpr.Timeout)*time.Second,
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Debug("UDP relay request")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if err != relay.ErrTimeout {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Info("UDP relay error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Debug("UDP relay session closed")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize UDP relay")
|
||||
}
|
||||
logrus.WithField("addr", udpr.Listen).Info("UDP relay up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}(udpr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.TCPTProxy.Listen) > 0 {
|
||||
go func() {
|
||||
rl, err := tproxy.NewTCPTProxy(client, config.TCPTProxy.Listen,
|
||||
time.Duration(config.TCPTProxy.Timeout)*time.Second,
|
||||
func(addr, reqAddr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Debug("TCP TProxy request")
|
||||
},
|
||||
func(addr, reqAddr net.Addr, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Info("TCP TProxy error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Debug("TCP TProxy EOF")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize TCP TProxy")
|
||||
}
|
||||
logrus.WithField("addr", config.TCPTProxy.Listen).Info("TCP TProxy up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.UDPTProxy.Listen) > 0 {
|
||||
go func() {
|
||||
rl, err := tproxy.NewUDPTProxy(client, config.UDPTProxy.Listen,
|
||||
time.Duration(config.UDPTProxy.Timeout)*time.Second,
|
||||
func(addr, reqAddr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Debug("UDP TProxy request")
|
||||
},
|
||||
func(addr, reqAddr net.Addr, err error) {
|
||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Info("UDP TProxy error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Debug("UDP TProxy session closed")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize UDP TProxy")
|
||||
}
|
||||
logrus.WithField("addr", config.UDPTProxy.Listen).Info("UDP TProxy up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.TCPRedirect.Listen) > 0 {
|
||||
go func() {
|
||||
rl, err := redirect.NewTCPRedirect(client, config.TCPRedirect.Listen,
|
||||
time.Duration(config.TCPRedirect.Timeout)*time.Second,
|
||||
func(addr, reqAddr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Debug("TCP Redirect request")
|
||||
},
|
||||
func(addr, reqAddr net.Addr, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Info("TCP Redirect error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr.String()),
|
||||
}).Debug("TCP Redirect EOF")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize TCP Redirect")
|
||||
}
|
||||
logrus.WithField("addr", config.TCPRedirect.Listen).Info("TCP Redirect up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
err := <-errChan
|
||||
logrus.WithField("error", err).Fatal("Client shutdown")
|
||||
}
|
||||
|
||||
func parseClientConfig(cb []byte) (*clientConfig, error) {
|
||||
var c clientConfig
|
||||
err := json5.Unmarshal(cb, &c)
|
||||
tlsConfig, err := viperToClientTLSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, c.Check()
|
||||
// QUIC
|
||||
quicConfig := viperToClientQUICConfig()
|
||||
// Bandwidth
|
||||
bwConfig, err := viperToClientBandwidthConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &client.Config{
|
||||
ConnFactory: nil, // TODO
|
||||
ServerAddr: addr,
|
||||
ServerName: sni,
|
||||
Auth: viper.GetString("auth"),
|
||||
TLSConfig: tlsConfig,
|
||||
QUICConfig: quicConfig,
|
||||
BandwidthConfig: bwConfig,
|
||||
FastOpen: viper.GetBool("fastOpen"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func viperToClientTLSConfig() (client.TLSConfig, error) {
|
||||
config := client.TLSConfig{
|
||||
InsecureSkipVerify: viper.GetBool("tls.insecure"),
|
||||
}
|
||||
caPath := viper.GetString("tls.ca")
|
||||
if caPath != "" {
|
||||
ca, err := os.ReadFile(caPath)
|
||||
if err != nil {
|
||||
return client.TLSConfig{}, configError{Field: "tls.ca", Err: err}
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(ca) {
|
||||
return client.TLSConfig{}, configError{Field: "tls.ca", Err: errors.New("failed to parse CA certificate")}
|
||||
}
|
||||
config.RootCAs = pool
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func viperToClientQUICConfig() client.QUICConfig {
|
||||
return client.QUICConfig{
|
||||
InitialStreamReceiveWindow: viper.GetUint64("quic.initStreamReceiveWindow"),
|
||||
MaxStreamReceiveWindow: viper.GetUint64("quic.maxStreamReceiveWindow"),
|
||||
InitialConnectionReceiveWindow: viper.GetUint64("quic.initConnReceiveWindow"),
|
||||
MaxConnectionReceiveWindow: viper.GetUint64("quic.maxConnReceiveWindow"),
|
||||
MaxIdleTimeout: viper.GetDuration("quic.maxIdleTimeout"),
|
||||
KeepAlivePeriod: viper.GetDuration("quic.keepAlivePeriod"),
|
||||
DisablePathMTUDiscovery: viper.GetBool("quic.disablePathMTUDiscovery"),
|
||||
}
|
||||
}
|
||||
|
||||
func viperToClientBandwidthConfig() (client.BandwidthConfig, error) {
|
||||
bw := client.BandwidthConfig{}
|
||||
upStr, downStr := viper.GetString("bandwidth.up"), viper.GetString("bandwidth.down")
|
||||
if upStr == "" || downStr == "" {
|
||||
return client.BandwidthConfig{}, configError{Field: "bandwidth", Err: errors.New("bandwidth.up and bandwidth.down must be set")}
|
||||
}
|
||||
up, err := convBandwidth(upStr)
|
||||
if err != nil {
|
||||
return client.BandwidthConfig{}, configError{Field: "bandwidth.up", Err: err}
|
||||
}
|
||||
down, err := convBandwidth(downStr)
|
||||
if err != nil {
|
||||
return client.BandwidthConfig{}, configError{Field: "bandwidth.down", Err: err}
|
||||
}
|
||||
bw.MaxTx, bw.MaxRx = up, down
|
||||
return bw, nil
|
||||
}
|
||||
|
||||
func clientSOCKS5(v *viper.Viper, c client.Client) error {
|
||||
listenAddr := v.GetString("listen")
|
||||
if listenAddr == "" {
|
||||
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
||||
}
|
||||
l, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return configError{Field: "listen", Err: err}
|
||||
}
|
||||
var authFunc func(username, password string) bool
|
||||
username, password := v.GetString("username"), v.GetString("password")
|
||||
if username != "" && password != "" {
|
||||
authFunc = func(username, password string) bool {
|
||||
return username == username && password == password
|
||||
}
|
||||
}
|
||||
s := socks5.Server{
|
||||
HyClient: c,
|
||||
AuthFunc: authFunc,
|
||||
DisableUDP: viper.GetBool("disableUDP"),
|
||||
EventLogger: &socks5Logger{},
|
||||
}
|
||||
logger.Info("SOCKS5 server listening", zap.String("addr", listenAddr))
|
||||
return s.Serve(l)
|
||||
}
|
||||
|
||||
func completeServerAddrString(addrStr string) string {
|
||||
if _, _, err := net.SplitHostPort(addrStr); err != nil {
|
||||
// No port provided, use default HTTPS port
|
||||
return net.JoinHostPort(addrStr, "443")
|
||||
}
|
||||
return addrStr
|
||||
}
|
||||
|
||||
type socks5Logger struct{}
|
||||
|
||||
func (l *socks5Logger) TCPRequest(addr net.Addr, reqAddr string) {
|
||||
logger.Debug("SOCKS5 TCP request", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr))
|
||||
}
|
||||
|
||||
func (l *socks5Logger) TCPError(addr net.Addr, reqAddr string, err error) {
|
||||
if err == nil {
|
||||
logger.Debug("SOCKS5 TCP closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr))
|
||||
} else {
|
||||
logger.Error("SOCKS5 TCP error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *socks5Logger) UDPRequest(addr net.Addr) {
|
||||
logger.Debug("SOCKS5 UDP request", zap.String("addr", addr.String()))
|
||||
}
|
||||
|
||||
func (l *socks5Logger) UDPError(addr net.Addr, err error) {
|
||||
if err == nil {
|
||||
logger.Debug("SOCKS5 UDP closed", zap.String("addr", addr.String()))
|
||||
} else {
|
||||
logger.Error("SOCKS5 UDP error", zap.String("addr", addr.String()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
//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()
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
//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")
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
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)
|
||||
}
|
||||
},
|
||||
}
|
|
@ -1,385 +0,0 @@
|
|||
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 len(c.ACME.Domains) > 0 && (len(c.CertFile) > 0 || len(c.KeyFile) > 0) {
|
||||
return errors.New("cannot use both ACME and cert/key files, they are mutually exclusive")
|
||||
}
|
||||
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"`
|
||||
LazyStart bool `json:"lazy_start"`
|
||||
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
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
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{}
|
|
@ -1,95 +0,0 @@
|
|||
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
208
app/cmd/main.go
|
@ -1,208 +0,0 @@
|
|||
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())
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
56
app/cmd/ping.go
Normal file
56
app/cmd/ping.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/apernet/hysteria/core/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("no address specified")
|
||||
}
|
||||
addr := args[0]
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
logger.Fatal("failed to read client config", zap.Error(err))
|
||||
}
|
||||
config, err := viperToClientConfig()
|
||||
if err != nil {
|
||||
logger.Fatal("failed to parse client config", zap.Error(err))
|
||||
}
|
||||
|
||||
c, err := client.NewClient(config)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to initialize client", zap.Error(err))
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
logger.Info("connecting", zap.String("address", addr))
|
||||
start := time.Now()
|
||||
conn, err := c.DialTCP(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()))
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type prometheusTrafficCounter struct {
|
||||
reg *prometheus.Registry
|
||||
upCounterVec *prometheus.CounterVec
|
||||
downCounterVec *prometheus.CounterVec
|
||||
connGaugeVec *prometheus.GaugeVec
|
||||
counterMap map[string]counters
|
||||
}
|
||||
|
||||
type counters struct {
|
||||
UpCounter prometheus.Counter
|
||||
DownCounter prometheus.Counter
|
||||
ConnGauge prometheus.Gauge
|
||||
}
|
||||
|
||||
func NewPrometheusTrafficCounter(reg *prometheus.Registry) cs.TrafficCounter {
|
||||
c := &prometheusTrafficCounter{
|
||||
reg: reg,
|
||||
upCounterVec: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "hysteria_traffic_uplink_bytes_total",
|
||||
}, []string{"auth"}),
|
||||
downCounterVec: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "hysteria_traffic_downlink_bytes_total",
|
||||
}, []string{"auth"}),
|
||||
connGaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "hysteria_active_conn",
|
||||
}, []string{"auth"}),
|
||||
counterMap: make(map[string]counters),
|
||||
}
|
||||
reg.MustRegister(c.upCounterVec, c.downCounterVec, c.connGaugeVec)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *prometheusTrafficCounter) getCounters(auth string) counters {
|
||||
cts, ok := c.counterMap[auth]
|
||||
if !ok {
|
||||
cts = counters{
|
||||
UpCounter: c.upCounterVec.WithLabelValues(auth),
|
||||
DownCounter: c.downCounterVec.WithLabelValues(auth),
|
||||
ConnGauge: c.connGaugeVec.WithLabelValues(auth),
|
||||
}
|
||||
c.counterMap[auth] = cts
|
||||
}
|
||||
return cts
|
||||
}
|
||||
|
||||
func (c *prometheusTrafficCounter) Rx(auth string, n int) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.DownCounter.Add(float64(n))
|
||||
}
|
||||
|
||||
func (c *prometheusTrafficCounter) Tx(auth string, n int) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.UpCounter.Add(float64(n))
|
||||
}
|
||||
|
||||
func (c *prometheusTrafficCounter) IncConn(auth string) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.ConnGauge.Inc()
|
||||
}
|
||||
|
||||
func (c *prometheusTrafficCounter) DecConn(auth string) {
|
||||
cts := c.getCounters(auth)
|
||||
cts.ConnGauge.Dec()
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
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
|
||||
}
|
79
app/cmd/root.go
Normal file
79
app/cmd/root.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
appLogo = `
|
||||
░█░█░█░█░█▀▀░▀█▀░█▀▀░█▀▄░▀█▀░█▀█░░░▀▀▄
|
||||
░█▀█░░█░░▀▀█░░█░░█▀▀░█▀▄░░█░░█▀█░░░▄▀░
|
||||
░▀░▀░░▀░░▀▀▀░░▀░░▀▀▀░▀░▀░▀▀▀░▀░▀░░░▀▀▀
|
||||
`
|
||||
appDesc = "a powerful, censorship-resistant proxy tool optimized for lossy networks"
|
||||
appAuthors = "Aperture Internet Laboratory <https://github.com/apernet>"
|
||||
)
|
||||
|
||||
var (
|
||||
// These values will be injected by the build system
|
||||
appVersion = "Unknown"
|
||||
appDate = "Unknown"
|
||||
appCommit = "Unknown"
|
||||
|
||||
appVersionLong = fmt.Sprintf("Version:\t%s\nBuildDate:\t%s\nCommitHash:\t%s", appVersion, appDate, appCommit)
|
||||
)
|
||||
|
||||
var logger *zap.Logger
|
||||
|
||||
// Flags
|
||||
var cfgFile string
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "hysteria",
|
||||
Short: appDesc,
|
||||
Long: fmt.Sprintf("%s\n%s\n%s\n\n%s", appLogo, appDesc, appAuthors, appVersionLong),
|
||||
Run: runClient, // Default to client mode
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
initLogger()
|
||||
initFlags()
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
func initLogger() {
|
||||
// TODO: Configurable logging
|
||||
l, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logger = l
|
||||
}
|
||||
|
||||
func initFlags() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file")
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("/etc/hysteria/")
|
||||
viper.AddConfigPath("$HOME/.hysteria")
|
||||
viper.AddConfigPath(".")
|
||||
}
|
||||
}
|
|
@ -1,307 +1,275 @@
|
|||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"github.com/apernet/hysteria/app/auth"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/apernet/hysteria/extras/auth"
|
||||
|
||||
"github.com/apernet/hysteria/core/pktconns"
|
||||
|
||||
"github.com/apernet/hysteria/core/acl"
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/apernet/hysteria/core/pmtud"
|
||||
"github.com/apernet/hysteria/core/sockopt"
|
||||
"github.com/apernet/hysteria/core/transport"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/yosuke-furukawa/json5/encoding/json5"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFactory{
|
||||
"": pktconns.NewServerUDPConnFunc,
|
||||
"udp": pktconns.NewServerUDPConnFunc,
|
||||
"wechat": pktconns.NewServerWeChatConnFunc,
|
||||
"wechat-video": pktconns.NewServerWeChatConnFunc,
|
||||
"faketcp": pktconns.NewServerFakeTCPConnFunc,
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Server mode",
|
||||
Run: runServer,
|
||||
}
|
||||
|
||||
func server(config *serverConfig) {
|
||||
logrus.WithField("config", config.String()).Info("Server configuration loaded")
|
||||
config.Fill() // Fill default values
|
||||
// Resolver
|
||||
if len(config.Resolver) > 0 {
|
||||
err := setResolver(config.Resolver)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to set resolver")
|
||||
}
|
||||
func init() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
initServerConfigDefaults()
|
||||
}
|
||||
|
||||
func initServerConfigDefaults() {
|
||||
viper.SetDefault("listen", ":443")
|
||||
}
|
||||
|
||||
func runServer(cmd *cobra.Command, args []string) {
|
||||
logger.Info("server mode")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
logger.Fatal("failed to read server config", zap.Error(err))
|
||||
}
|
||||
// Load TLS config
|
||||
var tlsConfig *tls.Config
|
||||
if len(config.ACME.Domains) > 0 {
|
||||
// ACME mode
|
||||
tc, err := acmeTLSConfig(config.ACME.Domains, config.ACME.Email,
|
||||
config.ACME.DisableHTTPChallenge, config.ACME.DisableTLSALPNChallenge,
|
||||
config.ACME.AltHTTPPort, config.ACME.AltTLSALPNPort)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to get a certificate with ACME")
|
||||
}
|
||||
tc.NextProtos = []string{config.ALPN}
|
||||
tc.MinVersion = tls.VersionTLS13
|
||||
tlsConfig = tc
|
||||
} else {
|
||||
// Local cert mode
|
||||
kpl, err := newKeypairLoader(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"cert": config.CertFile,
|
||||
"key": config.KeyFile,
|
||||
}).Fatal("Failed to load the certificate")
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
GetCertificate: kpl.GetCertificateFunc(),
|
||||
NextProtos: []string{config.ALPN},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
}
|
||||
// QUIC config
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: config.ReceiveWindowConn,
|
||||
MaxStreamReceiveWindow: config.ReceiveWindowConn,
|
||||
InitialConnectionReceiveWindow: config.ReceiveWindowClient,
|
||||
MaxConnectionReceiveWindow: config.ReceiveWindowClient,
|
||||
MaxIncomingStreams: int64(config.MaxConnClient),
|
||||
MaxIdleTimeout: ServerMaxIdleTimeoutSec * time.Second,
|
||||
KeepAlivePeriod: 0, // Keep alive should solely be client's responsibility
|
||||
DisablePathMTUDiscovery: config.DisableMTUDiscovery,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery {
|
||||
logrus.Info("Path MTU Discovery is not yet supported on this platform")
|
||||
}
|
||||
// Auth
|
||||
var authFunc cs.ConnectFunc
|
||||
var err error
|
||||
switch authMode := config.Auth.Mode; authMode {
|
||||
case "", "none":
|
||||
if len(config.Obfs) == 0 {
|
||||
logrus.Warn("Neither authentication nor obfuscation is turned on. " +
|
||||
"Your server could be used by anyone! Are you sure this is what you want?")
|
||||
}
|
||||
authFunc = func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
|
||||
return true, "Welcome"
|
||||
}
|
||||
case "password", "passwords":
|
||||
authFunc, err = auth.PasswordAuthFunc(config.Auth.Config)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to enable password authentication")
|
||||
} else {
|
||||
logrus.Info("Password authentication enabled")
|
||||
}
|
||||
case "external":
|
||||
authFunc, err = auth.ExternalAuthFunc(config.Auth.Config)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to enable external authentication")
|
||||
} else {
|
||||
logrus.Info("External authentication enabled")
|
||||
}
|
||||
default:
|
||||
logrus.WithField("mode", config.Auth.Mode).Fatal("Unsupported authentication mode")
|
||||
}
|
||||
connectFunc := func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
|
||||
ok, msg := authFunc(addr, auth, sSend, sRecv)
|
||||
if !ok {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"msg": msg,
|
||||
}).Info("Authentication failed, client rejected")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
}).Info("Client connected")
|
||||
}
|
||||
return ok, msg
|
||||
}
|
||||
// Resolve preference
|
||||
if len(config.ResolvePreference) > 0 {
|
||||
pref, err := transport.ResolvePreferenceFromString(config.ResolvePreference)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to parse the resolve preference")
|
||||
}
|
||||
transport.DefaultServerTransport.ResolvePreference = pref
|
||||
}
|
||||
// SOCKS5 outbound
|
||||
if config.SOCKS5Outbound.Server != "" {
|
||||
transport.DefaultServerTransport.SOCKS5Client = transport.NewSOCKS5Client(config.SOCKS5Outbound.Server,
|
||||
config.SOCKS5Outbound.User, config.SOCKS5Outbound.Password)
|
||||
}
|
||||
// Bind outbound
|
||||
if config.BindOutbound.Device != "" {
|
||||
iface, err := net.InterfaceByName(config.BindOutbound.Device)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to find the interface")
|
||||
}
|
||||
transport.DefaultServerTransport.LocalUDPIntf = iface
|
||||
sockopt.BindDialer(transport.DefaultServerTransport.Dialer, iface)
|
||||
}
|
||||
if config.BindOutbound.Address != "" {
|
||||
ip := net.ParseIP(config.BindOutbound.Address)
|
||||
if ip == nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Fatal("Failed to parse the address")
|
||||
}
|
||||
transport.DefaultServerTransport.Dialer.LocalAddr = &net.TCPAddr{IP: ip}
|
||||
transport.DefaultServerTransport.LocalUDPAddr = &net.UDPAddr{IP: ip}
|
||||
}
|
||||
// ACL
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACL) > 0 {
|
||||
aclEngine, err = acl.LoadFromFile(config.ACL, func(addr string) (*net.IPAddr, error) {
|
||||
ipAddr, _, err := transport.DefaultServerTransport.ResolveIPAddr(addr)
|
||||
return ipAddr, err
|
||||
},
|
||||
func() (*geoip2.Reader, error) {
|
||||
return loadMMDBReader(config.MMDB)
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"file": config.ACL,
|
||||
}).Fatal("Failed to parse ACL")
|
||||
}
|
||||
aclEngine.DefaultAction = acl.ActionDirect
|
||||
}
|
||||
// Prometheus
|
||||
var trafficCounter cs.TrafficCounter
|
||||
if len(config.PrometheusListen) > 0 {
|
||||
promReg := prometheus.NewRegistry()
|
||||
trafficCounter = NewPrometheusTrafficCounter(promReg)
|
||||
go func() {
|
||||
http.Handle("/metrics", promhttp.HandlerFor(promReg, promhttp.HandlerOpts{}))
|
||||
err := http.ListenAndServe(config.PrometheusListen, nil)
|
||||
logrus.WithField("error", err).Fatal("Prometheus HTTP server error")
|
||||
}()
|
||||
}
|
||||
// Packet conn
|
||||
pktConnFuncFactory := serverPacketConnFuncFactoryMap[config.Protocol]
|
||||
if pktConnFuncFactory == nil {
|
||||
logrus.WithField("protocol", config.Protocol).Fatal("Unsupported protocol")
|
||||
}
|
||||
pktConnFunc := pktConnFuncFactory(config.Obfs)
|
||||
pktConn, err := pktConnFunc(config.Listen)
|
||||
config, err := viperToServerConfig()
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"addr": config.Listen,
|
||||
}).Fatal("Failed to listen on the UDP address")
|
||||
logger.Fatal("failed to parse server config", zap.Error(err))
|
||||
}
|
||||
// Server
|
||||
up, down, _ := config.Speed()
|
||||
server, err := cs.NewServer(tlsConfig, quicConfig, pktConn,
|
||||
transport.DefaultServerTransport, up, down, config.DisableUDP, aclEngine,
|
||||
connectFunc, disconnectFunc, tcpRequestFunc, tcpErrorFunc, udpRequestFunc, udpErrorFunc, trafficCounter)
|
||||
|
||||
s, err := server.NewServer(config)
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize server")
|
||||
logger.Fatal("failed to initialize server", zap.Error(err))
|
||||
}
|
||||
defer server.Close()
|
||||
logrus.WithField("addr", config.Listen).Info("Server up and running")
|
||||
logger.Info("server up and running")
|
||||
|
||||
err = server.Serve()
|
||||
logrus.WithField("error", err).Fatal("Server shutdown")
|
||||
}
|
||||
|
||||
func disconnectFunc(addr net.Addr, auth []byte, err error) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"error": err,
|
||||
}).Info("Client disconnected")
|
||||
}
|
||||
|
||||
func tcpRequestFunc(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
"action": actionToString(action, arg),
|
||||
}).Debug("TCP request")
|
||||
}
|
||||
|
||||
func tcpErrorFunc(addr net.Addr, auth []byte, reqAddr string, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
"error": err,
|
||||
}).Info("TCP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"dst": defaultIPMasker.Mask(reqAddr),
|
||||
}).Debug("TCP EOF")
|
||||
if err := s.Serve(); err != nil {
|
||||
logger.Fatal("failed to serve", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func udpRequestFunc(addr net.Addr, auth []byte, sessionID uint32) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"session": sessionID,
|
||||
}).Debug("UDP request")
|
||||
}
|
||||
|
||||
func udpErrorFunc(addr net.Addr, auth []byte, sessionID uint32, err error) {
|
||||
if err != io.EOF {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"session": sessionID,
|
||||
"error": err,
|
||||
}).Info("UDP error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": defaultIPMasker.Mask(addr.String()),
|
||||
"session": sessionID,
|
||||
}).Debug("UDP EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func actionToString(action acl.Action, arg string) string {
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
return "Direct"
|
||||
case acl.ActionProxy:
|
||||
return "Proxy"
|
||||
case acl.ActionBlock:
|
||||
return "Block"
|
||||
case acl.ActionHijack:
|
||||
return "Hijack to " + arg
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerConfig(cb []byte) (*serverConfig, error) {
|
||||
var c serverConfig
|
||||
err := json5.Unmarshal(cb, &c)
|
||||
func viperToServerConfig() (*server.Config, error) {
|
||||
// Conn
|
||||
conn, err := viperToServerConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, c.Check()
|
||||
// TLS
|
||||
tlsConfig, err := viperToServerTLSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// QUIC
|
||||
quicConfig := viperToServerQUICConfig()
|
||||
// Bandwidth
|
||||
bwConfig, err := viperToServerBandwidthConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Disable UDP
|
||||
disableUDP := viper.GetBool("disableUDP")
|
||||
// Authenticator
|
||||
authenticator, err := viperToAuthenticator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Config
|
||||
config := &server.Config{
|
||||
TLSConfig: tlsConfig,
|
||||
QUICConfig: quicConfig,
|
||||
Conn: conn,
|
||||
Outbound: nil, // TODO
|
||||
BandwidthConfig: bwConfig,
|
||||
DisableUDP: disableUDP,
|
||||
Authenticator: authenticator,
|
||||
EventLogger: &serverLogger{},
|
||||
MasqHandler: nil, // TODO
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func viperToServerConn() (net.PacketConn, error) {
|
||||
listen := viper.GetString("listen")
|
||||
if listen == "" {
|
||||
return nil, configError{Field: "listen", Err: errors.New("empty listen address")}
|
||||
}
|
||||
uAddr, err := net.ResolveUDPAddr("udp", listen)
|
||||
if err != nil {
|
||||
return nil, configError{Field: "listen", Err: err}
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", uAddr)
|
||||
if err != nil {
|
||||
return nil, configError{Field: "listen", Err: err}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func viperToServerTLSConfig() (server.TLSConfig, error) {
|
||||
vTLS, vACME := viper.Sub("tls"), viper.Sub("acme")
|
||||
if vTLS == nil && vACME == nil {
|
||||
return server.TLSConfig{}, configError{Field: "tls", Err: errors.New("must set either tls or acme")}
|
||||
}
|
||||
if vTLS != nil && vACME != nil {
|
||||
return server.TLSConfig{}, configError{Field: "tls", Err: errors.New("cannot set both tls and acme")}
|
||||
}
|
||||
if vTLS != nil {
|
||||
return viperToServerTLSConfigLocal(vTLS)
|
||||
} else {
|
||||
return viperToServerTLSConfigACME(vACME)
|
||||
}
|
||||
}
|
||||
|
||||
func viperToServerTLSConfigLocal(v *viper.Viper) (server.TLSConfig, error) {
|
||||
certPath, keyPath := v.GetString("cert"), v.GetString("key")
|
||||
if certPath == "" || keyPath == "" {
|
||||
return server.TLSConfig{}, configError{Field: "tls", Err: errors.New("empty cert or key path")}
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return server.TLSConfig{}, configError{Field: "tls", Err: err}
|
||||
}
|
||||
return server.TLSConfig{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func viperToServerTLSConfigACME(v *viper.Viper) (server.TLSConfig, error) {
|
||||
dataDir := v.GetString("dir")
|
||||
if dataDir == "" {
|
||||
dataDir = "acme"
|
||||
}
|
||||
|
||||
cfg := &certmagic.Config{
|
||||
RenewalWindowRatio: certmagic.DefaultRenewalWindowRatio,
|
||||
KeySource: certmagic.DefaultKeyGenerator,
|
||||
Storage: &certmagic.FileStorage{Path: dataDir},
|
||||
Logger: logger,
|
||||
}
|
||||
issuer := certmagic.NewACMEIssuer(cfg, certmagic.ACMEIssuer{
|
||||
Email: v.GetString("email"),
|
||||
Agreed: true,
|
||||
DisableHTTPChallenge: v.GetBool("disableHTTP"),
|
||||
DisableTLSALPNChallenge: v.GetBool("disableTLSALPN"),
|
||||
AltHTTPPort: v.GetInt("altHTTPPort"),
|
||||
AltTLSALPNPort: v.GetInt("altTLSALPNPort"),
|
||||
Logger: logger,
|
||||
})
|
||||
switch strings.ToLower(v.GetString("ca")) {
|
||||
case "letsencrypt", "le", "":
|
||||
// Default to Let's Encrypt
|
||||
issuer.CA = certmagic.LetsEncryptProductionCA
|
||||
case "zerossl", "zero":
|
||||
issuer.CA = certmagic.ZeroSSLProductionCA
|
||||
default:
|
||||
return server.TLSConfig{}, configError{Field: "acme.ca", Err: errors.New("unknown CA")}
|
||||
}
|
||||
cfg.Issuers = []certmagic.Issuer{issuer}
|
||||
|
||||
cache := certmagic.NewCache(certmagic.CacheOptions{
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||
return cfg, nil
|
||||
},
|
||||
Logger: logger,
|
||||
})
|
||||
cfg = certmagic.New(cache, *cfg)
|
||||
|
||||
domains := v.GetStringSlice("domains")
|
||||
if len(domains) == 0 {
|
||||
return server.TLSConfig{}, configError{Field: "acme.domains", Err: errors.New("empty domains")}
|
||||
}
|
||||
err := cfg.ManageSync(context.Background(), domains)
|
||||
if err != nil {
|
||||
return server.TLSConfig{}, configError{Field: "acme", Err: err}
|
||||
}
|
||||
return server.TLSConfig{
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func viperToServerQUICConfig() server.QUICConfig {
|
||||
return server.QUICConfig{
|
||||
InitialStreamReceiveWindow: viper.GetUint64("quic.initStreamReceiveWindow"),
|
||||
MaxStreamReceiveWindow: viper.GetUint64("quic.maxStreamReceiveWindow"),
|
||||
InitialConnectionReceiveWindow: viper.GetUint64("quic.initConnReceiveWindow"),
|
||||
MaxConnectionReceiveWindow: viper.GetUint64("quic.maxConnReceiveWindow"),
|
||||
MaxIdleTimeout: viper.GetDuration("quic.maxIdleTimeout"),
|
||||
MaxIncomingStreams: viper.GetInt64("quic.maxIncomingStreams"),
|
||||
DisablePathMTUDiscovery: viper.GetBool("quic.disablePathMTUDiscovery"),
|
||||
}
|
||||
}
|
||||
|
||||
func viperToServerBandwidthConfig() (server.BandwidthConfig, error) {
|
||||
bw := server.BandwidthConfig{}
|
||||
upStr, downStr := viper.GetString("bandwidth.up"), viper.GetString("bandwidth.down")
|
||||
if upStr != "" {
|
||||
up, err := convBandwidth(upStr)
|
||||
if err != nil {
|
||||
return server.BandwidthConfig{}, configError{Field: "bandwidth.up", Err: err}
|
||||
}
|
||||
bw.MaxTx = up
|
||||
}
|
||||
if downStr != "" {
|
||||
down, err := convBandwidth(downStr)
|
||||
if err != nil {
|
||||
return server.BandwidthConfig{}, configError{Field: "bandwidth.down", Err: err}
|
||||
}
|
||||
bw.MaxRx = down
|
||||
}
|
||||
return bw, nil
|
||||
}
|
||||
|
||||
func viperToAuthenticator() (server.Authenticator, error) {
|
||||
authType := viper.GetString("auth.type")
|
||||
if authType == "" {
|
||||
return nil, configError{Field: "auth.type", Err: errors.New("empty auth type")}
|
||||
}
|
||||
switch authType {
|
||||
case "password":
|
||||
pw := viper.GetString("auth.password")
|
||||
if pw == "" {
|
||||
return nil, configError{Field: "auth.password", Err: errors.New("empty auth password")}
|
||||
}
|
||||
return &auth.PasswordAuthenticator{Password: pw}, nil
|
||||
default:
|
||||
return nil, configError{Field: "auth.type", Err: errors.New("unsupported auth type")}
|
||||
}
|
||||
}
|
||||
|
||||
type serverLogger struct{}
|
||||
|
||||
func (l *serverLogger) Connect(addr net.Addr, id string, tx uint64) {
|
||||
logger.Info("client connected", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint64("tx", tx))
|
||||
}
|
||||
|
||||
func (l *serverLogger) Disconnect(addr net.Addr, id string, err error) {
|
||||
logger.Info("client disconnected", zap.String("addr", addr.String()), zap.String("id", id), zap.Error(err))
|
||||
}
|
||||
|
||||
func (l *serverLogger) TCPRequest(addr net.Addr, id, reqAddr string) {
|
||||
logger.Debug("TCP request", zap.String("addr", addr.String()), zap.String("id", id), zap.String("reqAddr", reqAddr))
|
||||
}
|
||||
|
||||
func (l *serverLogger) TCPError(addr net.Addr, id, reqAddr string, err error) {
|
||||
if err == nil {
|
||||
logger.Debug("TCP closed", zap.String("addr", addr.String()), zap.String("id", id), zap.String("reqAddr", reqAddr))
|
||||
} else {
|
||||
logger.Error("TCP error", zap.String("addr", addr.String()), zap.String("id", id), zap.String("reqAddr", reqAddr), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *serverLogger) UDPRequest(addr net.Addr, id string, sessionID uint32) {
|
||||
logger.Debug("UDP request", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint32("sessionID", sessionID))
|
||||
}
|
||||
|
||||
func (l *serverLogger) UDPError(addr net.Addr, id string, sessionID uint32, err error) {
|
||||
if err == nil {
|
||||
logger.Debug("UDP closed", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint32("sessionID", sessionID))
|
||||
} else {
|
||||
logger.Error("UDP error", zap.String("addr", addr.String()), zap.String("id", id), zap.Uint32("sessionID", sessionID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const githubAPIURL = "https://api.github.com/repos/apernet/hysteria/releases/latest"
|
||||
|
||||
type releaseInfo struct {
|
||||
URL string `json:"html_url"`
|
||||
TagName string `json:"tag_name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
PublishedAt string `json:"published_at"`
|
||||
}
|
||||
|
||||
func checkUpdate() {
|
||||
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 fetchLatestRelease() (*releaseInfo, error) {
|
||||
hc := &http.Client{
|
||||
Timeout: time.Second * 20,
|
||||
}
|
||||
resp, err := hc.Get(githubAPIURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info releaseInfo
|
||||
err = json.Unmarshal(body, &info)
|
||||
return &info, err
|
||||
}
|
35
app/cmd/utils.go
Normal file
35
app/cmd/utils.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/apernet/hysteria/extras/utils"
|
||||
)
|
||||
|
||||
// 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 utils.StringToBps(bwT)
|
||||
case int:
|
||||
return uint64(bwT), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid type %T for bandwidth", bwT)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
79
app/go.mod
79
app/go.mod
|
@ -3,95 +3,56 @@ module github.com/apernet/hysteria/app
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/apernet/hysteria/core v0.0.0-00010101000000-000000000000
|
||||
github.com/apernet/hysteria/extras v0.0.0-00010101000000-000000000000
|
||||
github.com/caddyserver/certmagic v0.17.2
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20221015165544-a0805db90819
|
||||
github.com/folbricht/routedns v0.1.21-0.20230220022436-4ae86ce30d53
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/quic-go/quic-go v0.34.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a
|
||||
github.com/xjasonlyu/tun2socks/v2 v2.5.0
|
||||
github.com/yosuke-furukawa/json5 v0.1.1
|
||||
go.uber.org/zap v1.23.0
|
||||
gvisor.dev/gvisor v0.0.0-20230401011607-0333bf067633
|
||||
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/coreos/go-iptables v0.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20230131232505-5a9e8f65f08f // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jtacoma/uritemplates v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // 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/miekg/dns v1.1.51 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.8.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.4 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/transport/v2 v2.0.0 // indirect
|
||||
github.com/pion/udp v0.1.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/quic-go/quic-go v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
|
||||
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.3.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/apernet/hysteria/core => ../core/
|
||||
|
||||
replace github.com/quic-go/quic-go => github.com/apernet/quic-go v0.34.1-0.20230507231629-ec008b7e8473
|
||||
|
||||
replace github.com/LiamHaworth/go-tproxy => github.com/apernet/go-tproxy v0.0.0-20221025153553-ed04a2935f88
|
||||
replace github.com/apernet/hysteria/core => ../core
|
||||
|
||||
replace github.com/elazarl/goproxy => github.com/apernet/goproxy v0.0.0-20221124043924-155acfaf278f
|
||||
replace github.com/apernet/hysteria/extras => ../extras
|
||||
|
|
249
app/go.sum
249
app/go.sum
|
@ -38,33 +38,13 @@ 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=
|
||||
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/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 h1:vX+gnvBc56EbWYrmlhYbFYRaeikAke1GL84N4BEYOFE=
|
||||
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
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.34.1-0.20230507231629-ec008b7e8473 h1:3KFetJ/lUFn0m9xTFg+rMmz2nyHg+D2boJX0Rp4OF6c=
|
||||
github.com/apernet/quic-go v0.34.1-0.20230507231629-ec008b7e8473/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
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/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
|
||||
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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
@ -72,44 +52,24 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||
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-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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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.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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
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/folbricht/routedns v0.1.21-0.20230220022436-4ae86ce30d53 h1:jbMwVtjBl/cQM+l+xjGtV2C3jdpOTCZW2U3kIUf7Czg=
|
||||
github.com/folbricht/routedns v0.1.21-0.20230220022436-4ae86ce30d53/go.mod h1:Kig320CyqKR4G/JQ9G5QLJaUkkGoySJjgUXRjGnnQ0I=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
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-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
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-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
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/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=
|
||||
|
@ -137,13 +97,9 @@ 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
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 v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
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.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
|
@ -153,11 +109,7 @@ 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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/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=
|
||||
|
@ -171,8 +123,8 @@ 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-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-20230131232505-5a9e8f65f08f h1:gl1DCiSk+mrXXBGPm6CEeS2MkJuMVzAOrXg34oVj1QI=
|
||||
github.com/google/pprof v0.0.0-20230131232505-5a9e8f65f08f/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
|
@ -180,133 +132,62 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
|||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
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/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
|
||||
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/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-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/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.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/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
|
||||
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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
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/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
|
||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
|
||||
github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU=
|
||||
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
|
||||
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/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/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/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg=
|
||||
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw=
|
||||
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/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8=
|
||||
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
|
||||
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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
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/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
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/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
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=
|
||||
|
@ -314,7 +195,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
@ -330,14 +210,8 @@ 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/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
|
||||
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a h1:BOqgJ4jku0LHPDoR51RD8Mxmo0LHxCzJT/M9MemYdHo=
|
||||
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
|
||||
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
|
||||
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
|
||||
github.com/xjasonlyu/tun2socks/v2 v2.5.0 h1:eV/8PUZnRBd7CA3x6eE8xnHOHg072MzWMv+en+c5pPg=
|
||||
github.com/xjasonlyu/tun2socks/v2 v2.5.0/go.mod h1:iDkMNAYEUDN651NClepI2eRqwUnk3qVKyex/NzubffQ=
|
||||
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/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM=
|
||||
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
|
||||
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.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -351,17 +225,16 @@ 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.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -370,9 +243,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
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.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -383,8 +255,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-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-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE=
|
||||
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -410,11 +282,10 @@ 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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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-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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -422,7 +293,6 @@ 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-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-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-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=
|
||||
|
@ -446,16 +316,11 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
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-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-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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -465,8 +330,6 @@ 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-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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -480,13 +343,11 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
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/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-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-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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -495,7 +356,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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-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-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -508,8 +368,6 @@ 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-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-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-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -517,48 +375,34 @@ 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-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-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-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-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-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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
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.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.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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
@ -608,18 +452,13 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
|
|||
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.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
|
||||
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.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
@ -708,31 +547,19 @@ 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
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.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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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-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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20230401011607-0333bf067633 h1:trSjCkJT04PkOY8WayMOlk9MV6SK2LqvMBRnoi9jy5E=
|
||||
gvisor.dev/gvisor v0.0.0-20230401011607-0333bf067633/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
|
||||
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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
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{}) {}
|
294
app/internal/socks5/server.go
Normal file
294
app/internal/socks5/server.go
Normal file
|
@ -0,0 +1,294 @@
|
|||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/txthinking/socks5"
|
||||
|
||||
"github.com/apernet/hysteria/core/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.DialTCP(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.ListenUDP()
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
7
app/main.go
Normal file
7
app/main.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "github.com/apernet/hysteria/app/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
import socket
|
||||
import time
|
||||
|
||||
import socks
|
||||
import time
|
||||
|
||||
TARGET = "1.1.1.1"
|
||||
|
||||
|
||||
def check_tcp() -> None:
|
||||
def test_tcp() -> None:
|
||||
s = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.set_proxy(socks.SOCKS5, "127.0.0.1", 1080)
|
||||
|
||||
print(f"Sending HTTP request to {TARGET}")
|
||||
print(f"TCP - Sending HTTP request to {TARGET}")
|
||||
start = time.time()
|
||||
s.connect((TARGET, 80))
|
||||
s.send(b"GET / HTTP/1.1\r\nHost: " + TARGET.encode() + b"\r\n\r\n")
|
||||
|
@ -20,26 +19,26 @@ def check_tcp() -> None:
|
|||
elif not data.startswith(b"HTTP/1.1 "):
|
||||
print("Invalid response received")
|
||||
else:
|
||||
print("Response received")
|
||||
print("TCP test passed")
|
||||
end = time.time()
|
||||
s.close()
|
||||
|
||||
print(f"Time: {round((end - start) * 1000, 2)} ms")
|
||||
|
||||
|
||||
def check_udp() -> None:
|
||||
def test_udp() -> None:
|
||||
s = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.set_proxy(socks.SOCKS5, "127.0.0.1", 1080)
|
||||
|
||||
req = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05\x62\x61\x69\x64\x75\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"
|
||||
print(f"Sending DNS request to {TARGET}")
|
||||
print(f"UDP - Sending DNS request to {TARGET}")
|
||||
start = time.time()
|
||||
s.sendto(req, (TARGET, 53))
|
||||
(rsp, address) = s.recvfrom(4096)
|
||||
if address[0] == TARGET and address[1] == 53 and rsp[0] == req[0] and rsp[1] == req[1]:
|
||||
print("UDP check passed")
|
||||
print("UDP test passed")
|
||||
else:
|
||||
print("Invalid response")
|
||||
print("Invalid response received")
|
||||
end = time.time()
|
||||
s.close()
|
||||
|
||||
|
@ -47,5 +46,5 @@ def check_udp() -> None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_tcp()
|
||||
check_udp()
|
||||
test_tcp()
|
||||
test_udp()
|
|
@ -1,17 +0,0 @@
|
|||
//go:build !386
|
||||
// +build !386
|
||||
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func getsockopt(s uintptr, level uintptr, 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
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
SYS_GETSOCKOPT = 15
|
||||
)
|
||||
|
||||
// we cannot call socketcall with syscall.Syscall6, it always fails with EFAULT.
|
||||
// we have to call syscall.socketcall with this trick.
|
||||
func syscall_socketcall(call int, a0, a1, a2, a3, a4, a5 uintptr) (n int, err syscall.Errno)
|
||||
|
||||
func getsockopt(s uintptr, level uintptr, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
|
||||
_, e := syscall_socketcall(SYS_GETSOCKOPT, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
SO_ORIGINAL_DST = 80
|
||||
IP6T_SO_ORIGINAL_DST = 80
|
||||
)
|
||||
|
||||
type sockAddr struct {
|
||||
family uint16
|
||||
port [2]byte // big endian regardless of host byte order
|
||||
data [24]byte // check sockaddr_in or sockaddr_in6 for more information
|
||||
}
|
||||
|
||||
func getOrigDst(fd uintptr) (*sockAddr, error) {
|
||||
var addr sockAddr
|
||||
addrSize := uint32(unsafe.Sizeof(addr))
|
||||
// try IPv6 first
|
||||
err := getsockopt(fd, syscall.SOL_IPV6, IP6T_SO_ORIGINAL_DST, unsafe.Pointer(&addr), &addrSize)
|
||||
if err != nil {
|
||||
// try IPv4
|
||||
err = getsockopt(fd, syscall.SOL_IP, SO_ORIGINAL_DST, unsafe.Pointer(&addr), &addrSize)
|
||||
if err != nil {
|
||||
// failed
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &addr, nil
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·syscall_socketcall(SB),NOSPLIT,$0-36
|
||||
JMP syscall·socketcall(SB)
|
|
@ -1,97 +0,0 @@
|
|||
package redirect
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
)
|
||||
|
||||
type TCPRedirect struct {
|
||||
HyClient *cs.Client
|
||||
ListenAddr *net.TCPAddr
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr, reqAddr net.Addr)
|
||||
ErrorFunc func(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func NewTCPRedirect(hyClient *cs.Client, listen string, timeout time.Duration,
|
||||
connFunc func(addr, reqAddr net.Addr),
|
||||
errorFunc func(addr, reqAddr net.Addr, err error),
|
||||
) (*TCPRedirect, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &TCPRedirect{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: tAddr,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *TCPRedirect) ListenAndServe() error {
|
||||
listener, err := net.ListenTCP("tcp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
for {
|
||||
c, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer c.Close()
|
||||
dest, err := getDestAddr(c.(*net.TCPConn))
|
||||
if err != nil || dest.IP.IsLoopback() {
|
||||
// Silently drop the connection if we failed to get the destination address,
|
||||
// or if it's a loopback address (not a redirected connection).
|
||||
return
|
||||
}
|
||||
r.ConnFunc(c.RemoteAddr(), dest)
|
||||
rc, err := r.HyClient.DialTCP(dest.String())
|
||||
if err != nil {
|
||||
r.ErrorFunc(c.RemoteAddr(), dest, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
err = utils.PipePairWithTimeout(c, rc, r.Timeout)
|
||||
r.ErrorFunc(c.RemoteAddr(), dest, err)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func getDestAddr(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 = getOrigDst(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("unknown address family")
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package redirect
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
)
|
||||
|
||||
type TCPRedirect struct{}
|
||||
|
||||
func NewTCPRedirect(hyClient *cs.Client, listen string, timeout time.Duration,
|
||||
connFunc func(addr, reqAddr net.Addr),
|
||||
errorFunc func(addr, reqAddr net.Addr, err error),
|
||||
) (*TCPRedirect, error) {
|
||||
return nil, errors.New("not supported on the current system")
|
||||
}
|
||||
|
||||
func (r *TCPRedirect) ListenAndServe() error {
|
||||
return nil
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
)
|
||||
|
||||
type TCPRelay struct {
|
||||
HyClient *cs.Client
|
||||
ListenAddr *net.TCPAddr
|
||||
Remote string
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr net.Addr)
|
||||
ErrorFunc func(addr net.Addr, err error)
|
||||
}
|
||||
|
||||
func NewTCPRelay(hyClient *cs.Client, listen, remote string, timeout time.Duration,
|
||||
connFunc func(addr net.Addr), errorFunc func(addr net.Addr, err error),
|
||||
) (*TCPRelay, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &TCPRelay{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: tAddr,
|
||||
Remote: remote,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *TCPRelay) ListenAndServe() error {
|
||||
listener, err := net.ListenTCP("tcp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
for {
|
||||
c, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer c.Close()
|
||||
r.ConnFunc(c.RemoteAddr())
|
||||
rc, err := r.HyClient.DialTCP(r.Remote)
|
||||
if err != nil {
|
||||
r.ErrorFunc(c.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
err = utils.PipePairWithTimeout(c, rc, r.Timeout)
|
||||
r.ErrorFunc(c.RemoteAddr(), err)
|
||||
}()
|
||||
}
|
||||
}
|
124
app/relay/udp.go
124
app/relay/udp.go
|
@ -1,124 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
)
|
||||
|
||||
const udpBufferSize = 4096
|
||||
|
||||
var ErrTimeout = errors.New("inactivity timeout")
|
||||
|
||||
type UDPRelay struct {
|
||||
HyClient *cs.Client
|
||||
ListenAddr *net.UDPAddr
|
||||
Remote string
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr net.Addr)
|
||||
ErrorFunc func(addr net.Addr, err error)
|
||||
}
|
||||
|
||||
func NewUDPRelay(hyClient *cs.Client, listen, remote string, timeout time.Duration,
|
||||
connFunc func(addr net.Addr), errorFunc func(addr net.Addr, err error),
|
||||
) (*UDPRelay, error) {
|
||||
uAddr, err := net.ResolveUDPAddr("udp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &UDPRelay{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: uAddr,
|
||||
Remote: remote,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
if timeout == 0 {
|
||||
r.Timeout = 1 * time.Minute
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type connEntry struct {
|
||||
HyConn cs.HyUDPConn
|
||||
Deadline atomic.Value
|
||||
}
|
||||
|
||||
func (r *UDPRelay) ListenAndServe() error {
|
||||
conn, err := net.ListenUDP("udp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// src <-> HyClient HyUDPConn
|
||||
connMap := make(map[string]*connEntry)
|
||||
var connMapMutex sync.RWMutex
|
||||
// Read loop
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, rAddr, err := conn.ReadFromUDP(buf)
|
||||
if n > 0 {
|
||||
connMapMutex.RLock()
|
||||
entry := connMap[rAddr.String()]
|
||||
connMapMutex.RUnlock()
|
||||
if entry != nil {
|
||||
// Existing conn
|
||||
entry.Deadline.Store(time.Now().Add(r.Timeout))
|
||||
_ = entry.HyConn.WriteTo(buf[:n], r.Remote)
|
||||
} else {
|
||||
// New
|
||||
r.ConnFunc(rAddr)
|
||||
hyConn, err := r.HyClient.DialUDP()
|
||||
if err != nil {
|
||||
r.ErrorFunc(rAddr, err)
|
||||
} else {
|
||||
// Add it to the map
|
||||
entry := &connEntry{HyConn: hyConn}
|
||||
entry.Deadline.Store(time.Now().Add(r.Timeout))
|
||||
connMapMutex.Lock()
|
||||
connMap[rAddr.String()] = entry
|
||||
connMapMutex.Unlock()
|
||||
// Start remote to local
|
||||
go func() {
|
||||
for {
|
||||
bs, _, err := hyConn.ReadFrom()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
entry.Deadline.Store(time.Now().Add(r.Timeout))
|
||||
_, _ = conn.WriteToUDP(bs, rAddr)
|
||||
}
|
||||
}()
|
||||
// Timeout cleanup routine
|
||||
go func() {
|
||||
for {
|
||||
ttl := entry.Deadline.Load().(time.Time).Sub(time.Now())
|
||||
if ttl <= 0 {
|
||||
// Time to die
|
||||
connMapMutex.Lock()
|
||||
_ = hyConn.Close()
|
||||
delete(connMap, rAddr.String())
|
||||
connMapMutex.Unlock()
|
||||
r.ErrorFunc(rAddr, ErrTimeout)
|
||||
return
|
||||
} else {
|
||||
time.Sleep(ttl)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Send the packet
|
||||
_ = hyConn.WriteTo(buf[:n], r.Remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
35
app/server.example.yaml
Normal file
35
app/server.example.yaml
Normal file
|
@ -0,0 +1,35 @@
|
|||
listen: :443
|
||||
|
||||
# tls:
|
||||
# cert: my.crt
|
||||
# key: my.key
|
||||
|
||||
acme:
|
||||
domains:
|
||||
- my.example.com
|
||||
email: hackerman@abcd.com
|
||||
# ca: LetsEncrypt
|
||||
# disableHTTP: false
|
||||
# disableTLSALPN: false
|
||||
# altHTTPPort: 80
|
||||
# altTLSALPNPort: 443
|
||||
# dir: "custom_dir"
|
||||
|
||||
# quic:
|
||||
# initStreamReceiveWindow: 8388608
|
||||
# maxStreamReceiveWindow: 8388608
|
||||
# initConnReceiveWindow: 20971520
|
||||
# maxConnReceiveWindow: 20971520
|
||||
# maxIdleTimeout: 130s
|
||||
# maxIncomingStreams: 1024
|
||||
# disablePathMTUDiscovery: false
|
||||
|
||||
# bandwidth:
|
||||
# up: "100 mbps"
|
||||
# down: "100 mbps"
|
||||
#
|
||||
# disableUDP: false
|
||||
|
||||
auth:
|
||||
type: "password"
|
||||
password: "hello world"
|
|
@ -1,442 +0,0 @@
|
|||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/apernet/hysteria/core/acl"
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/apernet/hysteria/core/transport"
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
)
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/txthinking/socks5"
|
||||
)
|
||||
|
||||
const udpBufferSize = 4096
|
||||
|
||||
var (
|
||||
ErrUnsupportedCmd = errors.New("unsupported command")
|
||||
ErrUserPassAuth = errors.New("invalid username or password")
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
HyClient *cs.Client
|
||||
Transport *transport.ClientTransport
|
||||
AuthFunc func(username, password string) bool
|
||||
Method byte
|
||||
TCPAddr *net.TCPAddr
|
||||
TCPTimeout time.Duration
|
||||
ACLEngine *acl.Engine
|
||||
DisableUDP bool
|
||||
|
||||
TCPRequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
|
||||
TCPErrorFunc func(addr net.Addr, reqAddr string, err error)
|
||||
UDPAssociateFunc func(addr net.Addr)
|
||||
UDPErrorFunc func(addr net.Addr, err error)
|
||||
|
||||
tcpListener *net.TCPListener
|
||||
}
|
||||
|
||||
func NewServer(hyClient *cs.Client, transport *transport.ClientTransport, addr string,
|
||||
authFunc func(username, password string) bool, tcpTimeout time.Duration,
|
||||
aclEngine *acl.Engine, disableUDP bool,
|
||||
tcpReqFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
|
||||
tcpErrorFunc func(addr net.Addr, reqAddr string, err error),
|
||||
udpAssocFunc func(addr net.Addr), udpErrorFunc func(addr net.Addr, err error),
|
||||
) (*Server, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := socks5.MethodNone
|
||||
if authFunc != nil {
|
||||
m = socks5.MethodUsernamePassword
|
||||
}
|
||||
s := &Server{
|
||||
HyClient: hyClient,
|
||||
Transport: transport,
|
||||
AuthFunc: authFunc,
|
||||
Method: m,
|
||||
TCPAddr: tAddr,
|
||||
TCPTimeout: tcpTimeout,
|
||||
ACLEngine: aclEngine,
|
||||
DisableUDP: disableUDP,
|
||||
TCPRequestFunc: tcpReqFunc,
|
||||
TCPErrorFunc: tcpErrorFunc,
|
||||
UDPAssociateFunc: udpAssocFunc,
|
||||
UDPErrorFunc: udpErrorFunc,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) negotiate(c *net.TCPConn) error {
|
||||
rq, err := socks5.NewNegotiationRequestFrom(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var got bool
|
||||
var m byte
|
||||
for _, m = range rq.Methods {
|
||||
if m == s.Method {
|
||||
got = true
|
||||
}
|
||||
}
|
||||
if !got {
|
||||
rp := socks5.NewNegotiationReply(socks5.MethodUnsupportAll)
|
||||
if _, err := rp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
rp := socks5.NewNegotiationReply(s.Method)
|
||||
if _, err := rp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.Method == socks5.MethodUsernamePassword {
|
||||
urq, err := socks5.NewUserPassNegotiationRequestFrom(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !s.AuthFunc(string(urq.Uname), string(urq.Passwd)) {
|
||||
urp := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusFailure)
|
||||
if _, err := urp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrUserPassAuth
|
||||
}
|
||||
urp := socks5.NewUserPassNegotiationReply(socks5.UserPassStatusSuccess)
|
||||
if _, err := urp.WriteTo(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe() error {
|
||||
var err error
|
||||
s.tcpListener, err = net.ListenTCP("tcp", s.TCPAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.tcpListener.Close()
|
||||
for {
|
||||
c, err := s.tcpListener.AcceptTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer c.Close()
|
||||
if s.TCPTimeout != 0 {
|
||||
if err := c.SetDeadline(time.Now().Add(s.TCPTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := s.negotiate(c); err != nil {
|
||||
return
|
||||
}
|
||||
r, err := socks5.NewRequestFrom(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = s.handle(c, r)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handle(c *net.TCPConn, r *socks5.Request) error {
|
||||
if r.Cmd == socks5.CmdConnect {
|
||||
// TCP
|
||||
return s.handleTCP(c, r)
|
||||
} else if r.Cmd == socks5.CmdUDP {
|
||||
// UDP
|
||||
if !s.DisableUDP {
|
||||
return s.handleUDP(c, r)
|
||||
} else {
|
||||
_ = sendReply(c, socks5.RepCommandNotSupported)
|
||||
return ErrUnsupportedCmd
|
||||
}
|
||||
} else {
|
||||
_ = sendReply(c, socks5.RepCommandNotSupported)
|
||||
return ErrUnsupportedCmd
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
host, port, addr := parseRequestAddress(r)
|
||||
action, arg := acl.ActionProxy, ""
|
||||
var ipAddr *net.IPAddr
|
||||
var resErr error
|
||||
if s.ACLEngine != nil {
|
||||
action, arg, _, ipAddr, resErr = s.ACLEngine.ResolveAndMatch(host, port, false)
|
||||
// Doesn't always matter if the resolution fails, as we may send it through HyClient
|
||||
}
|
||||
s.TCPRequestFunc(c.RemoteAddr(), addr, action, arg)
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.TCPErrorFunc(c.RemoteAddr(), addr, closeErr)
|
||||
}()
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
if resErr != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = resErr
|
||||
return resErr
|
||||
}
|
||||
rc, err := s.Transport.DialTCP(&net.TCPAddr{
|
||||
IP: ipAddr.IP,
|
||||
Port: int(port),
|
||||
Zone: ipAddr.Zone,
|
||||
})
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = utils.PipePairWithTimeout(c, rc, s.TCPTimeout)
|
||||
return nil
|
||||
case acl.ActionProxy:
|
||||
rc, err := s.HyClient.DialTCP(addr)
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = utils.PipePairWithTimeout(c, rc, s.TCPTimeout)
|
||||
return nil
|
||||
case acl.ActionBlock:
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = errors.New("blocked in ACL")
|
||||
return nil
|
||||
case acl.ActionHijack:
|
||||
hijackIPAddr, err := s.Transport.ResolveIPAddr(arg)
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
rc, err := s.Transport.DialTCP(&net.TCPAddr{
|
||||
IP: hijackIPAddr.IP,
|
||||
Port: int(port),
|
||||
Zone: hijackIPAddr.Zone,
|
||||
})
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = utils.PipePairWithTimeout(c, rc, s.TCPTimeout)
|
||||
return nil
|
||||
default:
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = fmt.Errorf("unknown action %d", action)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
|
||||
s.UDPAssociateFunc(c.RemoteAddr())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.UDPErrorFunc(c.RemoteAddr(), closeErr)
|
||||
}()
|
||||
// Start local UDP server
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: s.TCPAddr.IP,
|
||||
Zone: s.TCPAddr.Zone,
|
||||
})
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer udpConn.Close()
|
||||
// Local UDP relay conn for ACL Direct
|
||||
var localRelayConn *net.UDPConn
|
||||
if s.ACLEngine != nil {
|
||||
localRelayConn, err = s.Transport.ListenUDP()
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer localRelayConn.Close()
|
||||
}
|
||||
// HyClient UDP session
|
||||
hyUDP, err := s.HyClient.DialUDP()
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer hyUDP.Close()
|
||||
// Send UDP server addr to the client
|
||||
// Same IP as TCP but a different port
|
||||
tcpLocalAddr := c.LocalAddr().(*net.TCPAddr)
|
||||
var atyp byte
|
||||
var addr, port []byte
|
||||
if ip4 := tcpLocalAddr.IP.To4(); ip4 != nil {
|
||||
atyp = socks5.ATYPIPv4
|
||||
addr = ip4
|
||||
} else if ip6 := tcpLocalAddr.IP.To16(); ip6 != nil {
|
||||
atyp = socks5.ATYPIPv6
|
||||
addr = ip6
|
||||
} else {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = errors.New("invalid local addr")
|
||||
return closeErr
|
||||
}
|
||||
port = make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(port, uint16(udpConn.LocalAddr().(*net.UDPAddr).Port))
|
||||
_, _ = socks5.NewReply(socks5.RepSuccess, atyp, addr, port).WriteTo(c)
|
||||
// Let UDP server do its job, we hold the TCP connection here
|
||||
go s.udpServer(udpConn, localRelayConn, hyUDP)
|
||||
if s.TCPTimeout != 0 {
|
||||
// Disable TCP timeout for UDP holder
|
||||
_ = c.SetDeadline(time.Time{})
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
_, err := c.Read(buf)
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
break
|
||||
}
|
||||
}
|
||||
// As the TCP connection closes, so does the UDP server & HyClient session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) udpServer(clientConn *net.UDPConn, localRelayConn *net.UDPConn, hyUDP cs.HyUDPConn) {
|
||||
var clientAddr *net.UDPAddr
|
||||
buf := make([]byte, udpBufferSize)
|
||||
// Local to remote
|
||||
for {
|
||||
n, cAddr, err := clientConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
d, err := socks5.NewDatagramFromBytes(buf[:n])
|
||||
if err != nil || d.Frag != 0 {
|
||||
// Ignore bad packets
|
||||
continue
|
||||
}
|
||||
if clientAddr == nil {
|
||||
// Whoever sends the first valid packet is our client
|
||||
clientAddr = cAddr
|
||||
// Start remote to local
|
||||
go func() {
|
||||
for {
|
||||
bs, from, err := hyUDP.ReadFrom()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
atyp, addr, port, err := socks5.ParseAddress(from)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
d := socks5.NewDatagram(atyp, addr, port, bs)
|
||||
_, _ = clientConn.WriteToUDP(d.Bytes(), clientAddr)
|
||||
}
|
||||
}()
|
||||
if localRelayConn != nil {
|
||||
go func() {
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, from, err := localRelayConn.ReadFrom(buf)
|
||||
if n > 0 {
|
||||
atyp, addr, port, err := socks5.ParseAddress(from.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
d := socks5.NewDatagram(atyp, addr, port, buf[:n])
|
||||
_, _ = clientConn.WriteToUDP(d.Bytes(), clientAddr)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else if cAddr.String() != clientAddr.String() {
|
||||
// Not our client, bye
|
||||
continue
|
||||
}
|
||||
host, port, addr := parseDatagramRequestAddress(d)
|
||||
action, arg := acl.ActionProxy, ""
|
||||
var ipAddr *net.IPAddr
|
||||
var resErr error
|
||||
if s.ACLEngine != nil && localRelayConn != nil {
|
||||
action, arg, _, ipAddr, resErr = s.ACLEngine.ResolveAndMatch(host, port, true)
|
||||
// Doesn't always matter if the resolution fails, as we may send it through HyClient
|
||||
}
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
if resErr != nil {
|
||||
return
|
||||
}
|
||||
_, _ = localRelayConn.WriteToUDP(d.Data, &net.UDPAddr{
|
||||
IP: ipAddr.IP,
|
||||
Port: int(port),
|
||||
Zone: ipAddr.Zone,
|
||||
})
|
||||
case acl.ActionProxy:
|
||||
_ = hyUDP.WriteTo(d.Data, addr)
|
||||
case acl.ActionBlock:
|
||||
// Do nothing
|
||||
case acl.ActionHijack:
|
||||
hijackIPAddr, err := s.Transport.ResolveIPAddr(arg)
|
||||
if err == nil {
|
||||
_, _ = localRelayConn.WriteToUDP(d.Data, &net.UDPAddr{
|
||||
IP: hijackIPAddr.IP,
|
||||
Port: int(port),
|
||||
Zone: hijackIPAddr.Zone,
|
||||
})
|
||||
}
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendReply(conn *net.TCPConn, rep byte) error {
|
||||
p := socks5.NewReply(rep, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
|
||||
_, err := p.WriteTo(conn)
|
||||
return err
|
||||
}
|
||||
|
||||
func parseRequestAddress(r *socks5.Request) (host string, port uint16, addr string) {
|
||||
p := binary.BigEndian.Uint16(r.DstPort)
|
||||
if r.Atyp == socks5.ATYPDomain {
|
||||
d := string(r.DstAddr[1:])
|
||||
return d, p, net.JoinHostPort(d, strconv.Itoa(int(p)))
|
||||
} else {
|
||||
ipStr := net.IP(r.DstAddr).String()
|
||||
return ipStr, p, net.JoinHostPort(ipStr, strconv.Itoa(int(p)))
|
||||
}
|
||||
}
|
||||
|
||||
func parseDatagramRequestAddress(r *socks5.Datagram) (host string, port uint16, addr string) {
|
||||
p := binary.BigEndian.Uint16(r.DstPort)
|
||||
if r.Atyp == socks5.ATYPDomain {
|
||||
d := string(r.DstAddr[1:])
|
||||
return d, p, net.JoinHostPort(d, strconv.Itoa(int(p)))
|
||||
} else {
|
||||
ipStr := net.IP(r.DstAddr).String()
|
||||
return ipStr, p, net.JoinHostPort(ipStr, strconv.Itoa(int(p)))
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package tproxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/LiamHaworth/go-tproxy"
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
)
|
||||
|
||||
type TCPTProxy struct {
|
||||
HyClient *cs.Client
|
||||
ListenAddr *net.TCPAddr
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr, reqAddr net.Addr)
|
||||
ErrorFunc func(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func NewTCPTProxy(hyClient *cs.Client, listen string, timeout time.Duration,
|
||||
connFunc func(addr, reqAddr net.Addr),
|
||||
errorFunc func(addr, reqAddr net.Addr, err error),
|
||||
) (*TCPTProxy, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &TCPTProxy{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: tAddr,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *TCPTProxy) ListenAndServe() error {
|
||||
listener, err := tproxy.ListenTCP("tcp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
for {
|
||||
c, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer c.Close()
|
||||
// Under TPROXY mode, we are effectively acting as the remote server
|
||||
// So our LocalAddr is actually the target to which the user is trying to connect
|
||||
// and our RemoteAddr is the local address where the user initiates the connection
|
||||
r.ConnFunc(c.RemoteAddr(), c.LocalAddr())
|
||||
rc, err := r.HyClient.DialTCP(c.LocalAddr().String())
|
||||
if err != nil {
|
||||
r.ErrorFunc(c.RemoteAddr(), c.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
err = utils.PipePairWithTimeout(c, rc, r.Timeout)
|
||||
r.ErrorFunc(c.RemoteAddr(), c.LocalAddr(), err)
|
||||
}()
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
)
|
||||
|
||||
type TCPTProxy struct{}
|
||||
|
||||
func NewTCPTProxy(hyClient *cs.Client, listen string, timeout time.Duration,
|
||||
connFunc func(addr, reqAddr net.Addr),
|
||||
errorFunc func(addr, reqAddr net.Addr, err error),
|
||||
) (*TCPTProxy, error) {
|
||||
return nil, errors.New("not supported on the current system")
|
||||
}
|
||||
|
||||
func (r *TCPTProxy) ListenAndServe() error {
|
||||
return nil
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
package tproxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/LiamHaworth/go-tproxy"
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
)
|
||||
|
||||
const udpBufferSize = 4096
|
||||
|
||||
type UDPTProxy struct {
|
||||
HyClient *cs.Client
|
||||
ListenAddr *net.UDPAddr
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr, reqAddr net.Addr)
|
||||
ErrorFunc func(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func NewUDPTProxy(hyClient *cs.Client, listen string, timeout time.Duration,
|
||||
connFunc func(addr, reqAddr net.Addr),
|
||||
errorFunc func(addr, reqAddr net.Addr, err error),
|
||||
) (*UDPTProxy, error) {
|
||||
uAddr, err := net.ResolveUDPAddr("udp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &UDPTProxy{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: uAddr,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
if timeout == 0 {
|
||||
r.Timeout = 1 * time.Minute
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) ListenAndServe() error {
|
||||
conn, err := tproxy.ListenUDP("udp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// Read loop
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, srcAddr, dstAddr, err := tproxy.ReadFromUDP(conn, buf) // Huge Caveat!! This essentially works as TCP's Accept here - won't repeat for the same srcAddr/dstAddr pair - because and only because we have tproxy.DialUDP("udp", dstAddr, srcAddr) to take over the connection below
|
||||
if n > 0 {
|
||||
r.ConnFunc(srcAddr, dstAddr)
|
||||
localConn, err := tproxy.DialUDP("udp", dstAddr, srcAddr)
|
||||
if err != nil {
|
||||
r.ErrorFunc(srcAddr, dstAddr, err)
|
||||
continue
|
||||
}
|
||||
hyConn, err := r.HyClient.DialUDP()
|
||||
if err != nil {
|
||||
r.ErrorFunc(srcAddr, dstAddr, err)
|
||||
_ = localConn.Close()
|
||||
continue
|
||||
}
|
||||
_ = hyConn.WriteTo(buf[:n], dstAddr.String())
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
// Start remote to local
|
||||
go func() {
|
||||
for {
|
||||
bs, _, err := hyConn.ReadFrom()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
_, err = localConn.Write(bs)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
_ = localConn.SetDeadline(time.Now().Add(r.Timeout))
|
||||
}
|
||||
}()
|
||||
// Start local to remote
|
||||
go func() {
|
||||
for {
|
||||
_ = localConn.SetDeadline(time.Now().Add(r.Timeout))
|
||||
n, err := localConn.Read(buf)
|
||||
if n > 0 {
|
||||
err := hyConn.WriteTo(buf[:n], dstAddr.String())
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Error cleanup routine
|
||||
go func() {
|
||||
err := <-errChan
|
||||
_ = localConn.Close()
|
||||
_ = hyConn.Close()
|
||||
r.ErrorFunc(srcAddr, dstAddr, err)
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
)
|
||||
|
||||
var ErrTimeout = errors.New("inactivity timeout")
|
||||
|
||||
type UDPTProxy struct{}
|
||||
|
||||
func NewUDPTProxy(hyClient *cs.Client, listen string, timeout time.Duration,
|
||||
connFunc func(addr, reqAddr net.Addr), errorFunc func(addr, reqAddr net.Addr, err error),
|
||||
) (*UDPTProxy, error) {
|
||||
return nil, errors.New("not supported on the current system")
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) ListenAndServe() error {
|
||||
return nil
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
//go:build gpl
|
||||
// +build gpl
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
t2score "github.com/xjasonlyu/tun2socks/v2/core"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
var _ adapter.TransportHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
HyClient *cs.Client
|
||||
Timeout time.Duration
|
||||
DeviceInfo DeviceInfo
|
||||
|
||||
RequestFunc func(addr net.Addr, reqAddr string)
|
||||
ErrorFunc func(addr net.Addr, reqAddr string, err error)
|
||||
}
|
||||
|
||||
const (
|
||||
MTU = 1500
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceTypeFd = iota
|
||||
DeviceTypeName
|
||||
)
|
||||
|
||||
type DeviceInfo struct {
|
||||
Type int
|
||||
Fd int
|
||||
Name string
|
||||
MTU uint32
|
||||
TCPSendBufferSize int
|
||||
TCPReceiveBufferSize int
|
||||
TCPModerateReceiveBuffer bool
|
||||
}
|
||||
|
||||
func (d *DeviceInfo) Open() (dev device.Device, err error) {
|
||||
switch d.Type {
|
||||
case DeviceTypeFd:
|
||||
dev, err = fdbased.Open(strconv.Itoa(d.Fd), d.MTU)
|
||||
case DeviceTypeName:
|
||||
dev, err = tun.Open(d.Name, d.MTU)
|
||||
default:
|
||||
err = fmt.Errorf("unknown device type: %d", d.Type)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewServerWithTunFd(hyClient *cs.Client, timeout time.Duration, tunFd int, mtu uint32,
|
||||
tcpSendBufferSize, tcpReceiveBufferSize int, tcpModerateReceiveBuffer bool,
|
||||
) (*Server, error) {
|
||||
if mtu == 0 {
|
||||
mtu = MTU
|
||||
}
|
||||
s := &Server{
|
||||
HyClient: hyClient,
|
||||
Timeout: timeout,
|
||||
DeviceInfo: DeviceInfo{
|
||||
Type: DeviceTypeFd,
|
||||
Fd: tunFd,
|
||||
MTU: mtu,
|
||||
TCPSendBufferSize: tcpSendBufferSize,
|
||||
TCPReceiveBufferSize: tcpReceiveBufferSize,
|
||||
TCPModerateReceiveBuffer: tcpModerateReceiveBuffer,
|
||||
},
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func NewServer(hyClient *cs.Client, timeout time.Duration, name string, mtu uint32,
|
||||
tcpSendBufferSize, tcpReceiveBufferSize int, tcpModerateReceiveBuffer bool,
|
||||
) (*Server, error) {
|
||||
if mtu == 0 {
|
||||
mtu = MTU
|
||||
}
|
||||
s := &Server{
|
||||
HyClient: hyClient,
|
||||
Timeout: timeout,
|
||||
DeviceInfo: DeviceInfo{
|
||||
Type: DeviceTypeName,
|
||||
Name: name,
|
||||
MTU: mtu,
|
||||
TCPSendBufferSize: tcpSendBufferSize,
|
||||
TCPReceiveBufferSize: tcpReceiveBufferSize,
|
||||
TCPModerateReceiveBuffer: tcpModerateReceiveBuffer,
|
||||
},
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe() error {
|
||||
var dev device.Device
|
||||
var st *stack.Stack
|
||||
|
||||
defer func() {
|
||||
if dev != nil {
|
||||
_ = dev.Close()
|
||||
}
|
||||
if st != nil {
|
||||
st.Close()
|
||||
st.Wait()
|
||||
}
|
||||
}()
|
||||
|
||||
dev, err := s.DeviceInfo.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var opts []option.Option
|
||||
if s.DeviceInfo.TCPSendBufferSize > 0 {
|
||||
opts = append(opts, option.WithTCPSendBufferSize(s.DeviceInfo.TCPSendBufferSize))
|
||||
}
|
||||
if s.DeviceInfo.TCPReceiveBufferSize > 0 {
|
||||
opts = append(opts, option.WithTCPReceiveBufferSize(s.DeviceInfo.TCPReceiveBufferSize))
|
||||
}
|
||||
if s.DeviceInfo.TCPModerateReceiveBuffer {
|
||||
opts = append(opts, option.WithTCPModerateReceiveBuffer(s.DeviceInfo.TCPModerateReceiveBuffer))
|
||||
}
|
||||
|
||||
t2sconf := t2score.Config{
|
||||
LinkEndpoint: dev,
|
||||
TransportHandler: s,
|
||||
Options: opts,
|
||||
}
|
||||
|
||||
st, err = t2score.CreateStack(&t2sconf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
//go:build gpl
|
||||
// +build gpl
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
||||
)
|
||||
|
||||
func (s *Server) HandleTCP(localConn adapter.TCPConn) {
|
||||
go s.handleTCPConn(localConn)
|
||||
}
|
||||
|
||||
func (s *Server) handleTCPConn(localConn adapter.TCPConn) {
|
||||
defer localConn.Close()
|
||||
|
||||
id := localConn.ID()
|
||||
remoteAddr := net.TCPAddr{
|
||||
IP: net.IP(id.LocalAddress),
|
||||
Port: int(id.LocalPort),
|
||||
}
|
||||
localAddr := net.TCPAddr{
|
||||
IP: net.IP(id.RemoteAddress),
|
||||
Port: int(id.RemotePort),
|
||||
}
|
||||
|
||||
if s.RequestFunc != nil {
|
||||
s.RequestFunc(&localAddr, remoteAddr.String())
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if s.ErrorFunc != nil && err != nil {
|
||||
s.ErrorFunc(&localAddr, remoteAddr.String(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
rc, err := s.HyClient.DialTCP(remoteAddr.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
err = utils.PipePairWithTimeout(localConn, rc, s.Timeout)
|
||||
}
|
114
app/tun/udp.go
114
app/tun/udp.go
|
@ -1,114 +0,0 @@
|
|||
//go:build gpl
|
||||
// +build gpl
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/cs"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
||||
)
|
||||
|
||||
const udpBufferSize = 4096
|
||||
|
||||
func (s *Server) HandleUDP(conn adapter.UDPConn) {
|
||||
go s.handleUDPConn(conn)
|
||||
}
|
||||
|
||||
func (s *Server) handleUDPConn(conn adapter.UDPConn) {
|
||||
defer conn.Close()
|
||||
|
||||
id := conn.ID()
|
||||
remoteAddr := net.UDPAddr{
|
||||
IP: net.IP(id.LocalAddress),
|
||||
Port: int(id.LocalPort),
|
||||
}
|
||||
localAddr := net.UDPAddr{
|
||||
IP: net.IP(id.RemoteAddress),
|
||||
Port: int(id.RemotePort),
|
||||
}
|
||||
|
||||
if s.RequestFunc != nil {
|
||||
s.RequestFunc(&localAddr, remoteAddr.String())
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if s.ErrorFunc != nil && err != nil {
|
||||
s.ErrorFunc(&localAddr, remoteAddr.String(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
rc, err := s.HyClient.DialUDP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
err = s.relayUDP(conn, rc, &remoteAddr, s.Timeout)
|
||||
}
|
||||
|
||||
func (s *Server) relayUDP(lc adapter.UDPConn, rc cs.HyUDPConn, to *net.UDPAddr, timeout time.Duration) (err error) {
|
||||
errChan := make(chan error, 2)
|
||||
// local => remote
|
||||
go func() {
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
if timeout != 0 {
|
||||
_ = lc.SetDeadline(time.Now().Add(timeout))
|
||||
n, err := lc.Read(buf)
|
||||
if n > 0 {
|
||||
err = rc.WriteTo(buf[:n], to.String())
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
// remote => local
|
||||
go func() {
|
||||
for {
|
||||
pkt, addr, err := rc.ReadFrom()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
if pkt != nil {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("cannot parse as port: %s", portStr)
|
||||
return
|
||||
}
|
||||
|
||||
// adapter.UDPConn doesn't support WriteFrom() yet,
|
||||
// so we check the src address and behavior like a symmetric NAT
|
||||
if !to.IP.Equal(net.ParseIP(host)) || to.Port != port {
|
||||
// drop the packet silently
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = lc.Write(pkt)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return <-errChan
|
||||
}
|
113
build.ps1
113
build.ps1
|
@ -1,113 +0,0 @@
|
|||
# Hysteria build script for Windows (PowerShell)
|
||||
|
||||
# Environment variable options:
|
||||
# - HY_APP_VERSION: App version
|
||||
# - HY_APP_COMMIT: App commit hash
|
||||
# - HY_APP_PLATFORMS: Platforms to build for (e.g. "windows/amd64,linux/amd64,darwin/amd64")
|
||||
|
||||
function PlatformToEnv($os, $arch) {
|
||||
$env:CGO_ENABLED = 0
|
||||
$env:GOOS = $os
|
||||
$env:GOARCH = $arch
|
||||
|
||||
switch -Regex ($arch) {
|
||||
"arm" {
|
||||
$env:GOARM = "7"
|
||||
}
|
||||
"armv5" {
|
||||
$env:GOARM = "5"
|
||||
$env:GOARCH = "arm"
|
||||
}
|
||||
"armv6" {
|
||||
$env:GOARM = "6"
|
||||
$env:GOARCH = "arm"
|
||||
}
|
||||
"armv7" {
|
||||
$env:GOARM = "7"
|
||||
$env:GOARCH = "arm"
|
||||
}
|
||||
"mips(le)?" {
|
||||
$env:GOMIPS = ""
|
||||
}
|
||||
"mips-sf" {
|
||||
$env:GOMIPS = "softfloat"
|
||||
$env:GOARCH = "mips"
|
||||
}
|
||||
"mipsle-sf" {
|
||||
$env:GOMIPS = "softfloat"
|
||||
$env:GOARCH = "mipsle"
|
||||
}
|
||||
"amd64" {
|
||||
$env:GOAMD64 = ""
|
||||
$env:GOARCH = "amd64"
|
||||
}
|
||||
"amd64-avx" {
|
||||
$env:GOAMD64 = "v3"
|
||||
$env:GOARCH = "amd64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(Get-Command go -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Error: go is not installed." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (!(Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Error: git is not installed." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
if (!(git rev-parse --is-inside-work-tree 2>$null)) {
|
||||
Write-Host "Error: not in a git repository." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ldflags = "-s -w -X 'main.appDate=$(Get-Date -Format "yyyy-MM-dd HH:mm:ss")'"
|
||||
if ($env:HY_APP_VERSION) {
|
||||
$ldflags += " -X 'main.appVersion=$($env:HY_APP_VERSION)'"
|
||||
}
|
||||
else {
|
||||
$ldflags += " -X 'main.appVersion=$(git describe --tags --always --match "v*")'"
|
||||
}
|
||||
if ($env:HY_APP_COMMIT) {
|
||||
$ldflags += " -X 'main.appCommit=$($env:HY_APP_COMMIT)'"
|
||||
}
|
||||
else {
|
||||
$ldflags += " -X 'main.appCommit=$(git rev-parse HEAD)'"
|
||||
}
|
||||
|
||||
if ($env:HY_APP_PLATFORMS) {
|
||||
$platforms = $env:HY_APP_PLATFORMS.Split(",")
|
||||
}
|
||||
else {
|
||||
$goos = go env GOOS
|
||||
$goarch = go env GOARCH
|
||||
$platforms = @("$goos/$goarch")
|
||||
}
|
||||
|
||||
if (Test-Path build) {
|
||||
Remove-Item -Recurse -Force build
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path build
|
||||
|
||||
Write-Host "Starting build..." -ForegroundColor Green
|
||||
|
||||
foreach ($platform in $platforms) {
|
||||
$os = $platform.Split("/")[0]
|
||||
$arch = $platform.Split("/")[1]
|
||||
PlatformToEnv $os $arch
|
||||
Write-Host "Building $os/$arch" -ForegroundColor Green
|
||||
$output = "build/hysteria-$os-$arch"
|
||||
if ($os -eq "windows") {
|
||||
$output = "$output.exe"
|
||||
}
|
||||
go build -o $output -tags=gpl -ldflags $ldflags -trimpath ./app/cmd/
|
||||
if ($LastExitCode -ne 0) {
|
||||
Write-Host "Error: failed to build $os/$arch" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Build complete." -ForegroundColor Green
|
||||
|
||||
Get-ChildItem -Path build | Format-Table -AutoSize
|
130
build.sh
130
build.sh
|
@ -1,130 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Hysteria build script for Linux
|
||||
# Environment variable options:
|
||||
# - HY_APP_VERSION: App version
|
||||
# - HY_APP_COMMIT: App commit hash
|
||||
# - HY_APP_PLATFORMS: Platforms to build for (e.g. "windows/amd64,linux/amd64,darwin/amd64")
|
||||
|
||||
export LC_ALL=C
|
||||
export LC_DATE=C
|
||||
|
||||
has_command() {
|
||||
local cmd="$1"
|
||||
type -P "$cmd" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
if ! has_command go; then
|
||||
echo 'Error: go is not installed.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! has_command git; then
|
||||
echo 'Error: git is not installed.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
echo 'Error: not in a git repository.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
platform_to_env() {
|
||||
local os="$1"
|
||||
local arch="$2"
|
||||
local env="GOOS=$os GOARCH=$arch CGO_ENABLED=0"
|
||||
|
||||
case "$arch" in
|
||||
arm)
|
||||
env+=" GOARM=7 GOARCH=arm"
|
||||
;;
|
||||
armv5)
|
||||
env+=" GOARM=5 GOARCH=arm"
|
||||
;;
|
||||
armv6)
|
||||
env+=" GOARM=6 GOARCH=arm"
|
||||
;;
|
||||
armv7)
|
||||
env+=" GOARM=7 GOARCH=arm"
|
||||
;;
|
||||
mips | mipsle)
|
||||
env+=" GOMIPS="
|
||||
;;
|
||||
mips-sf)
|
||||
env+=" GOMIPS=softfloat GOARCH=mips"
|
||||
;;
|
||||
mipsle-sf)
|
||||
env+=" GOMIPS=softfloat GOARCH=mipsle"
|
||||
;;
|
||||
amd64)
|
||||
env+=" GOAMD64= GOARCH=amd64"
|
||||
;;
|
||||
amd64-avx)
|
||||
env+=" GOAMD64=v3 GOARCH=amd64"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$env"
|
||||
}
|
||||
|
||||
make_ldflags() {
|
||||
local ldflags="-s -w -X 'main.appDate=$(date -u '+%F %T')'"
|
||||
if [ -n "$HY_APP_VERSION" ]; then
|
||||
ldflags="$ldflags -X 'main.appVersion=$HY_APP_VERSION'"
|
||||
else
|
||||
ldflags="$ldflags -X 'main.appVersion=$(git describe --tags --always --match 'v*')'"
|
||||
fi
|
||||
if [ -n "$HY_APP_COMMIT" ]; then
|
||||
ldflags="$ldflags -X 'main.appCommit=$HY_APP_COMMIT'"
|
||||
else
|
||||
ldflags="$ldflags -X 'main.appCommit=$(git rev-parse HEAD)'"
|
||||
fi
|
||||
echo "$ldflags"
|
||||
}
|
||||
|
||||
build_for_platform() {
|
||||
local platform="$1"
|
||||
local ldflags="$2"
|
||||
|
||||
local GOOS="${platform%/*}"
|
||||
local GOARCH="${platform#*/}"
|
||||
if [[ -z "$GOOS" || -z "$GOARCH" ]]; then
|
||||
echo "Invalid platform $platform" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Building $GOOS/$GOARCH"
|
||||
local output="build/hysteria-$GOOS-$GOARCH"
|
||||
if [[ "$GOOS" = "windows" ]]; then
|
||||
output="$output.exe"
|
||||
fi
|
||||
local envs="$(platform_to_env "$GOOS" "$GOARCH")"
|
||||
local exit_val=0
|
||||
env $envs go build -o "$output" -tags=gpl -ldflags "$ldflags" -trimpath ./app/cmd/ || exit_val=$?
|
||||
if [[ "$exit_val" -ne 0 ]]; then
|
||||
echo "Error: failed to build $GOOS/$GOARCH" >&2
|
||||
return $exit_val
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
if [ -z "$HY_APP_PLATFORMS" ]; then
|
||||
HY_APP_PLATFORMS="$(go env GOOS)/$(go env GOARCH)"
|
||||
fi
|
||||
platforms=(${HY_APP_PLATFORMS//,/ })
|
||||
ldflags="$(make_ldflags)"
|
||||
|
||||
mkdir -p build
|
||||
rm -rf build/*
|
||||
|
||||
echo "Starting build..."
|
||||
|
||||
for platform in "${platforms[@]}"; do
|
||||
build_for_platform "$platform" "$ldflags"
|
||||
done
|
||||
|
||||
echo "Build complete."
|
||||
|
||||
ls -lh build/ | awk '{print $9, $5}'
|
|
@ -1,143 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
const entryCacheSize = 1024
|
||||
|
||||
type Engine struct {
|
||||
DefaultAction Action
|
||||
Entries []Entry
|
||||
Cache *lru.ARCCache[cacheKey, cacheValue]
|
||||
ResolveIPAddr func(string) (*net.IPAddr, error)
|
||||
GeoIPReader *geoip2.Reader
|
||||
}
|
||||
|
||||
type cacheKey struct {
|
||||
Host string
|
||||
Port uint16
|
||||
IsUDP bool
|
||||
}
|
||||
|
||||
type cacheValue struct {
|
||||
Action Action
|
||||
Arg string
|
||||
}
|
||||
|
||||
func LoadFromFile(filename string, resolveIPAddr func(string) (*net.IPAddr, error), geoIPLoadFunc func() (*geoip2.Reader, error)) (*Engine, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
entries := make([]Entry, 0, 1024)
|
||||
var geoIPReader *geoip2.Reader
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if len(line) == 0 || strings.HasPrefix(line, "#") {
|
||||
// Ignore empty lines & comments
|
||||
continue
|
||||
}
|
||||
entry, err := ParseEntry(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := entry.Matcher.(*countryMatcher); ok && geoIPReader == nil {
|
||||
geoIPReader, err = geoIPLoadFunc() // lazy load GeoIP reader only when needed
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
cache, err := lru.NewARC[cacheKey, cacheValue](entryCacheSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Engine{
|
||||
DefaultAction: ActionProxy,
|
||||
Entries: entries,
|
||||
Cache: cache,
|
||||
ResolveIPAddr: resolveIPAddr,
|
||||
GeoIPReader: geoIPReader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// action, arg, isDomain, resolvedIP, error
|
||||
func (e *Engine) ResolveAndMatch(host string, port uint16, isUDP bool) (Action, string, bool, *net.IPAddr, error) {
|
||||
ip, zone := utils.ParseIPZone(host)
|
||||
if ip == nil {
|
||||
// Domain
|
||||
ipAddr, err := e.ResolveIPAddr(host)
|
||||
if ce, ok := e.Cache.Get(cacheKey{host, port, isUDP}); ok {
|
||||
// Cache hit
|
||||
return ce.Action, ce.Arg, true, ipAddr, err
|
||||
}
|
||||
for _, entry := range e.Entries {
|
||||
mReq := MatchRequest{
|
||||
Domain: host,
|
||||
Port: port,
|
||||
DB: e.GeoIPReader,
|
||||
}
|
||||
if ipAddr != nil {
|
||||
mReq.IP = ipAddr.IP
|
||||
}
|
||||
if isUDP {
|
||||
mReq.Protocol = ProtocolUDP
|
||||
} else {
|
||||
mReq.Protocol = ProtocolTCP
|
||||
}
|
||||
if entry.Match(mReq) {
|
||||
e.Cache.Add(cacheKey{host, port, isUDP},
|
||||
cacheValue{entry.Action, entry.ActionArg})
|
||||
return entry.Action, entry.ActionArg, true, ipAddr, err
|
||||
}
|
||||
}
|
||||
e.Cache.Add(cacheKey{host, port, isUDP}, cacheValue{e.DefaultAction, ""})
|
||||
return e.DefaultAction, "", true, ipAddr, err
|
||||
} else {
|
||||
// IP
|
||||
if ce, ok := e.Cache.Get(cacheKey{ip.String(), port, isUDP}); ok {
|
||||
// Cache hit
|
||||
return ce.Action, ce.Arg, false, &net.IPAddr{
|
||||
IP: ip,
|
||||
Zone: zone,
|
||||
}, nil
|
||||
}
|
||||
for _, entry := range e.Entries {
|
||||
mReq := MatchRequest{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
DB: e.GeoIPReader,
|
||||
}
|
||||
if isUDP {
|
||||
mReq.Protocol = ProtocolUDP
|
||||
} else {
|
||||
mReq.Protocol = ProtocolTCP
|
||||
}
|
||||
if entry.Match(mReq) {
|
||||
e.Cache.Add(cacheKey{ip.String(), port, isUDP},
|
||||
cacheValue{entry.Action, entry.ActionArg})
|
||||
return entry.Action, entry.ActionArg, false, &net.IPAddr{
|
||||
IP: ip,
|
||||
Zone: zone,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
e.Cache.Add(cacheKey{ip.String(), port, isUDP}, cacheValue{e.DefaultAction, ""})
|
||||
return e.DefaultAction, "", false, &net.IPAddr{
|
||||
IP: ip,
|
||||
Zone: zone,
|
||||
}, nil
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
)
|
||||
|
||||
func TestEngine_ResolveAndMatch(t *testing.T) {
|
||||
cache, _ := lru.NewARC[cacheKey, cacheValue](entryCacheSize)
|
||||
e := &Engine{
|
||||
DefaultAction: ActionDirect,
|
||||
Entries: []Entry{
|
||||
{
|
||||
Action: ActionProxy,
|
||||
ActionArg: "",
|
||||
Matcher: &domainMatcher{
|
||||
matcherBase: matcherBase{
|
||||
Protocol: ProtocolTCP,
|
||||
Port: 443,
|
||||
},
|
||||
Domain: "google.com",
|
||||
Suffix: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: ActionHijack,
|
||||
ActionArg: "good.org",
|
||||
Matcher: &domainMatcher{
|
||||
matcherBase: matcherBase{},
|
||||
Domain: "evil.corp",
|
||||
Suffix: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: ActionProxy,
|
||||
ActionArg: "",
|
||||
Matcher: &netMatcher{
|
||||
matcherBase: matcherBase{},
|
||||
Net: &net.IPNet{
|
||||
IP: net.ParseIP("10.0.0.0"),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: ActionBlock,
|
||||
ActionArg: "",
|
||||
Matcher: &allMatcher{},
|
||||
},
|
||||
},
|
||||
Cache: cache,
|
||||
ResolveIPAddr: func(s string) (*net.IPAddr, error) {
|
||||
if strings.Contains(s, "evil.corp") {
|
||||
return nil, errors.New("resolve error")
|
||||
}
|
||||
return net.ResolveIPAddr("ip", s)
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
host string
|
||||
port uint16
|
||||
isUDP bool
|
||||
wantAction Action
|
||||
wantArg string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "domain proxy",
|
||||
host: "google.com",
|
||||
port: 443,
|
||||
isUDP: false,
|
||||
wantAction: ActionProxy,
|
||||
wantArg: "",
|
||||
},
|
||||
{
|
||||
name: "domain block",
|
||||
host: "google.com",
|
||||
port: 80,
|
||||
isUDP: false,
|
||||
wantAction: ActionBlock,
|
||||
wantArg: "",
|
||||
},
|
||||
{
|
||||
name: "domain suffix 1",
|
||||
host: "evil.corp",
|
||||
port: 8899,
|
||||
isUDP: true,
|
||||
wantAction: ActionHijack,
|
||||
wantArg: "good.org",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "domain suffix 2",
|
||||
host: "notevil.corp",
|
||||
port: 22,
|
||||
isUDP: false,
|
||||
wantAction: ActionBlock,
|
||||
wantArg: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "domain suffix 3",
|
||||
host: "im.real.evil.corp",
|
||||
port: 443,
|
||||
isUDP: true,
|
||||
wantAction: ActionHijack,
|
||||
wantArg: "good.org",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ip match",
|
||||
host: "10.2.3.4",
|
||||
port: 80,
|
||||
isUDP: false,
|
||||
wantAction: ActionProxy,
|
||||
wantArg: "",
|
||||
},
|
||||
{
|
||||
name: "ip mismatch",
|
||||
host: "100.5.6.0",
|
||||
port: 1234,
|
||||
isUDP: false,
|
||||
wantAction: ActionBlock,
|
||||
wantArg: "",
|
||||
},
|
||||
{
|
||||
name: "domain proxy cache",
|
||||
host: "google.com",
|
||||
port: 443,
|
||||
isUDP: false,
|
||||
wantAction: ActionProxy,
|
||||
wantArg: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotAction, gotArg, _, _, err := e.ResolveAndMatch(tt.host, tt.port, tt.isUDP)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ResolveAndMatch() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotAction != tt.wantAction {
|
||||
t.Errorf("ResolveAndMatch() gotAction = %v, wantAction %v", gotAction, tt.wantAction)
|
||||
}
|
||||
if gotArg != tt.wantArg {
|
||||
t.Errorf("ResolveAndMatch() gotArg = %v, wantAction %v", gotArg, tt.wantArg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,334 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
type (
|
||||
Action byte
|
||||
Protocol byte
|
||||
)
|
||||
|
||||
const (
|
||||
ActionDirect = Action(iota)
|
||||
ActionProxy
|
||||
ActionBlock
|
||||
ActionHijack
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolAll = Protocol(iota)
|
||||
ProtocolTCP
|
||||
ProtocolUDP
|
||||
)
|
||||
|
||||
var protocolPortAliases = map[string]string{
|
||||
"echo": "*/7",
|
||||
"ftp-data": "*/20",
|
||||
"ftp": "*/21",
|
||||
"ssh": "*/22",
|
||||
"telnet": "*/23",
|
||||
"domain": "*/53",
|
||||
"dns": "*/53",
|
||||
"http": "*/80",
|
||||
"sftp": "*/115",
|
||||
"ntp": "*/123",
|
||||
"https": "*/443",
|
||||
"quic": "udp/443",
|
||||
"socks": "*/1080",
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Action Action
|
||||
ActionArg string
|
||||
Matcher Matcher
|
||||
}
|
||||
|
||||
type MatchRequest struct {
|
||||
IP net.IP
|
||||
Domain string
|
||||
|
||||
Protocol Protocol
|
||||
Port uint16
|
||||
|
||||
DB *geoip2.Reader
|
||||
}
|
||||
|
||||
type Matcher interface {
|
||||
Match(MatchRequest) bool
|
||||
}
|
||||
|
||||
type matcherBase struct {
|
||||
Protocol Protocol
|
||||
Port uint16 // 0 for all ports
|
||||
}
|
||||
|
||||
func (m *matcherBase) MatchProtocolPort(p Protocol, port uint16) bool {
|
||||
return (m.Protocol == ProtocolAll || m.Protocol == p) && (m.Port == 0 || m.Port == port)
|
||||
}
|
||||
|
||||
func parseProtocolPort(s string) (Protocol, uint16, error) {
|
||||
if protocolPortAliases[s] != "" {
|
||||
s = protocolPortAliases[s]
|
||||
}
|
||||
if len(s) == 0 || s == "*" {
|
||||
return ProtocolAll, 0, nil
|
||||
}
|
||||
parts := strings.Split(s, "/")
|
||||
if len(parts) != 2 {
|
||||
return ProtocolAll, 0, errors.New("invalid protocol/port syntax")
|
||||
}
|
||||
protocol := ProtocolAll
|
||||
switch parts[0] {
|
||||
case "tcp":
|
||||
protocol = ProtocolTCP
|
||||
case "udp":
|
||||
protocol = ProtocolUDP
|
||||
case "*":
|
||||
protocol = ProtocolAll
|
||||
default:
|
||||
return ProtocolAll, 0, errors.New("invalid protocol")
|
||||
}
|
||||
if parts[1] == "*" {
|
||||
return protocol, 0, nil
|
||||
}
|
||||
port, err := strconv.ParseUint(parts[1], 10, 16)
|
||||
if err != nil {
|
||||
return ProtocolAll, 0, errors.New("invalid port")
|
||||
}
|
||||
return protocol, uint16(port), nil
|
||||
}
|
||||
|
||||
type netMatcher struct {
|
||||
matcherBase
|
||||
Net *net.IPNet
|
||||
}
|
||||
|
||||
func (m *netMatcher) Match(r MatchRequest) bool {
|
||||
if r.IP == nil {
|
||||
return false
|
||||
}
|
||||
return m.Net.Contains(r.IP) && m.MatchProtocolPort(r.Protocol, r.Port)
|
||||
}
|
||||
|
||||
type domainMatcher struct {
|
||||
matcherBase
|
||||
Domain string
|
||||
Suffix bool
|
||||
}
|
||||
|
||||
func (m *domainMatcher) Match(r MatchRequest) bool {
|
||||
if len(r.Domain) == 0 {
|
||||
return false
|
||||
}
|
||||
domain := strings.ToLower(r.Domain)
|
||||
return (m.Domain == domain || (m.Suffix && strings.HasSuffix(domain, "."+m.Domain))) &&
|
||||
m.MatchProtocolPort(r.Protocol, r.Port)
|
||||
}
|
||||
|
||||
type countryMatcher struct {
|
||||
matcherBase
|
||||
Country string // ISO 3166-1 alpha-2 country code, upper case
|
||||
}
|
||||
|
||||
func (m *countryMatcher) Match(r MatchRequest) bool {
|
||||
if r.IP == nil || r.DB == nil {
|
||||
return false
|
||||
}
|
||||
c, err := r.DB.Country(r.IP)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return c.Country.IsoCode == m.Country && m.MatchProtocolPort(r.Protocol, r.Port)
|
||||
}
|
||||
|
||||
type allMatcher struct {
|
||||
matcherBase
|
||||
}
|
||||
|
||||
func (m *allMatcher) Match(r MatchRequest) bool {
|
||||
return m.MatchProtocolPort(r.Protocol, r.Port)
|
||||
}
|
||||
|
||||
func (e Entry) Match(r MatchRequest) bool {
|
||||
return e.Matcher.Match(r)
|
||||
}
|
||||
|
||||
func ParseEntry(s string) (Entry, error) {
|
||||
fields := strings.Fields(s)
|
||||
if len(fields) < 2 {
|
||||
return Entry{}, fmt.Errorf("expected at least 2 fields, got %d", len(fields))
|
||||
}
|
||||
e := Entry{}
|
||||
action := fields[0]
|
||||
conds := fields[1:]
|
||||
switch strings.ToLower(action) {
|
||||
case "direct":
|
||||
e.Action = ActionDirect
|
||||
case "proxy":
|
||||
e.Action = ActionProxy
|
||||
case "block":
|
||||
e.Action = ActionBlock
|
||||
case "hijack":
|
||||
if len(conds) < 2 {
|
||||
return Entry{}, fmt.Errorf("hijack requires at least 3 fields, got %d", len(fields))
|
||||
}
|
||||
e.Action = ActionHijack
|
||||
e.ActionArg = conds[len(conds)-1]
|
||||
conds = conds[:len(conds)-1]
|
||||
default:
|
||||
return Entry{}, fmt.Errorf("invalid action %s", fields[0])
|
||||
}
|
||||
m, err := condsToMatcher(conds)
|
||||
if err != nil {
|
||||
return Entry{}, err
|
||||
}
|
||||
e.Matcher = m
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func condsToMatcher(conds []string) (Matcher, error) {
|
||||
if len(conds) < 1 {
|
||||
return nil, errors.New("no condition specified")
|
||||
}
|
||||
typ, args := conds[0], conds[1:]
|
||||
switch strings.ToLower(typ) {
|
||||
case "domain":
|
||||
// domain <domain> <optional: protocol/port>
|
||||
if len(args) == 0 || len(args) > 2 {
|
||||
return nil, fmt.Errorf("invalid number of arguments for domain: %d, expected 1 or 2", len(args))
|
||||
}
|
||||
mb := matcherBase{}
|
||||
if len(args) == 2 {
|
||||
protocol, port, err := parseProtocolPort(args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mb.Protocol = protocol
|
||||
mb.Port = port
|
||||
}
|
||||
return &domainMatcher{
|
||||
matcherBase: mb,
|
||||
Domain: args[0],
|
||||
Suffix: false,
|
||||
}, nil
|
||||
case "domain-suffix":
|
||||
// domain-suffix <domain> <optional: protocol/port>
|
||||
if len(args) == 0 || len(args) > 2 {
|
||||
return nil, fmt.Errorf("invalid number of arguments for domain-suffix: %d, expected 1 or 2", len(args))
|
||||
}
|
||||
mb := matcherBase{}
|
||||
if len(args) == 2 {
|
||||
protocol, port, err := parseProtocolPort(args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mb.Protocol = protocol
|
||||
mb.Port = port
|
||||
}
|
||||
return &domainMatcher{
|
||||
matcherBase: mb,
|
||||
Domain: args[0],
|
||||
Suffix: true,
|
||||
}, nil
|
||||
case "cidr":
|
||||
// cidr <cidr> <optional: protocol/port>
|
||||
if len(args) == 0 || len(args) > 2 {
|
||||
return nil, fmt.Errorf("invalid number of arguments for cidr: %d, expected 1 or 2", len(args))
|
||||
}
|
||||
mb := matcherBase{}
|
||||
if len(args) == 2 {
|
||||
protocol, port, err := parseProtocolPort(args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mb.Protocol = protocol
|
||||
mb.Port = port
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &netMatcher{
|
||||
matcherBase: mb,
|
||||
Net: ipNet,
|
||||
}, nil
|
||||
case "ip":
|
||||
// ip <ip> <optional: protocol/port>
|
||||
if len(args) == 0 || len(args) > 2 {
|
||||
return nil, fmt.Errorf("invalid number of arguments for ip: %d, expected 1 or 2", len(args))
|
||||
}
|
||||
mb := matcherBase{}
|
||||
if len(args) == 2 {
|
||||
protocol, port, err := parseProtocolPort(args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mb.Protocol = protocol
|
||||
mb.Port = port
|
||||
}
|
||||
ip := net.ParseIP(args[0])
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid ip: %s", args[0])
|
||||
}
|
||||
var ipNet *net.IPNet
|
||||
if ip.To4() != nil {
|
||||
ipNet = &net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}
|
||||
} else {
|
||||
ipNet = &net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(128, 128),
|
||||
}
|
||||
}
|
||||
return &netMatcher{
|
||||
matcherBase: mb,
|
||||
Net: ipNet,
|
||||
}, nil
|
||||
case "country":
|
||||
// country <country> <optional: protocol/port>
|
||||
if len(args) == 0 || len(args) > 2 {
|
||||
return nil, fmt.Errorf("invalid number of arguments for country: %d, expected 1 or 2", len(args))
|
||||
}
|
||||
mb := matcherBase{}
|
||||
if len(args) == 2 {
|
||||
protocol, port, err := parseProtocolPort(args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mb.Protocol = protocol
|
||||
mb.Port = port
|
||||
}
|
||||
return &countryMatcher{
|
||||
matcherBase: mb,
|
||||
Country: strings.ToUpper(args[0]),
|
||||
}, nil
|
||||
case "all":
|
||||
// all <optional: protocol/port>
|
||||
if len(args) > 1 {
|
||||
return nil, fmt.Errorf("invalid number of arguments for all: %d, expected 0 or 1", len(args))
|
||||
}
|
||||
mb := matcherBase{}
|
||||
if len(args) == 1 {
|
||||
protocol, port, err := parseProtocolPort(args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mb.Protocol = protocol
|
||||
mb.Port = port
|
||||
}
|
||||
return &allMatcher{
|
||||
matcherBase: mb,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid condition type: %s", typ)
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseEntry(t *testing.T) {
|
||||
_, ok3net, _ := net.ParseCIDR("8.8.8.0/24")
|
||||
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Entry
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "empty", args: args{""}, want: Entry{}, wantErr: true},
|
||||
{
|
||||
name: "ok 1", args: args{"direct domain-suffix google.com"},
|
||||
want: Entry{ActionDirect, "", &domainMatcher{
|
||||
matcherBase: matcherBase{},
|
||||
Domain: "google.com",
|
||||
Suffix: true,
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ok 2", args: args{"proxy domain shithole"},
|
||||
want: Entry{ActionProxy, "", &domainMatcher{
|
||||
matcherBase: matcherBase{},
|
||||
Domain: "shithole",
|
||||
Suffix: false,
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ok 3", args: args{"block cidr 8.8.8.0/24 */53"},
|
||||
want: Entry{ActionBlock, "", &netMatcher{
|
||||
matcherBase: matcherBase{ProtocolAll, 53},
|
||||
Net: ok3net,
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ok 4", args: args{"hijack all udp/* udpblackhole.net"},
|
||||
want: Entry{ActionHijack, "udpblackhole.net", &allMatcher{
|
||||
matcherBase: matcherBase{ProtocolUDP, 0},
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "err 1", args: args{"what the heck"},
|
||||
want: Entry{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "err 2", args: args{"proxy sucks ass"},
|
||||
want: Entry{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "err 3", args: args{"block ip 999.999.999.999"},
|
||||
want: Entry{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "err 4", args: args{"hijack domain google.com"},
|
||||
want: Entry{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "err 5", args: args{"hijack domain google.com bing.com 123"},
|
||||
want: Entry{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseEntry(tt.args.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseEntry() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ParseEntry() got = %v, wantAction %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
439
core/client/client.go
Normal file
439
core/client/client.go
Normal file
|
@ -0,0 +1,439 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
coreErrs "github.com/apernet/hysteria/core/errors"
|
||||
"github.com/apernet/hysteria/core/internal/congestion"
|
||||
"github.com/apernet/hysteria/core/internal/frag"
|
||||
"github.com/apernet/hysteria/core/internal/protocol"
|
||||
"github.com/apernet/hysteria/core/internal/utils"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
)
|
||||
|
||||
const (
|
||||
udpMessageChanSize = 1024
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
DialTCP(addr string) (net.Conn, error)
|
||||
ListenUDP() (HyUDPConn, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type HyUDPConn interface {
|
||||
Receive() ([]byte, string, error)
|
||||
Send([]byte, string) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
func NewClient(config *Config) (Client, error) {
|
||||
if err := config.fill(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &clientImpl{
|
||||
config: config,
|
||||
}
|
||||
c.conn = &autoReconnectConn{
|
||||
Connect: c.connect,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
config *Config
|
||||
conn *autoReconnectConn
|
||||
|
||||
udpSM udpSessionManager
|
||||
}
|
||||
|
||||
type udpSessionEntry struct {
|
||||
Ch chan *protocol.UDPMessage
|
||||
D *frag.Defragger
|
||||
Closed bool
|
||||
}
|
||||
|
||||
type udpSessionManager struct {
|
||||
mutex sync.RWMutex
|
||||
m map[uint32]*udpSessionEntry
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) Init() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.m = make(map[uint32]*udpSessionEntry)
|
||||
}
|
||||
|
||||
// Add returns both a channel for receiving messages and a function to close the channel & delete the session.
|
||||
func (m *udpSessionManager) Add(id uint32) (<-chan *protocol.UDPMessage, func()) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// Important: make sure we add and delete the channel in the same map,
|
||||
// as the map may be replaced by Init() at any time.
|
||||
currentM := m.m
|
||||
|
||||
entry := &udpSessionEntry{
|
||||
Ch: make(chan *protocol.UDPMessage, udpMessageChanSize),
|
||||
D: &frag.Defragger{},
|
||||
Closed: false,
|
||||
}
|
||||
currentM[id] = entry
|
||||
|
||||
return entry.Ch, func() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
if entry.Closed {
|
||||
// Double close a channel will panic,
|
||||
// so we need a flag to make sure we only close it once.
|
||||
return
|
||||
}
|
||||
entry.Closed = true
|
||||
close(entry.Ch)
|
||||
delete(currentM, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) Feed(msg *protocol.UDPMessage) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
entry, ok := m.m[msg.SessionID]
|
||||
if !ok {
|
||||
// No such session, drop the message
|
||||
return
|
||||
}
|
||||
dfMsg := entry.D.Feed(msg)
|
||||
if dfMsg == nil {
|
||||
// Not a complete message yet
|
||||
return
|
||||
}
|
||||
select {
|
||||
case entry.Ch <- dfMsg:
|
||||
// OK
|
||||
default:
|
||||
// Channel is full, drop the message
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientImpl) connect() (quic.Connection, func(), error) {
|
||||
// Use a new packet conn for each connection,
|
||||
// remember to close it after the QUIC connection is closed.
|
||||
pktConn, err := c.config.ConnFactory.New(c.config.ServerAddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Convert config to TLS config & QUIC config
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: c.config.TLSConfig.InsecureSkipVerify,
|
||||
RootCAs: c.config.TLSConfig.RootCAs,
|
||||
}
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: c.config.QUICConfig.InitialStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: c.config.QUICConfig.MaxStreamReceiveWindow,
|
||||
InitialConnectionReceiveWindow: c.config.QUICConfig.InitialConnectionReceiveWindow,
|
||||
MaxConnectionReceiveWindow: c.config.QUICConfig.MaxConnectionReceiveWindow,
|
||||
MaxIdleTimeout: c.config.QUICConfig.MaxIdleTimeout,
|
||||
KeepAlivePeriod: c.config.QUICConfig.KeepAlivePeriod,
|
||||
DisablePathMTUDiscovery: c.config.QUICConfig.DisablePathMTUDiscovery,
|
||||
EnableDatagrams: true,
|
||||
}
|
||||
// Prepare RoundTripper
|
||||
var conn quic.EarlyConnection
|
||||
rt := &http3.RoundTripper{
|
||||
EnableDatagrams: true,
|
||||
TLSClientConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Dial: func(ctx context.Context, _ string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
qc, err := quic.DialEarlyContext(ctx, pktConn, c.config.ServerAddr, c.config.ServerName, tlsCfg, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn = qc
|
||||
return qc, nil
|
||||
},
|
||||
}
|
||||
// Send auth HTTP request
|
||||
req := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: protocol.URLHost,
|
||||
Path: protocol.URLPath,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
}
|
||||
protocol.AuthRequestDataToHeader(req.Header, c.config.Auth, c.config.BandwidthConfig.MaxRx)
|
||||
resp, err := rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
if conn != nil {
|
||||
_ = conn.CloseWithError(0, "")
|
||||
}
|
||||
_ = pktConn.Close()
|
||||
return nil, nil, &coreErrs.ConnectError{Err: err}
|
||||
}
|
||||
if resp.StatusCode != protocol.StatusAuthOK {
|
||||
_ = conn.CloseWithError(0, "")
|
||||
_ = pktConn.Close()
|
||||
return nil, nil, &coreErrs.AuthError{StatusCode: resp.StatusCode}
|
||||
}
|
||||
// Auth OK
|
||||
serverRx := protocol.AuthResponseDataFromHeader(resp.Header)
|
||||
// actualTx = min(serverRx, clientTx)
|
||||
actualTx := serverRx
|
||||
if actualTx == 0 || actualTx > c.config.BandwidthConfig.MaxTx {
|
||||
actualTx = c.config.BandwidthConfig.MaxTx
|
||||
}
|
||||
// Set congestion control when applicable
|
||||
if actualTx > 0 {
|
||||
conn.SetCongestionControl(congestion.NewBrutalSender(actualTx))
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
|
||||
c.udpSM.Init()
|
||||
go c.udpLoop(conn)
|
||||
|
||||
return conn, func() {
|
||||
_ = conn.CloseWithError(0, "")
|
||||
_ = pktConn.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) udpLoop(conn quic.Connection) {
|
||||
for {
|
||||
msg, err := conn.ReceiveMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.handleUDPMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// client <- remote direction
|
||||
func (c *clientImpl) handleUDPMessage(msg []byte) {
|
||||
udpMsg, err := protocol.ParseUDPMessage(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.udpSM.Feed(udpMsg)
|
||||
}
|
||||
|
||||
// openStream wraps the stream with QStream, which handles Close() properly
|
||||
func (c *clientImpl) openStream() (quic.Connection, quic.Stream, error) {
|
||||
qc, stream, err := c.conn.OpenStream()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return qc, &utils.QStream{Stream: stream}, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) DialTCP(addr string) (net.Conn, error) {
|
||||
qc, stream, err := c.openStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = protocol.WriteTCPRequest(stream, addr)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if c.config.FastOpen {
|
||||
// Don't wait for the response when fast open is enabled.
|
||||
// Return the connection immediately, defer the response handling
|
||||
// to the first Read() call.
|
||||
return &tcpConn{
|
||||
Orig: stream,
|
||||
PseudoLocalAddr: qc.LocalAddr(),
|
||||
PseudoRemoteAddr: qc.RemoteAddr(),
|
||||
Established: false,
|
||||
}, nil
|
||||
}
|
||||
// Read response
|
||||
ok, msg, err := protocol.ReadTCPResponse(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
_ = stream.Close()
|
||||
return nil, coreErrs.DialError{Message: msg}
|
||||
}
|
||||
return &tcpConn{
|
||||
Orig: stream,
|
||||
PseudoLocalAddr: qc.LocalAddr(),
|
||||
PseudoRemoteAddr: qc.RemoteAddr(),
|
||||
Established: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) ListenUDP() (HyUDPConn, error) {
|
||||
qc, stream, err := c.openStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = protocol.WriteUDPRequest(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Read response
|
||||
ok, sessionID, msg, err := protocol.ReadUDPResponse(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
_ = stream.Close()
|
||||
return nil, coreErrs.DialError{Message: msg}
|
||||
}
|
||||
|
||||
ch, closeFunc := c.udpSM.Add(sessionID)
|
||||
uc := &udpConn{
|
||||
QC: qc,
|
||||
Stream: stream,
|
||||
SessionID: sessionID,
|
||||
Ch: ch,
|
||||
CloseFunc: closeFunc,
|
||||
SendBuf: make([]byte, protocol.MaxUDPSize),
|
||||
}
|
||||
go uc.Hold()
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
type tcpConn struct {
|
||||
Orig quic.Stream
|
||||
PseudoLocalAddr net.Addr
|
||||
PseudoRemoteAddr net.Addr
|
||||
Established bool
|
||||
}
|
||||
|
||||
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
||||
if !c.Established {
|
||||
// Read response
|
||||
ok, msg, err := protocol.ReadTCPResponse(c.Orig)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ok {
|
||||
return 0, coreErrs.DialError{Message: msg}
|
||||
}
|
||||
c.Established = true
|
||||
}
|
||||
return c.Orig.Read(b)
|
||||
}
|
||||
|
||||
func (c *tcpConn) Write(b []byte) (n int, err error) {
|
||||
return c.Orig.Write(b)
|
||||
}
|
||||
|
||||
func (c *tcpConn) Close() error {
|
||||
return c.Orig.Close()
|
||||
}
|
||||
|
||||
func (c *tcpConn) LocalAddr() net.Addr {
|
||||
return c.PseudoLocalAddr
|
||||
}
|
||||
|
||||
func (c *tcpConn) RemoteAddr() net.Addr {
|
||||
return c.PseudoRemoteAddr
|
||||
}
|
||||
|
||||
func (c *tcpConn) SetDeadline(t time.Time) error {
|
||||
return c.Orig.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tcpConn) SetReadDeadline(t time.Time) error {
|
||||
return c.Orig.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *tcpConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.Orig.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type udpConn struct {
|
||||
QC quic.Connection
|
||||
Stream quic.Stream
|
||||
SessionID uint32
|
||||
Ch <-chan *protocol.UDPMessage
|
||||
CloseFunc func()
|
||||
SendBuf []byte
|
||||
}
|
||||
|
||||
func (c *udpConn) Hold() {
|
||||
// Hold (drain) the stream until someone closes it.
|
||||
// Closing the stream is the signal to stop the UDP session.
|
||||
_, _ = io.Copy(io.Discard, c.Stream)
|
||||
_ = c.Close()
|
||||
}
|
||||
|
||||
func (c *udpConn) Receive() ([]byte, string, error) {
|
||||
msg := <-c.Ch
|
||||
if msg == nil {
|
||||
// Closed
|
||||
return nil, "", io.EOF
|
||||
}
|
||||
return msg.Data, msg.Addr, nil
|
||||
}
|
||||
|
||||
// Send is not thread-safe as it uses a shared send buffer for now.
|
||||
func (c *udpConn) Send(data []byte, addr string) error {
|
||||
// Try no frag first
|
||||
msg := protocol.UDPMessage{
|
||||
SessionID: c.SessionID,
|
||||
PacketID: 0,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: addr,
|
||||
Data: data,
|
||||
}
|
||||
n := msg.Serialize(c.SendBuf)
|
||||
if n < 0 {
|
||||
// Message even larger than MaxUDPSize, drop it
|
||||
// Maybe we should return an error in the future?
|
||||
return nil
|
||||
}
|
||||
sendErr := c.QC.SendMessage(c.SendBuf[:n])
|
||||
if sendErr == nil {
|
||||
// All good
|
||||
return nil
|
||||
}
|
||||
var errTooLarge quic.ErrMessageTooLarge
|
||||
if errors.As(sendErr, &errTooLarge) {
|
||||
// Message too large, try fragmentation
|
||||
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
|
||||
fMsgs := frag.FragUDPMessage(msg, int(errTooLarge))
|
||||
for _, fMsg := range fMsgs {
|
||||
n = fMsg.Serialize(c.SendBuf)
|
||||
err := c.QC.SendMessage(c.SendBuf[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Other error
|
||||
return sendErr
|
||||
}
|
||||
|
||||
func (c *udpConn) Close() error {
|
||||
c.CloseFunc()
|
||||
return c.Stream.Close()
|
||||
}
|
107
core/client/config.go
Normal file
107
core/client/config.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/errors"
|
||||
"github.com/apernet/hysteria/core/internal/pmtud"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultStreamReceiveWindow = 8388608 // 8MB
|
||||
defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 // 20MB
|
||||
defaultMaxIdleTimeout = 30 * time.Second
|
||||
defaultKeepAlivePeriod = 10 * time.Second
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ConnFactory ConnFactory
|
||||
ServerAddr net.Addr
|
||||
ServerName string // host or host:port
|
||||
Auth string
|
||||
TLSConfig TLSConfig
|
||||
QUICConfig QUICConfig
|
||||
BandwidthConfig BandwidthConfig
|
||||
FastOpen bool
|
||||
}
|
||||
|
||||
// fill fills the fields that are not set by the user with default values when possible,
|
||||
// and returns an error if the user has not set a required field.
|
||||
func (c *Config) fill() error {
|
||||
if c.ConnFactory == nil {
|
||||
c.ConnFactory = &udpConnFactory{}
|
||||
}
|
||||
if c.ServerAddr == nil {
|
||||
return errors.ConfigError{Field: "ServerAddr", Reason: "must be set"}
|
||||
}
|
||||
if c.ServerName == "" {
|
||||
return errors.ConfigError{Field: "ServerName", Reason: "must be set"}
|
||||
}
|
||||
if c.QUICConfig.InitialStreamReceiveWindow == 0 {
|
||||
c.QUICConfig.InitialStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if c.QUICConfig.InitialStreamReceiveWindow < 16384 {
|
||||
return errors.ConfigError{Field: "QUICConfig.InitialStreamReceiveWindow", Reason: "must be at least 16384"}
|
||||
}
|
||||
if c.QUICConfig.MaxStreamReceiveWindow == 0 {
|
||||
c.QUICConfig.MaxStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if c.QUICConfig.MaxStreamReceiveWindow < 16384 {
|
||||
return errors.ConfigError{Field: "QUICConfig.MaxStreamReceiveWindow", Reason: "must be at least 16384"}
|
||||
}
|
||||
if c.QUICConfig.InitialConnectionReceiveWindow == 0 {
|
||||
c.QUICConfig.InitialConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if c.QUICConfig.InitialConnectionReceiveWindow < 16384 {
|
||||
return errors.ConfigError{Field: "QUICConfig.InitialConnectionReceiveWindow", Reason: "must be at least 16384"}
|
||||
}
|
||||
if c.QUICConfig.MaxConnectionReceiveWindow == 0 {
|
||||
c.QUICConfig.MaxConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if c.QUICConfig.MaxConnectionReceiveWindow < 16384 {
|
||||
return errors.ConfigError{Field: "QUICConfig.MaxConnectionReceiveWindow", Reason: "must be at least 16384"}
|
||||
}
|
||||
if c.QUICConfig.MaxIdleTimeout == 0 {
|
||||
c.QUICConfig.MaxIdleTimeout = defaultMaxIdleTimeout
|
||||
} else if c.QUICConfig.MaxIdleTimeout < 4*time.Second || c.QUICConfig.MaxIdleTimeout > 120*time.Second {
|
||||
return errors.ConfigError{Field: "QUICConfig.MaxIdleTimeout", Reason: "must be between 4s and 120s"}
|
||||
}
|
||||
if c.QUICConfig.KeepAlivePeriod == 0 {
|
||||
c.QUICConfig.KeepAlivePeriod = defaultKeepAlivePeriod
|
||||
} else if c.QUICConfig.KeepAlivePeriod < 2*time.Second || c.QUICConfig.KeepAlivePeriod > 60*time.Second {
|
||||
return errors.ConfigError{Field: "QUICConfig.KeepAlivePeriod", Reason: "must be between 2s and 60s"}
|
||||
}
|
||||
c.QUICConfig.DisablePathMTUDiscovery = c.QUICConfig.DisablePathMTUDiscovery || pmtud.DisablePathMTUDiscovery
|
||||
return nil
|
||||
}
|
||||
|
||||
type ConnFactory interface {
|
||||
New(net.Addr) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
type udpConnFactory struct{}
|
||||
|
||||
func (f *udpConnFactory) New(addr net.Addr) (net.PacketConn, error) {
|
||||
return net.ListenUDP("udp", nil)
|
||||
}
|
||||
|
||||
// TLSConfig contains the TLS configuration fields that we want to expose to the user.
|
||||
type TLSConfig struct {
|
||||
InsecureSkipVerify bool
|
||||
RootCAs *x509.CertPool
|
||||
}
|
||||
|
||||
// QUICConfig contains the QUIC configuration fields that we want to expose to the user.
|
||||
type QUICConfig struct {
|
||||
InitialStreamReceiveWindow uint64
|
||||
MaxStreamReceiveWindow uint64
|
||||
InitialConnectionReceiveWindow uint64
|
||||
MaxConnectionReceiveWindow uint64
|
||||
MaxIdleTimeout time.Duration
|
||||
KeepAlivePeriod time.Duration
|
||||
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
|
||||
}
|
||||
|
||||
// BandwidthConfig describes the maximum bandwidth that the server can use, in bytes per second.
|
||||
type BandwidthConfig struct {
|
||||
MaxTx uint64
|
||||
MaxRx uint64
|
||||
}
|
68
core/client/reconnect.go
Normal file
68
core/client/reconnect.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// autoReconnectConn is a wrapper of quic.Connection that automatically reconnects
|
||||
// when a non-temporary error (usually a timeout) occurs.
|
||||
type autoReconnectConn struct {
|
||||
// Connect is called whenever a new QUIC connection is needed.
|
||||
// It should return a new QUIC connection, a function to close the connection
|
||||
// (and potentially other underlying resources), and an error if one occurred.
|
||||
Connect func() (quic.Connection, func(), error)
|
||||
|
||||
conn quic.Connection
|
||||
closeFunc func()
|
||||
connMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *autoReconnectConn) OpenStream() (quic.Connection, quic.Stream, error) {
|
||||
c.connMutex.Lock()
|
||||
defer c.connMutex.Unlock()
|
||||
// First time?
|
||||
if c.conn == nil {
|
||||
conn, closeFunc, err := c.Connect()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.conn = conn
|
||||
c.closeFunc = closeFunc
|
||||
}
|
||||
stream, err := c.conn.OpenStream()
|
||||
if err == nil {
|
||||
// All is good
|
||||
return c.conn, stream, nil
|
||||
} else if nErr, ok := err.(net.Error); ok && nErr.Temporary() {
|
||||
// Temporary error, just pass the error to the caller
|
||||
return nil, nil, err
|
||||
} else {
|
||||
// Permanent error
|
||||
// Close the previous connection,
|
||||
// reconnect and try again (only once)
|
||||
c.closeFunc()
|
||||
conn, closeFunc, err := c.Connect()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.conn = conn
|
||||
c.closeFunc = closeFunc
|
||||
stream, err = c.conn.OpenStream()
|
||||
return c.conn, stream, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *autoReconnectConn) Close() error {
|
||||
c.connMutex.Lock()
|
||||
defer c.connMutex.Unlock()
|
||||
if c.conn == nil {
|
||||
return nil
|
||||
}
|
||||
c.closeFunc()
|
||||
c.conn = nil
|
||||
c.closeFunc = nil
|
||||
return nil
|
||||
}
|
|
@ -1,447 +0,0 @@
|
|||
package cs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/pktconns"
|
||||
|
||||
"github.com/apernet/hysteria/core/congestion"
|
||||
|
||||
"github.com/apernet/hysteria/core/pmtud"
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
var ErrClosed = errors.New("closed")
|
||||
|
||||
type Client struct {
|
||||
serverAddr string
|
||||
|
||||
sendBPS, recvBPS uint64
|
||||
auth []byte
|
||||
fastOpen bool
|
||||
|
||||
tlsConfig *tls.Config
|
||||
quicConfig *quic.Config
|
||||
|
||||
pktConnFunc pktconns.ClientPacketConnFunc
|
||||
|
||||
reconnectMutex sync.Mutex
|
||||
pktConn net.PacketConn
|
||||
quicConn quic.Connection
|
||||
closed bool
|
||||
|
||||
udpSessionMutex sync.RWMutex
|
||||
udpSessionMap map[uint32]chan *udpMessage
|
||||
udpDefragger defragger
|
||||
|
||||
quicReconnectFunc func(err error)
|
||||
}
|
||||
|
||||
func NewClient(serverAddr string, auth []byte, tlsConfig *tls.Config, quicConfig *quic.Config,
|
||||
pktConnFunc pktconns.ClientPacketConnFunc, sendBPS uint64, recvBPS uint64, fastOpen bool, lazyStart bool,
|
||||
quicReconnectFunc func(err error),
|
||||
) (*Client, error) {
|
||||
quicConfig.DisablePathMTUDiscovery = quicConfig.DisablePathMTUDiscovery || pmtud.DisablePathMTUDiscovery
|
||||
c := &Client{
|
||||
serverAddr: serverAddr,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
auth: auth,
|
||||
fastOpen: fastOpen,
|
||||
tlsConfig: tlsConfig,
|
||||
quicConfig: quicConfig,
|
||||
pktConnFunc: pktConnFunc,
|
||||
quicReconnectFunc: quicReconnectFunc,
|
||||
}
|
||||
if lazyStart {
|
||||
return c, nil
|
||||
}
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) connect() error {
|
||||
// Clear previous connection
|
||||
if c.quicConn != nil {
|
||||
_ = c.quicConn.CloseWithError(0, "")
|
||||
}
|
||||
if c.pktConn != nil {
|
||||
_ = c.pktConn.Close()
|
||||
}
|
||||
// New connection
|
||||
pktConn, sAddr, err := c.pktConnFunc(c.serverAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Dial QUIC
|
||||
quicConn, err := quic.Dial(pktConn, sAddr, c.serverAddr, c.tlsConfig, c.quicConfig)
|
||||
if err != nil {
|
||||
_ = pktConn.Close()
|
||||
return err
|
||||
}
|
||||
// Control stream
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), protocolTimeout)
|
||||
stream, err := quicConn.OpenStreamSync(ctx)
|
||||
ctxCancel()
|
||||
if err != nil {
|
||||
_ = qErrorProtocol.Send(quicConn)
|
||||
_ = pktConn.Close()
|
||||
return err
|
||||
}
|
||||
ok, msg, err := c.handleControlStream(quicConn, stream)
|
||||
if err != nil {
|
||||
_ = qErrorProtocol.Send(quicConn)
|
||||
_ = pktConn.Close()
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
_ = qErrorAuth.Send(quicConn)
|
||||
_ = pktConn.Close()
|
||||
return fmt.Errorf("auth error: %s", msg)
|
||||
}
|
||||
// All good
|
||||
c.udpSessionMap = make(map[uint32]chan *udpMessage)
|
||||
go c.handleMessage(quicConn)
|
||||
c.pktConn = pktConn
|
||||
c.quicConn = quicConn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleControlStream(qc quic.Connection, stream quic.Stream) (bool, string, error) {
|
||||
// Send protocol version
|
||||
_, err := stream.Write([]byte{protocolVersion})
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Send client hello
|
||||
err = struc.Pack(stream, &clientHello{
|
||||
Rate: maxRate{
|
||||
SendBPS: c.sendBPS,
|
||||
RecvBPS: c.recvBPS,
|
||||
},
|
||||
Auth: c.auth,
|
||||
})
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Receive server hello
|
||||
var sh serverHello
|
||||
err = struc.Unpack(stream, &sh)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if sh.OK {
|
||||
qc.SetCongestionControl(congestion.NewBrutalSender(sh.Rate.RecvBPS))
|
||||
}
|
||||
return sh.OK, sh.Message, nil
|
||||
}
|
||||
|
||||
func (c *Client) handleMessage(qc quic.Connection) {
|
||||
for {
|
||||
msg, err := qc.ReceiveMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
var udpMsg udpMessage
|
||||
err = struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
dfMsg := c.udpDefragger.Feed(udpMsg)
|
||||
if dfMsg == nil {
|
||||
continue
|
||||
}
|
||||
c.udpSessionMutex.RLock()
|
||||
ch, ok := c.udpSessionMap[dfMsg.SessionID]
|
||||
if ok {
|
||||
select {
|
||||
case ch <- dfMsg:
|
||||
// OK
|
||||
default:
|
||||
// Silently drop the message when the channel is full
|
||||
}
|
||||
}
|
||||
c.udpSessionMutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) openStreamWithReconnect() (quic.Connection, quic.Stream, error) {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
if c.closed {
|
||||
return nil, nil, ErrClosed
|
||||
}
|
||||
if c.quicConn != nil {
|
||||
stream, err := c.quicConn.OpenStream()
|
||||
if err == nil {
|
||||
// All good
|
||||
return c.quicConn, &qStream{stream}, nil
|
||||
}
|
||||
// Something is wrong
|
||||
if nErr, ok := err.(net.Error); ok && nErr.Temporary() {
|
||||
// Temporary error, just return
|
||||
return nil, nil, err
|
||||
}
|
||||
if c.quicReconnectFunc != nil {
|
||||
c.quicReconnectFunc(err)
|
||||
}
|
||||
}
|
||||
// Permanent error, need to reconnect
|
||||
if err := c.connect(); err != nil {
|
||||
// Still error, oops
|
||||
return nil, nil, err
|
||||
}
|
||||
// We are not going to try again even if it still fails the second time
|
||||
stream, err := c.quicConn.OpenStream()
|
||||
return c.quicConn, &qStream{stream}, err
|
||||
}
|
||||
|
||||
func (c *Client) DialTCP(addr string) (net.Conn, error) {
|
||||
host, port, err := utils.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session, stream, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = struc.Pack(stream, &clientRequest{
|
||||
UDP: false,
|
||||
Host: host,
|
||||
Port: port,
|
||||
})
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// If fast open is enabled, we return the stream immediately
|
||||
// and defer the response handling to the first Read() call
|
||||
if !c.fastOpen {
|
||||
// Read response
|
||||
var sr serverResponse
|
||||
err = struc.Unpack(stream, &sr)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !sr.OK {
|
||||
_ = stream.Close()
|
||||
return nil, fmt.Errorf("connection rejected: %s", sr.Message)
|
||||
}
|
||||
}
|
||||
return &hyTCPConn{
|
||||
Orig: stream,
|
||||
PseudoLocalAddr: session.LocalAddr(),
|
||||
PseudoRemoteAddr: session.RemoteAddr(),
|
||||
Established: !c.fastOpen,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialUDP() (HyUDPConn, error) {
|
||||
session, stream, err := c.openStreamWithReconnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = struc.Pack(stream, &clientRequest{
|
||||
UDP: true,
|
||||
})
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Read response
|
||||
var sr serverResponse
|
||||
err = struc.Unpack(stream, &sr)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
if !sr.OK {
|
||||
_ = stream.Close()
|
||||
return nil, fmt.Errorf("connection rejected: %s", sr.Message)
|
||||
}
|
||||
|
||||
// Create a session in the map
|
||||
c.udpSessionMutex.Lock()
|
||||
nCh := make(chan *udpMessage, 1024)
|
||||
// Store the current session map for CloseFunc below
|
||||
// to ensure that we are adding and removing sessions on the same map,
|
||||
// as reconnecting will reassign the map
|
||||
sessionMap := c.udpSessionMap
|
||||
sessionMap[sr.UDPSessionID] = nCh
|
||||
c.udpSessionMutex.Unlock()
|
||||
|
||||
pktConn := &hyUDPConn{
|
||||
Session: session,
|
||||
Stream: stream,
|
||||
CloseFunc: func() {
|
||||
c.udpSessionMutex.Lock()
|
||||
if ch, ok := sessionMap[sr.UDPSessionID]; ok {
|
||||
close(ch)
|
||||
delete(sessionMap, sr.UDPSessionID)
|
||||
}
|
||||
c.udpSessionMutex.Unlock()
|
||||
},
|
||||
UDPSessionID: sr.UDPSessionID,
|
||||
MsgCh: nCh,
|
||||
}
|
||||
go pktConn.Hold()
|
||||
return pktConn, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.reconnectMutex.Lock()
|
||||
defer c.reconnectMutex.Unlock()
|
||||
err := qErrorGeneric.Send(c.quicConn)
|
||||
_ = c.pktConn.Close()
|
||||
c.closed = true
|
||||
return err
|
||||
}
|
||||
|
||||
// hyTCPConn wraps a QUIC stream and implements net.Conn returned by Client.DialTCP
|
||||
type hyTCPConn struct {
|
||||
Orig quic.Stream
|
||||
PseudoLocalAddr net.Addr
|
||||
PseudoRemoteAddr net.Addr
|
||||
Established bool
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) Read(b []byte) (n int, err error) {
|
||||
if !w.Established {
|
||||
var sr serverResponse
|
||||
err := struc.Unpack(w.Orig, &sr)
|
||||
if err != nil {
|
||||
_ = w.Close()
|
||||
return 0, err
|
||||
}
|
||||
if !sr.OK {
|
||||
_ = w.Close()
|
||||
return 0, fmt.Errorf("connection rejected: %s", sr.Message)
|
||||
}
|
||||
w.Established = true
|
||||
}
|
||||
return w.Orig.Read(b)
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) Write(b []byte) (n int, err error) {
|
||||
return w.Orig.Write(b)
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) Close() error {
|
||||
return w.Orig.Close()
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) LocalAddr() net.Addr {
|
||||
return w.PseudoLocalAddr
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) RemoteAddr() net.Addr {
|
||||
return w.PseudoRemoteAddr
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) SetDeadline(t time.Time) error {
|
||||
return w.Orig.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) SetReadDeadline(t time.Time) error {
|
||||
return w.Orig.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (w *hyTCPConn) SetWriteDeadline(t time.Time) error {
|
||||
return w.Orig.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
type HyUDPConn interface {
|
||||
ReadFrom() ([]byte, string, error)
|
||||
WriteTo([]byte, string) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type hyUDPConn struct {
|
||||
Session quic.Connection
|
||||
Stream quic.Stream
|
||||
CloseFunc func()
|
||||
UDPSessionID uint32
|
||||
MsgCh <-chan *udpMessage
|
||||
}
|
||||
|
||||
func (c *hyUDPConn) Hold() {
|
||||
// Hold the stream until it's closed
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
_, err := c.Stream.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
_ = c.Close()
|
||||
}
|
||||
|
||||
func (c *hyUDPConn) ReadFrom() ([]byte, string, error) {
|
||||
msg := <-c.MsgCh
|
||||
if msg == nil {
|
||||
// Closed
|
||||
return nil, "", ErrClosed
|
||||
}
|
||||
return msg.Data, net.JoinHostPort(msg.Host, strconv.Itoa(int(msg.Port))), nil
|
||||
}
|
||||
|
||||
func (c *hyUDPConn) WriteTo(p []byte, addr string) error {
|
||||
host, port, err := utils.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := udpMessage{
|
||||
SessionID: c.UDPSessionID,
|
||||
Host: host,
|
||||
Port: port,
|
||||
FragCount: 1,
|
||||
Data: p,
|
||||
}
|
||||
// try no frag first
|
||||
var msgBuf bytes.Buffer
|
||||
_ = struc.Pack(&msgBuf, &msg)
|
||||
err = c.Session.SendMessage(msgBuf.Bytes())
|
||||
if err != nil {
|
||||
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
|
||||
// need to frag
|
||||
msg.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||
fragMsgs := fragUDPMessage(msg, int(errSize))
|
||||
for _, fragMsg := range fragMsgs {
|
||||
msgBuf.Reset()
|
||||
_ = struc.Pack(&msgBuf, &fragMsg)
|
||||
err = c.Session.SendMessage(msgBuf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
// some other error
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *hyUDPConn) Close() error {
|
||||
c.CloseFunc()
|
||||
return c.Stream.Close()
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package cs
|
||||
|
||||
func fragUDPMessage(m udpMessage, maxSize int) []udpMessage {
|
||||
if m.Size() <= maxSize {
|
||||
return []udpMessage{m}
|
||||
}
|
||||
fullPayload := m.Data
|
||||
maxPayloadSize := maxSize - m.HeaderSize()
|
||||
off := 0
|
||||
fragID := uint8(0)
|
||||
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
|
||||
var frags []udpMessage
|
||||
for off < len(fullPayload) {
|
||||
payloadSize := len(fullPayload) - off
|
||||
if payloadSize > maxPayloadSize {
|
||||
payloadSize = maxPayloadSize
|
||||
}
|
||||
frag := m
|
||||
frag.FragID = fragID
|
||||
frag.FragCount = fragCount
|
||||
frag.DataLen = uint16(payloadSize)
|
||||
frag.Data = fullPayload[off : off+payloadSize]
|
||||
frags = append(frags, frag)
|
||||
off += payloadSize
|
||||
fragID++
|
||||
}
|
||||
return frags
|
||||
}
|
||||
|
||||
type defragger struct {
|
||||
msgID uint16
|
||||
frags []*udpMessage
|
||||
count uint8
|
||||
}
|
||||
|
||||
func (d *defragger) Feed(m udpMessage) *udpMessage {
|
||||
if m.FragCount <= 1 {
|
||||
return &m
|
||||
}
|
||||
if m.FragID >= m.FragCount {
|
||||
// wtf is this?
|
||||
return nil
|
||||
}
|
||||
if m.MsgID != d.msgID || m.FragCount != uint8(len(d.frags)) {
|
||||
// new message, clear previous state
|
||||
d.msgID = m.MsgID
|
||||
d.frags = make([]*udpMessage, m.FragCount)
|
||||
d.count = 1
|
||||
d.frags[m.FragID] = &m
|
||||
} else if d.frags[m.FragID] == nil {
|
||||
d.frags[m.FragID] = &m
|
||||
d.count++
|
||||
if int(d.count) == len(d.frags) {
|
||||
// all fragments received, assemble
|
||||
var data []byte
|
||||
for _, frag := range d.frags {
|
||||
data = append(data, frag.Data...)
|
||||
}
|
||||
m.DataLen = uint16(len(data))
|
||||
m.Data = data
|
||||
m.FragID = 0
|
||||
m.FragCount = 1
|
||||
return &m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,390 +0,0 @@
|
|||
package cs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_fragUDPMessage(t *testing.T) {
|
||||
type args struct {
|
||||
m udpMessage
|
||||
maxSize int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []udpMessage
|
||||
}{
|
||||
{
|
||||
"no frag",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
100,
|
||||
},
|
||||
[]udpMessage{
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 frags",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
22,
|
||||
},
|
||||
[]udpMessage{
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 4,
|
||||
Data: []byte("hell"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 1,
|
||||
Data: []byte("o"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"4 frags",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 20,
|
||||
Data: []byte("wow wow wow lol lmao"),
|
||||
},
|
||||
23,
|
||||
},
|
||||
[]udpMessage{
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte("wow w"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 1,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte("ow wo"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 2,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte("w lol"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 3,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte(" lmao"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := fragUDPMessage(tt.args.m, tt.args.maxSize); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("fragUDPMessage() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_defragger_Feed(t *testing.T) {
|
||||
d := &defragger{}
|
||||
type args struct {
|
||||
m udpMessage
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *udpMessage
|
||||
}{
|
||||
{
|
||||
"no frag",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"frag 0 - 1/2",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 0,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte("ilove"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 0 - 2/2",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 0,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 6,
|
||||
Data: []byte("nobody"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 0,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 11,
|
||||
Data: []byte("ilovenobody"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"frag 1 - 1/3",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 0,
|
||||
FragCount: 3,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 1 - 2/3",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 1,
|
||||
FragCount: 3,
|
||||
DataLen: 8,
|
||||
Data: []byte(" shitty "),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 1 - 3/3",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 2,
|
||||
FragCount: 3,
|
||||
DataLen: 7,
|
||||
Data: []byte("world!!"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 20,
|
||||
Data: []byte("hello shitty world!!"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"frag 2 - 1/2",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 3 - 2/2",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 778,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte(" moto"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 2 - 2/2",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte(" moto"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 2 - 1/2 re",
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 10,
|
||||
Data: []byte("hello moto"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := d.Feed(tt.args.m); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Feed() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package cs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
const (
|
||||
protocolVersion = uint8(3)
|
||||
protocolTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type qError struct {
|
||||
Code quic.ApplicationErrorCode
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e qError) Send(c quic.Connection) error {
|
||||
return c.CloseWithError(e.Code, e.Msg)
|
||||
}
|
||||
|
||||
var (
|
||||
qErrorGeneric = qError{0, ""}
|
||||
qErrorProtocol = qError{1, "protocol error"}
|
||||
qErrorAuth = qError{2, "auth error"}
|
||||
)
|
||||
|
||||
type maxRate struct {
|
||||
SendBPS uint64
|
||||
RecvBPS uint64
|
||||
}
|
||||
|
||||
type clientHello struct {
|
||||
Rate maxRate
|
||||
AuthLen uint16 `struc:"sizeof=Auth"`
|
||||
Auth []byte
|
||||
}
|
||||
|
||||
type serverHello struct {
|
||||
OK bool
|
||||
Rate maxRate
|
||||
MessageLen uint16 `struc:"sizeof=Message"`
|
||||
Message string
|
||||
}
|
||||
|
||||
type clientRequest struct {
|
||||
UDP bool
|
||||
HostLen uint16 `struc:"sizeof=Host"`
|
||||
Host string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type serverResponse struct {
|
||||
OK bool
|
||||
UDPSessionID uint32
|
||||
MessageLen uint16 `struc:"sizeof=Message"`
|
||||
Message string
|
||||
}
|
||||
|
||||
type udpMessage struct {
|
||||
SessionID uint32
|
||||
HostLen uint16 `struc:"sizeof=Host"`
|
||||
Host string
|
||||
Port uint16
|
||||
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
|
||||
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
|
||||
FragCount uint8 // must be 1 when not fragmented
|
||||
DataLen uint16 `struc:"sizeof=Data"`
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (m udpMessage) HeaderSize() int {
|
||||
return 4 + 2 + len(m.Host) + 2 + 2 + 1 + 1 + 2
|
||||
}
|
||||
|
||||
func (m udpMessage) Size() int {
|
||||
return m.HeaderSize() + len(m.Data)
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
package cs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/congestion"
|
||||
|
||||
"github.com/apernet/hysteria/core/acl"
|
||||
"github.com/apernet/hysteria/core/pmtud"
|
||||
"github.com/apernet/hysteria/core/transport"
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
type (
|
||||
ConnectFunc func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string)
|
||||
DisconnectFunc func(addr net.Addr, auth []byte, err error)
|
||||
TCPRequestFunc func(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string)
|
||||
TCPErrorFunc func(addr net.Addr, auth []byte, reqAddr string, err error)
|
||||
UDPRequestFunc func(addr net.Addr, auth []byte, sessionID uint32)
|
||||
UDPErrorFunc func(addr net.Addr, auth []byte, sessionID uint32, err error)
|
||||
)
|
||||
|
||||
type TrafficCounter interface {
|
||||
Rx(auth string, n int)
|
||||
Tx(auth string, n int)
|
||||
IncConn(auth string) // increase connection count
|
||||
DecConn(auth string) // decrease connection count
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
transport *transport.ServerTransport
|
||||
sendBPS, recvBPS uint64
|
||||
disableUDP bool
|
||||
aclEngine *acl.Engine
|
||||
|
||||
connectFunc ConnectFunc
|
||||
disconnectFunc DisconnectFunc
|
||||
tcpRequestFunc TCPRequestFunc
|
||||
tcpErrorFunc TCPErrorFunc
|
||||
udpRequestFunc UDPRequestFunc
|
||||
udpErrorFunc UDPErrorFunc
|
||||
|
||||
trafficCounter TrafficCounter
|
||||
|
||||
pktConn net.PacketConn
|
||||
listener quic.Listener
|
||||
}
|
||||
|
||||
func NewServer(tlsConfig *tls.Config, quicConfig *quic.Config,
|
||||
pktConn net.PacketConn, transport *transport.ServerTransport,
|
||||
sendBPS uint64, recvBPS uint64, disableUDP bool, aclEngine *acl.Engine,
|
||||
connectFunc ConnectFunc, disconnectFunc DisconnectFunc,
|
||||
tcpRequestFunc TCPRequestFunc, tcpErrorFunc TCPErrorFunc,
|
||||
udpRequestFunc UDPRequestFunc, udpErrorFunc UDPErrorFunc,
|
||||
trafficCounter TrafficCounter,
|
||||
) (*Server, error) {
|
||||
quicConfig.DisablePathMTUDiscovery = quicConfig.DisablePathMTUDiscovery || pmtud.DisablePathMTUDiscovery
|
||||
listener, err := quic.Listen(pktConn, tlsConfig, quicConfig)
|
||||
if err != nil {
|
||||
_ = pktConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
s := &Server{
|
||||
pktConn: pktConn,
|
||||
listener: listener,
|
||||
transport: transport,
|
||||
sendBPS: sendBPS,
|
||||
recvBPS: recvBPS,
|
||||
disableUDP: disableUDP,
|
||||
aclEngine: aclEngine,
|
||||
connectFunc: connectFunc,
|
||||
disconnectFunc: disconnectFunc,
|
||||
tcpRequestFunc: tcpRequestFunc,
|
||||
tcpErrorFunc: tcpErrorFunc,
|
||||
udpRequestFunc: udpRequestFunc,
|
||||
udpErrorFunc: udpErrorFunc,
|
||||
trafficCounter: trafficCounter,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) Serve() error {
|
||||
for {
|
||||
cc, err := s.listener.Accept(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go s.handleClient(cc)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
err := s.listener.Close()
|
||||
_ = s.pktConn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) handleClient(cc quic.Connection) {
|
||||
// Expect the client to create a control stream to send its own information
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), protocolTimeout)
|
||||
stream, err := cc.AcceptStream(ctx)
|
||||
ctxCancel()
|
||||
if err != nil {
|
||||
_ = qErrorProtocol.Send(cc)
|
||||
return
|
||||
}
|
||||
// Handle the control stream
|
||||
auth, ok, err := s.handleControlStream(cc, stream)
|
||||
if err != nil {
|
||||
_ = qErrorProtocol.Send(cc)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
_ = qErrorAuth.Send(cc)
|
||||
return
|
||||
}
|
||||
// Start accepting streams and messages
|
||||
sc := newServerClient(cc, s.transport, auth, s.disableUDP, s.aclEngine,
|
||||
s.tcpRequestFunc, s.tcpErrorFunc, s.udpRequestFunc, s.udpErrorFunc,
|
||||
s.trafficCounter)
|
||||
err = sc.Run()
|
||||
_ = qErrorGeneric.Send(cc)
|
||||
s.disconnectFunc(cc.RemoteAddr(), auth, err)
|
||||
}
|
||||
|
||||
// Auth & negotiate speed
|
||||
func (s *Server) handleControlStream(cc quic.Connection, stream quic.Stream) ([]byte, bool, error) {
|
||||
// Check version
|
||||
vb := make([]byte, 1)
|
||||
_, err := stream.Read(vb)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if vb[0] != protocolVersion {
|
||||
return nil, false, fmt.Errorf("unsupported protocol version %d, expecting %d", vb[0], protocolVersion)
|
||||
}
|
||||
// Parse client hello
|
||||
var ch clientHello
|
||||
err = struc.Unpack(stream, &ch)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// Speed
|
||||
if ch.Rate.SendBPS == 0 || ch.Rate.RecvBPS == 0 {
|
||||
return nil, false, errors.New("invalid rate from client")
|
||||
}
|
||||
serverSendBPS, serverRecvBPS := ch.Rate.RecvBPS, ch.Rate.SendBPS
|
||||
if s.sendBPS > 0 && serverSendBPS > s.sendBPS {
|
||||
serverSendBPS = s.sendBPS
|
||||
}
|
||||
if s.recvBPS > 0 && serverRecvBPS > s.recvBPS {
|
||||
serverRecvBPS = s.recvBPS
|
||||
}
|
||||
// Auth
|
||||
ok, msg := s.connectFunc(cc.RemoteAddr(), ch.Auth, serverSendBPS, serverRecvBPS)
|
||||
// Response
|
||||
err = struc.Pack(stream, &serverHello{
|
||||
OK: ok,
|
||||
Rate: maxRate{
|
||||
SendBPS: serverSendBPS,
|
||||
RecvBPS: serverRecvBPS,
|
||||
},
|
||||
Message: msg,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if ok {
|
||||
cc.SetCongestionControl(congestion.NewBrutalSender(serverSendBPS))
|
||||
}
|
||||
return ch.Auth, ok, nil
|
||||
}
|
|
@ -1,391 +0,0 @@
|
|||
package cs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/apernet/hysteria/core/acl"
|
||||
"github.com/apernet/hysteria/core/transport"
|
||||
"github.com/apernet/hysteria/core/utils"
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
const udpBufferSize = 4096
|
||||
|
||||
type serverClient struct {
|
||||
CC quic.Connection
|
||||
Transport *transport.ServerTransport
|
||||
Auth []byte
|
||||
AuthLabel string // Base64 encoded auth
|
||||
DisableUDP bool
|
||||
ACLEngine *acl.Engine
|
||||
CTCPRequestFunc TCPRequestFunc
|
||||
CTCPErrorFunc TCPErrorFunc
|
||||
CUDPRequestFunc UDPRequestFunc
|
||||
CUDPErrorFunc UDPErrorFunc
|
||||
|
||||
TrafficCounter TrafficCounter
|
||||
|
||||
udpSessionMutex sync.RWMutex
|
||||
udpSessionMap map[uint32]transport.STPacketConn
|
||||
nextUDPSessionID uint32
|
||||
udpDefragger defragger
|
||||
}
|
||||
|
||||
func newServerClient(cc quic.Connection, tr *transport.ServerTransport, auth []byte, disableUDP bool, ACLEngine *acl.Engine,
|
||||
CTCPRequestFunc TCPRequestFunc, CTCPErrorFunc TCPErrorFunc,
|
||||
CUDPRequestFunc UDPRequestFunc, CUDPErrorFunc UDPErrorFunc,
|
||||
TrafficCounter TrafficCounter,
|
||||
) *serverClient {
|
||||
sc := &serverClient{
|
||||
CC: cc,
|
||||
Transport: tr,
|
||||
Auth: auth,
|
||||
AuthLabel: base64.StdEncoding.EncodeToString(auth),
|
||||
DisableUDP: disableUDP,
|
||||
ACLEngine: ACLEngine,
|
||||
CTCPRequestFunc: CTCPRequestFunc,
|
||||
CTCPErrorFunc: CTCPErrorFunc,
|
||||
CUDPRequestFunc: CUDPRequestFunc,
|
||||
CUDPErrorFunc: CUDPErrorFunc,
|
||||
TrafficCounter: TrafficCounter,
|
||||
udpSessionMap: make(map[uint32]transport.STPacketConn),
|
||||
}
|
||||
return sc
|
||||
}
|
||||
|
||||
func (c *serverClient) ClientAddr() net.Addr {
|
||||
// quic.Connection's remote address may change since we have connection migration now,
|
||||
// so logs need to dynamically get the remote address every time.
|
||||
return c.CC.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *serverClient) Run() error {
|
||||
if !c.DisableUDP {
|
||||
go func() {
|
||||
for {
|
||||
msg, err := c.CC.ReceiveMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
c.handleMessage(msg)
|
||||
}
|
||||
}()
|
||||
}
|
||||
for {
|
||||
stream, err := c.CC.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.TrafficCounter != nil {
|
||||
c.TrafficCounter.IncConn(c.AuthLabel)
|
||||
}
|
||||
go func() {
|
||||
stream := &qStream{stream}
|
||||
c.handleStream(stream)
|
||||
_ = stream.Close()
|
||||
if c.TrafficCounter != nil {
|
||||
c.TrafficCounter.DecConn(c.AuthLabel)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) handleStream(stream quic.Stream) {
|
||||
// Read request
|
||||
var req clientRequest
|
||||
err := struc.Unpack(stream, &req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !req.UDP {
|
||||
// TCP connection
|
||||
c.handleTCP(stream, req.Host, req.Port)
|
||||
} else if !c.DisableUDP {
|
||||
// UDP connection
|
||||
c.handleUDP(stream)
|
||||
} else {
|
||||
// UDP disabled
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "UDP disabled",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) handleMessage(msg []byte) {
|
||||
var udpMsg udpMessage
|
||||
err := struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dfMsg := c.udpDefragger.Feed(udpMsg)
|
||||
if dfMsg == nil {
|
||||
return
|
||||
}
|
||||
c.udpSessionMutex.RLock()
|
||||
conn, ok := c.udpSessionMap[dfMsg.SessionID]
|
||||
c.udpSessionMutex.RUnlock()
|
||||
if ok {
|
||||
// Session found, send the message
|
||||
action, arg := acl.ActionDirect, ""
|
||||
var isDomain bool
|
||||
var ipAddr *net.IPAddr
|
||||
var err error
|
||||
if c.ACLEngine != nil {
|
||||
action, arg, isDomain, ipAddr, err = c.ACLEngine.ResolveAndMatch(dfMsg.Host, dfMsg.Port, true)
|
||||
} else if c.Transport.ProxyEnabled() { // Case for SOCKS5 outbound
|
||||
ipAddr, isDomain = c.Transport.ParseIPAddr(dfMsg.Host) // It is safe to leave ipAddr as nil since addrExToSOCKS5Addr will ignore it when there is a domain
|
||||
err = nil
|
||||
} else {
|
||||
ipAddr, isDomain, err = c.Transport.ResolveIPAddr(dfMsg.Host)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case acl.ActionDirect, acl.ActionProxy: // Treat proxy as direct on server side
|
||||
addrEx := &transport.AddrEx{
|
||||
IPAddr: ipAddr,
|
||||
Port: int(dfMsg.Port),
|
||||
}
|
||||
if isDomain {
|
||||
addrEx.Domain = dfMsg.Host
|
||||
}
|
||||
_, _ = conn.WriteTo(dfMsg.Data, addrEx)
|
||||
if c.TrafficCounter != nil {
|
||||
c.TrafficCounter.Tx(c.AuthLabel, len(dfMsg.Data))
|
||||
}
|
||||
case acl.ActionBlock:
|
||||
// Do nothing
|
||||
case acl.ActionHijack:
|
||||
var isDomain bool
|
||||
var hijackIPAddr *net.IPAddr
|
||||
var err error
|
||||
if c.Transport.ProxyEnabled() { // Case for domain requests + SOCKS5 outbound
|
||||
hijackIPAddr, isDomain = c.Transport.ParseIPAddr(arg) // It is safe to leave ipAddr as nil since addrExToSOCKS5Addr will ignore it when there is a domain
|
||||
err = nil
|
||||
} else {
|
||||
hijackIPAddr, isDomain, err = c.Transport.ResolveIPAddr(arg)
|
||||
}
|
||||
if err == nil {
|
||||
addrEx := &transport.AddrEx{
|
||||
IPAddr: hijackIPAddr,
|
||||
Port: int(dfMsg.Port),
|
||||
}
|
||||
if isDomain {
|
||||
addrEx.Domain = arg
|
||||
}
|
||||
_, _ = conn.WriteTo(dfMsg.Data, addrEx)
|
||||
if c.TrafficCounter != nil {
|
||||
c.TrafficCounter.Tx(c.AuthLabel, len(dfMsg.Data))
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverClient) handleTCP(stream quic.Stream, host string, port uint16) {
|
||||
addrStr := net.JoinHostPort(host, strconv.Itoa(int(port)))
|
||||
action, arg := acl.ActionDirect, ""
|
||||
var isDomain bool
|
||||
var ipAddr *net.IPAddr
|
||||
var err error
|
||||
if c.ACLEngine != nil {
|
||||
action, arg, isDomain, ipAddr, err = c.ACLEngine.ResolveAndMatch(host, port, false)
|
||||
} else if c.Transport.ProxyEnabled() { // Case for domain requests + SOCKS5 outbound
|
||||
ipAddr, isDomain = c.Transport.ParseIPAddr(host) // It is safe to leave ipAddr as nil since addrExToSOCKS5Addr will ignore it when there is a domain
|
||||
err = nil
|
||||
} else {
|
||||
ipAddr, isDomain, err = c.Transport.ResolveIPAddr(host)
|
||||
}
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "host resolution failure",
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr(), c.Auth, addrStr, err)
|
||||
return
|
||||
}
|
||||
c.CTCPRequestFunc(c.ClientAddr(), c.Auth, addrStr, action, arg)
|
||||
|
||||
var conn net.Conn // Connection to be piped
|
||||
switch action {
|
||||
case acl.ActionDirect, acl.ActionProxy: // Treat proxy as direct on server side
|
||||
addrEx := &transport.AddrEx{
|
||||
IPAddr: ipAddr,
|
||||
Port: int(port),
|
||||
}
|
||||
if isDomain {
|
||||
addrEx.Domain = host
|
||||
}
|
||||
conn, err = c.Transport.DialTCP(addrEx)
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr(), c.Auth, addrStr, err)
|
||||
return
|
||||
}
|
||||
case acl.ActionBlock:
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "blocked by ACL",
|
||||
})
|
||||
return
|
||||
case acl.ActionHijack:
|
||||
var isDomain bool
|
||||
var hijackIPAddr *net.IPAddr
|
||||
var err error
|
||||
if c.Transport.ProxyEnabled() { // Case for domain requests + SOCKS5 outbound
|
||||
hijackIPAddr, isDomain = c.Transport.ParseIPAddr(arg) // It is safe to leave ipAddr as nil since addrExToSOCKS5Addr will ignore it when there is a domain
|
||||
err = nil
|
||||
} else {
|
||||
hijackIPAddr, isDomain, err = c.Transport.ResolveIPAddr(arg)
|
||||
}
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr(), c.Auth, addrStr, err)
|
||||
return
|
||||
}
|
||||
addrEx := &transport.AddrEx{
|
||||
IPAddr: hijackIPAddr,
|
||||
Port: int(port),
|
||||
}
|
||||
if isDomain {
|
||||
addrEx.Domain = arg
|
||||
}
|
||||
conn, err = c.Transport.DialTCP(addrEx)
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
c.CTCPErrorFunc(c.ClientAddr(), c.Auth, addrStr, err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "ACL error",
|
||||
})
|
||||
return
|
||||
}
|
||||
// So far so good if we reach here
|
||||
defer conn.Close()
|
||||
err = struc.Pack(stream, &serverResponse{
|
||||
OK: true,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.TrafficCounter != nil {
|
||||
err = utils.Pipe2Way(stream, conn, func(i int) {
|
||||
if i > 0 {
|
||||
c.TrafficCounter.Tx(c.AuthLabel, i)
|
||||
} else {
|
||||
c.TrafficCounter.Rx(c.AuthLabel, -i)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
err = utils.Pipe2Way(stream, conn, nil)
|
||||
}
|
||||
c.CTCPErrorFunc(c.ClientAddr(), c.Auth, addrStr, err)
|
||||
}
|
||||
|
||||
func (c *serverClient) handleUDP(stream quic.Stream) {
|
||||
// Like in SOCKS5, the stream here is only used to maintain the UDP session. No need to read anything from it
|
||||
conn, err := c.Transport.ListenUDP()
|
||||
if err != nil {
|
||||
_ = struc.Pack(stream, &serverResponse{
|
||||
OK: false,
|
||||
Message: "UDP initialization failed",
|
||||
})
|
||||
c.CUDPErrorFunc(c.ClientAddr(), c.Auth, 0, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var id uint32
|
||||
c.udpSessionMutex.Lock()
|
||||
id = c.nextUDPSessionID
|
||||
c.udpSessionMap[id] = conn
|
||||
c.nextUDPSessionID += 1
|
||||
c.udpSessionMutex.Unlock()
|
||||
|
||||
err = struc.Pack(stream, &serverResponse{
|
||||
OK: true,
|
||||
UDPSessionID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.CUDPRequestFunc(c.ClientAddr(), c.Auth, id)
|
||||
|
||||
// Receive UDP packets, send them to the client
|
||||
go func() {
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, rAddr, err := conn.ReadFrom(buf)
|
||||
if n > 0 {
|
||||
var msgBuf bytes.Buffer
|
||||
msg := udpMessage{
|
||||
SessionID: id,
|
||||
Host: rAddr.IP.String(),
|
||||
Port: uint16(rAddr.Port),
|
||||
FragCount: 1,
|
||||
Data: buf[:n],
|
||||
}
|
||||
// try no frag first
|
||||
_ = struc.Pack(&msgBuf, &msg)
|
||||
sendErr := c.CC.SendMessage(msgBuf.Bytes())
|
||||
if sendErr != nil {
|
||||
if errSize, ok := sendErr.(quic.ErrMessageTooLarge); ok {
|
||||
// need to frag
|
||||
msg.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||
fragMsgs := fragUDPMessage(msg, int(errSize))
|
||||
for _, fragMsg := range fragMsgs {
|
||||
msgBuf.Reset()
|
||||
_ = struc.Pack(&msgBuf, &fragMsg)
|
||||
_ = c.CC.SendMessage(msgBuf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.TrafficCounter != nil {
|
||||
c.TrafficCounter.Rx(c.AuthLabel, n)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
_ = stream.Close()
|
||||
}()
|
||||
|
||||
// Hold the stream until it's closed by the client
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
_, err = stream.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.CUDPErrorFunc(c.ClientAddr(), c.Auth, id, err)
|
||||
|
||||
// Remove the session
|
||||
c.udpSessionMutex.Lock()
|
||||
delete(c.udpSessionMap, id)
|
||||
c.udpSessionMutex.Unlock()
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package cs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// qStream is a wrapper of quic.Stream that handles Close() correctly.
|
||||
// quic-go's quic.Stream.Close() only closes the write side of the stream,
|
||||
// NOT the read side. This would cause the pipe(s) to hang at Read() even
|
||||
// after the stream is supposedly "closed".
|
||||
// Ref: https://github.com/libp2p/go-libp2p/blob/master/p2p/transport/quic/stream.go
|
||||
type qStream struct {
|
||||
Stream quic.Stream
|
||||
}
|
||||
|
||||
func (s *qStream) StreamID() quic.StreamID {
|
||||
return s.Stream.StreamID()
|
||||
}
|
||||
|
||||
func (s *qStream) Read(p []byte) (n int, err error) {
|
||||
return s.Stream.Read(p)
|
||||
}
|
||||
|
||||
func (s *qStream) CancelRead(code quic.StreamErrorCode) {
|
||||
s.Stream.CancelRead(code)
|
||||
}
|
||||
|
||||
func (s *qStream) SetReadDeadline(t time.Time) error {
|
||||
return s.Stream.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (s *qStream) Write(p []byte) (n int, err error) {
|
||||
return s.Stream.Write(p)
|
||||
}
|
||||
|
||||
func (s *qStream) Close() error {
|
||||
s.Stream.CancelRead(0)
|
||||
return s.Stream.Close()
|
||||
}
|
||||
|
||||
func (s *qStream) CancelWrite(code quic.StreamErrorCode) {
|
||||
s.Stream.CancelWrite(code)
|
||||
}
|
||||
|
||||
func (s *qStream) Context() context.Context {
|
||||
return s.Stream.Context()
|
||||
}
|
||||
|
||||
func (s *qStream) SetWriteDeadline(t time.Time) error {
|
||||
return s.Stream.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *qStream) SetDeadline(t time.Time) error {
|
||||
return s.Stream.SetDeadline(t)
|
||||
}
|
58
core/errors/errors.go
Normal file
58
core/errors/errors.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ConfigError is returned when a configuration field is invalid.
|
||||
type ConfigError struct {
|
||||
Field string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (c ConfigError) Error() string {
|
||||
return fmt.Sprintf("invalid config: %s: %s", c.Field, c.Reason)
|
||||
}
|
||||
|
||||
// ConnectError is returned when the client fails to connect to the server.
|
||||
type ConnectError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (c ConnectError) Error() string {
|
||||
return "connect error: " + c.Err.Error()
|
||||
}
|
||||
|
||||
func (c ConnectError) Unwrap() error {
|
||||
return c.Err
|
||||
}
|
||||
|
||||
// AuthError is returned when the client fails to authenticate with the server.
|
||||
type AuthError struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (a AuthError) Error() string {
|
||||
return "authentication error, HTTP status code: " + strconv.Itoa(a.StatusCode)
|
||||
}
|
||||
|
||||
// DialError is returned when the server rejects the client's dial request.
|
||||
// This applies to both TCP and UDP.
|
||||
type DialError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (c DialError) Error() string {
|
||||
return "dial error: " + c.Message
|
||||
}
|
||||
|
||||
// ProtocolError is returned when the server/client runs into an unexpected
|
||||
// or malformed request/response/message.
|
||||
type ProtocolError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (p ProtocolError) Error() string {
|
||||
return "protocol error: " + p.Message
|
||||
}
|
34
core/go.mod
34
core/go.mod
|
@ -3,34 +3,28 @@ module github.com/apernet/hysteria/core
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/quic-go/quic-go v0.34.0
|
||||
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a
|
||||
golang.org/x/sys v0.7.0
|
||||
github.com/quic-go/quic-go v0.0.0-00010101000000-000000000000
|
||||
golang.org/x/time v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230131232505-5a9e8f65f08f // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.8.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
|
||||
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.3.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/quic-go/quic-go => github.com/apernet/quic-go v0.34.1-0.20230507231629-ec008b7e8473
|
||||
|
|
75
core/go.sum
75
core/go.sum
|
@ -1,11 +1,11 @@
|
|||
github.com/apernet/quic-go v0.34.1-0.20230507231629-ec008b7e8473 h1:3KFetJ/lUFn0m9xTFg+rMmz2nyHg+D2boJX0Rp4OF6c=
|
||||
github.com/apernet/quic-go v0.34.1-0.20230507231629-ec008b7e8473/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||
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/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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
|
@ -14,25 +14,17 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
|||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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/pprof v0.0.0-20230131232505-5a9e8f65f08f h1:gl1DCiSk+mrXXBGPm6CEeS2MkJuMVzAOrXg34oVj1QI=
|
||||
github.com/google/pprof v0.0.0-20230131232505-5a9e8f65f08f/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
|
||||
github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU=
|
||||
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
|
||||
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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
|
@ -45,55 +37,50 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
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/socks5 v0.0.0-20220212043548-414499347d4a h1:BOqgJ4jku0LHPDoR51RD8Mxmo0LHxCzJT/M9MemYdHo=
|
||||
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
|
||||
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
|
||||
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE=
|
||||
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
77
core/internal/frag/frag.go
Normal file
77
core/internal/frag/frag.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package frag
|
||||
|
||||
import (
|
||||
"github.com/apernet/hysteria/core/internal/protocol"
|
||||
)
|
||||
|
||||
func FragUDPMessage(m protocol.UDPMessage, maxSize int) []protocol.UDPMessage {
|
||||
if m.Size() <= maxSize {
|
||||
return []protocol.UDPMessage{m}
|
||||
}
|
||||
fullPayload := m.Data
|
||||
maxPayloadSize := maxSize - m.HeaderSize()
|
||||
off := 0
|
||||
fragID := uint8(0)
|
||||
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
|
||||
frags := make([]protocol.UDPMessage, fragCount)
|
||||
for off < len(fullPayload) {
|
||||
payloadSize := len(fullPayload) - off
|
||||
if payloadSize > maxPayloadSize {
|
||||
payloadSize = maxPayloadSize
|
||||
}
|
||||
frag := m
|
||||
frag.FragID = fragID
|
||||
frag.FragCount = fragCount
|
||||
frag.Data = fullPayload[off : off+payloadSize]
|
||||
frags[fragID] = frag
|
||||
off += payloadSize
|
||||
fragID++
|
||||
}
|
||||
return frags
|
||||
}
|
||||
|
||||
// Defragger handles the defragmentation of UDP messages.
|
||||
// The current implementation can only handle one packet ID at a time.
|
||||
// If another packet arrives before a packet has received all fragments
|
||||
// in their entirety, any previous state is discarded.
|
||||
type Defragger struct {
|
||||
pktID uint16
|
||||
frags []*protocol.UDPMessage
|
||||
count uint8
|
||||
size int // data size
|
||||
}
|
||||
|
||||
func (d *Defragger) Feed(m *protocol.UDPMessage) *protocol.UDPMessage {
|
||||
if m.FragCount <= 1 {
|
||||
return m
|
||||
}
|
||||
if m.FragID >= m.FragCount {
|
||||
// wtf is this?
|
||||
return nil
|
||||
}
|
||||
if m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {
|
||||
// new message, clear previous state
|
||||
d.pktID = m.PacketID
|
||||
d.frags = make([]*protocol.UDPMessage, m.FragCount)
|
||||
d.frags[m.FragID] = m
|
||||
d.count = 1
|
||||
d.size = len(m.Data)
|
||||
} else if d.frags[m.FragID] == nil {
|
||||
d.frags[m.FragID] = m
|
||||
d.count++
|
||||
d.size += len(m.Data)
|
||||
if int(d.count) == len(d.frags) {
|
||||
// all fragments received, assemble
|
||||
data := make([]byte, d.size)
|
||||
off := 0
|
||||
for _, frag := range d.frags {
|
||||
off += copy(data[off:], frag.Data)
|
||||
}
|
||||
m.Data = data
|
||||
m.FragID = 0
|
||||
m.FragCount = 1
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
336
core/internal/frag/frag_test.go
Normal file
336
core/internal/frag/frag_test.go
Normal file
|
@ -0,0 +1,336 @@
|
|||
package frag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/apernet/hysteria/core/internal/protocol"
|
||||
)
|
||||
|
||||
func TestFragUDPMessage(t *testing.T) {
|
||||
type args struct {
|
||||
m protocol.UDPMessage
|
||||
maxSize int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []protocol.UDPMessage
|
||||
}{
|
||||
{
|
||||
"no frag",
|
||||
args{
|
||||
protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
100,
|
||||
},
|
||||
[]protocol.UDPMessage{
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 frags",
|
||||
args{
|
||||
protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
20,
|
||||
},
|
||||
[]protocol.UDPMessage{
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hel"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("lo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"4 frags",
|
||||
args{
|
||||
protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("abcdefgh"),
|
||||
},
|
||||
19,
|
||||
},
|
||||
[]protocol.UDPMessage{
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 4,
|
||||
Addr: "test:123",
|
||||
Data: []byte("ab"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 1,
|
||||
FragCount: 4,
|
||||
Addr: "test:123",
|
||||
Data: []byte("cd"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 2,
|
||||
FragCount: 4,
|
||||
Addr: "test:123",
|
||||
Data: []byte("ef"),
|
||||
},
|
||||
{
|
||||
SessionID: 123,
|
||||
PacketID: 123,
|
||||
FragID: 3,
|
||||
FragCount: 4,
|
||||
Addr: "test:123",
|
||||
Data: []byte("gh"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := FragUDPMessage(tt.args.m, tt.args.maxSize); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("FragUDPMessage() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefragger(t *testing.T) {
|
||||
type args struct {
|
||||
m *protocol.UDPMessage
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *protocol.UDPMessage
|
||||
}{
|
||||
{
|
||||
"no frag",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"frag 0 - 1/2",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello "),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 0 - 2/2",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("moto"),
|
||||
},
|
||||
},
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("hello moto"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"frag 1 - 1/3",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 0,
|
||||
FragCount: 3,
|
||||
Addr: "test:123",
|
||||
Data: []byte("deco"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 1 - 2/3",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 1,
|
||||
FragCount: 3,
|
||||
Addr: "test:123",
|
||||
Data: []byte("*"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 1 - 3/3",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 2,
|
||||
FragCount: 3,
|
||||
Addr: "test:123",
|
||||
Data: []byte("27"),
|
||||
},
|
||||
},
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 987,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("deco*27"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"frag 2 - 1/2",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 233,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("shinsekai"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 3 - 2/2",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 244,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("what???"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 2 - 2/2",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 233,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte(" annaijo"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"invalid id",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 233,
|
||||
FragID: 88,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("shinsekai"),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"frag 2 - 1/2 re",
|
||||
args{
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 233,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
Addr: "test:123",
|
||||
Data: []byte("shinsekai"),
|
||||
},
|
||||
},
|
||||
&protocol.UDPMessage{
|
||||
SessionID: 123,
|
||||
PacketID: 233,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "test:123",
|
||||
Data: []byte("shinsekai annaijo"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d := &Defragger{}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := d.Feed(tt.args.m); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Feed() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
191
core/internal/integration_tests/close_test.go
Normal file
191
core/internal/integration_tests/close_test.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
package integration_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
// TestClientServerTCPClose tests whether the client/server propagates the close of a connection correctly.
|
||||
// In other words, closing one of the client/remote connections should cause the other to close as well.
|
||||
func TestClientServerTCPClose(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
t.Run("Close local", func(t *testing.T) {
|
||||
// TCP sink server
|
||||
sinkAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 33344}
|
||||
sinkListener, err := net.ListenTCP("tcp", sinkAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating sink server:", err)
|
||||
}
|
||||
sinkCh := make(chan sinkEvent, 1)
|
||||
sinkServer := &tcpSinkServer{
|
||||
Listener: sinkListener,
|
||||
Ch: sinkCh,
|
||||
}
|
||||
defer sinkServer.Close()
|
||||
go sinkServer.Serve()
|
||||
|
||||
// Generate some random data
|
||||
sData := make([]byte, 1024000)
|
||||
_, err = rand.Read(sData)
|
||||
if err != nil {
|
||||
t.Fatal("error generating random data:", err)
|
||||
}
|
||||
|
||||
// Dial and send data to TCP sink server
|
||||
conn, err := c.DialTCP(sinkAddr.String())
|
||||
if err != nil {
|
||||
t.Fatal("error dialing TCP:", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write(sData)
|
||||
if err != nil {
|
||||
t.Fatal("error writing to TCP:", err)
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
// This should cause the sink server to send an event to the channel
|
||||
_ = conn.Close()
|
||||
event := <-sinkCh
|
||||
if event.Err != nil {
|
||||
t.Fatal("non-nil error received from sink server:", event.Err)
|
||||
}
|
||||
if !bytes.Equal(event.Data, sData) {
|
||||
t.Fatal("data mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Close remote", func(t *testing.T) {
|
||||
// Generate some random data
|
||||
sData := make([]byte, 1024000)
|
||||
_, err = rand.Read(sData)
|
||||
if err != nil {
|
||||
t.Fatal("error generating random data:", err)
|
||||
}
|
||||
|
||||
// TCP sender server
|
||||
senderAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 33345}
|
||||
senderListener, err := net.ListenTCP("tcp", senderAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating sender server:", err)
|
||||
}
|
||||
senderServer := &tcpSenderServer{
|
||||
Listener: senderListener,
|
||||
Data: sData,
|
||||
}
|
||||
defer senderServer.Close()
|
||||
go senderServer.Serve()
|
||||
|
||||
// Dial and read data from TCP sender server
|
||||
conn, err := c.DialTCP(senderAddr.String())
|
||||
if err != nil {
|
||||
t.Fatal("error dialing TCP:", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
rData, err := io.ReadAll(conn)
|
||||
if err != nil {
|
||||
t.Fatal("error reading from TCP:", err)
|
||||
}
|
||||
if !bytes.Equal(rData, sData) {
|
||||
t.Fatal("data mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestClientServerUDPClose is the same as TestClientServerTCPClose, but for UDP.
|
||||
// Checking for UDP close is a bit tricky, so we will rely on the server event for now.
|
||||
func TestClientServerUDPClose(t *testing.T) {
|
||||
urCh := make(chan udpRequestEvent, 1)
|
||||
ueCh := make(chan udpErrorEvent, 1)
|
||||
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
EventLogger: &channelEventLogger{
|
||||
UDPRequestEventCh: urCh,
|
||||
UDPErrorEventCh: ueCh,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Listen UDP and close it, then check the server events
|
||||
conn, err := c.ListenUDP()
|
||||
if err != nil {
|
||||
t.Fatal("error listening UDP:", err)
|
||||
}
|
||||
_ = conn.Close()
|
||||
|
||||
reqEvent := <-urCh
|
||||
if reqEvent.ID != "nobody" {
|
||||
t.Fatal("incorrect ID in request event")
|
||||
}
|
||||
errEvent := <-ueCh
|
||||
if errEvent.ID != "nobody" {
|
||||
t.Fatal("incorrect ID in error event")
|
||||
}
|
||||
if errEvent.Err != nil {
|
||||
t.Fatal("non-nil error received from server:", errEvent.Err)
|
||||
}
|
||||
}
|
130
core/internal/integration_tests/masq_test.go
Normal file
130
core/internal/integration_tests/masq_test.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package integration_tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/internal/protocol"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
)
|
||||
|
||||
// TestServerMasquerade is a test to ensure that the server behaves as a normal
|
||||
// HTTP/3 server when dealing with an unauthenticated client. This is mainly to
|
||||
// confirm that the server does not expose itself to active probers.
|
||||
func TestServerMasquerade(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// QUIC connection & RoundTripper
|
||||
var conn quic.EarlyConnection
|
||||
rt := &http3.RoundTripper{
|
||||
EnableDatagrams: true,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Dial: func(ctx context.Context, _ string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
qc, err := quic.DialAddrEarlyContext(ctx, udpAddr.String(), tlsCfg, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn = qc
|
||||
return qc, nil
|
||||
},
|
||||
}
|
||||
defer rt.Close() // This will close the QUIC connection
|
||||
|
||||
// Send the bogus request
|
||||
// We expect 404 (from the default handler)
|
||||
req := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: protocol.URLHost,
|
||||
Path: protocol.URLPath,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
}
|
||||
resp, err := rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
t.Fatal("error sending request:", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
for k := range resp.Header {
|
||||
// Make sure no strange headers are sent
|
||||
if strings.Contains(k, "Hysteria") {
|
||||
t.Fatal("expected no Hysteria headers, got", k)
|
||||
}
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
|
||||
// We send a TCP request anyway, see if we get a response
|
||||
tcpStream, err := conn.OpenStream()
|
||||
if err != nil {
|
||||
t.Fatal("error opening stream:", err)
|
||||
}
|
||||
defer tcpStream.Close()
|
||||
err = protocol.WriteTCPRequest(tcpStream, "www.google.com:443")
|
||||
if err != nil {
|
||||
t.Fatal("error sending request:", err)
|
||||
}
|
||||
|
||||
// We should receive nothing
|
||||
_ = tcpStream.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
n, err := tcpStream.Read(buf)
|
||||
if n != 0 {
|
||||
t.Fatal("expected no response, got", n)
|
||||
}
|
||||
if nErr, ok := err.(net.Error); !ok || !nErr.Timeout() {
|
||||
t.Fatal("expected timeout, got", err)
|
||||
}
|
||||
|
||||
// Try UDP request
|
||||
udpStream, err := conn.OpenStream()
|
||||
if err != nil {
|
||||
t.Fatal("error opening stream:", err)
|
||||
}
|
||||
defer udpStream.Close()
|
||||
err = protocol.WriteUDPRequest(udpStream)
|
||||
if err != nil {
|
||||
t.Fatal("error sending request:", err)
|
||||
}
|
||||
|
||||
// We should receive nothing
|
||||
_ = udpStream.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
n, err = udpStream.Read(buf)
|
||||
if n != 0 {
|
||||
t.Fatal("expected no response, got", n)
|
||||
}
|
||||
if nErr, ok := err.(net.Error); !ok || !nErr.Timeout() {
|
||||
t.Fatal("expected timeout, got", err)
|
||||
}
|
||||
}
|
229
core/internal/integration_tests/smoke_test.go
Normal file
229
core/internal/integration_tests/smoke_test.go
Normal file
|
@ -0,0 +1,229 @@
|
|||
package integration_tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
coreErrs "github.com/apernet/hysteria/core/errors"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
// Smoke tests that act as a sanity check for client & server to ensure they can talk to each other correctly.
|
||||
|
||||
// TestClientNoServer tests how the client handles a server that doesn't exist.
|
||||
// The client should still be able to be created, but TCP & UDP requests should fail.
|
||||
func TestClientNoServer(t *testing.T) {
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514},
|
||||
ServerName: "not_a_real_server",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
var cErr *coreErrs.ConnectError
|
||||
|
||||
// Try TCP
|
||||
_, err = c.DialTCP("google.com:443")
|
||||
if !errors.As(err, &cErr) {
|
||||
t.Fatal("expected connect error from DialTCP")
|
||||
}
|
||||
|
||||
// Try UDP
|
||||
_, err = c.ListenUDP()
|
||||
if !errors.As(err, &cErr) {
|
||||
t.Fatal("expected connect error from ListenUDP")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientServerBadAuth tests two things:
|
||||
// - The server uses Authenticator when a client connects.
|
||||
// - How the client handles failed authentication.
|
||||
func TestClientServerBadAuth(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "correct password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "wrong password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
var aErr *coreErrs.AuthError
|
||||
|
||||
// Try TCP
|
||||
_, err = c.DialTCP("google.com:443")
|
||||
if !errors.As(err, &aErr) {
|
||||
t.Fatal("expected auth error from DialTCP")
|
||||
}
|
||||
|
||||
// Try UDP
|
||||
_, err = c.ListenUDP()
|
||||
if !errors.As(err, &aErr) {
|
||||
t.Fatal("expected auth error from ListenUDP")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientServerTCPEcho tests TCP forwarding using a TCP echo server.
|
||||
func TestClientServerTCPEcho(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create TCP echo server
|
||||
echoTCPAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14515}
|
||||
echoListener, err := net.ListenTCP("tcp", echoTCPAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating TCP echo server:", err)
|
||||
}
|
||||
echoServer := &tcpEchoServer{Listener: echoListener}
|
||||
defer echoServer.Close()
|
||||
go echoServer.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Dial TCP
|
||||
conn, err := c.DialTCP(echoTCPAddr.String())
|
||||
if err != nil {
|
||||
t.Fatal("error dialing TCP:", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Send and receive data
|
||||
sData := []byte("hello world")
|
||||
_, err = conn.Write(sData)
|
||||
if err != nil {
|
||||
t.Fatal("error writing to TCP:", err)
|
||||
}
|
||||
rData := make([]byte, len(sData))
|
||||
_, err = io.ReadFull(conn, rData)
|
||||
if err != nil {
|
||||
t.Fatal("error reading from TCP:", err)
|
||||
}
|
||||
if string(rData) != string(sData) {
|
||||
t.Fatalf("expected %q, got %q", sData, rData)
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientServerUDPEcho tests UDP forwarding using a UDP echo server.
|
||||
func TestClientServerUDPEcho(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create UDP echo server
|
||||
echoUDPAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 55555}
|
||||
echoConn, err := net.ListenUDP("udp", echoUDPAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating UDP echo server:", err)
|
||||
}
|
||||
echoServer := &udpEchoServer{Conn: echoConn}
|
||||
defer echoServer.Close()
|
||||
go echoServer.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Listen UDP
|
||||
conn, err := c.ListenUDP()
|
||||
if err != nil {
|
||||
t.Fatal("error listening UDP:", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Send and receive data
|
||||
sData := []byte("hello world")
|
||||
err = conn.Send(sData, echoUDPAddr.String())
|
||||
if err != nil {
|
||||
t.Fatal("error sending UDP:", err)
|
||||
}
|
||||
rData, rAddr, err := conn.Receive()
|
||||
if err != nil {
|
||||
t.Fatal("error receiving UDP:", err)
|
||||
}
|
||||
if string(rData) != string(sData) {
|
||||
t.Fatalf("expected %q, got %q", sData, rData)
|
||||
}
|
||||
if rAddr != echoUDPAddr.String() {
|
||||
t.Fatalf("expected %q, got %q", echoUDPAddr.String(), rAddr)
|
||||
}
|
||||
}
|
291
core/internal/integration_tests/stress_test.go
Normal file
291
core/internal/integration_tests/stress_test.go
Normal file
|
@ -0,0 +1,291 @@
|
|||
package integration_tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
type tcpStressor struct {
|
||||
DialFunc func() (net.Conn, error)
|
||||
Size int
|
||||
Parallel int
|
||||
Iterations int
|
||||
}
|
||||
|
||||
func (s *tcpStressor) Run(t *testing.T) {
|
||||
// Make some random data
|
||||
sData := make([]byte, s.Size)
|
||||
_, err := rand.Read(sData)
|
||||
if err != nil {
|
||||
t.Fatal("error generating random data:", err)
|
||||
}
|
||||
|
||||
// Run iterations
|
||||
for i := 0; i < s.Iterations; i++ {
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, s.Parallel)
|
||||
for j := 0; j < s.Parallel; j++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
conn, err := s.DialFunc()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
go conn.Write(sData)
|
||||
|
||||
rData := make([]byte, len(sData))
|
||||
_, err = io.ReadFull(conn, rData)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errChan) > 0 {
|
||||
t.Fatal("error reading from TCP:", <-errChan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type udpStressor struct {
|
||||
ListenFunc func() (client.HyUDPConn, error)
|
||||
ServerAddr string
|
||||
Size int
|
||||
Count int
|
||||
Parallel int
|
||||
Iterations int
|
||||
}
|
||||
|
||||
func (s *udpStressor) Run(t *testing.T) {
|
||||
// Make some random data
|
||||
sData := make([]byte, s.Size)
|
||||
_, err := rand.Read(sData)
|
||||
if err != nil {
|
||||
t.Fatal("error generating random data:", err)
|
||||
}
|
||||
|
||||
// Due to UDP's unreliability, we need to limit the rate of sending
|
||||
// to reduce packet loss. This is hardcoded to 1 MiB/s for now.
|
||||
limiter := rate.NewLimiter(1048576, 1048576)
|
||||
|
||||
// Run iterations
|
||||
for i := 0; i < s.Iterations; i++ {
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, s.Parallel)
|
||||
for j := 0; j < s.Parallel; j++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
conn, err := s.ListenFunc()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
go func() {
|
||||
// Sending routine
|
||||
for i := 0; i < s.Count; i++ {
|
||||
_ = limiter.WaitN(context.Background(), len(sData))
|
||||
_ = conn.Send(sData, s.ServerAddr)
|
||||
}
|
||||
}()
|
||||
|
||||
minCount := s.Count * 8 / 10 // Tolerate 20% packet loss
|
||||
for i := 0; i < minCount; i++ {
|
||||
rData, _, err := conn.Receive()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
if len(rData) != len(sData) {
|
||||
errChan <- fmt.Errorf("incomplete data received: %d/%d bytes", len(rData), len(sData))
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errChan) > 0 {
|
||||
t.Fatal("error reading from UDP:", <-errChan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientServerTCPStress(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create TCP echo server
|
||||
echoTCPAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14515}
|
||||
echoListener, err := net.ListenTCP("tcp", echoTCPAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating TCP echo server:", err)
|
||||
}
|
||||
echoServer := &tcpEchoServer{Listener: echoListener}
|
||||
defer echoServer.Close()
|
||||
go echoServer.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
dialFunc := func() (net.Conn, error) {
|
||||
return c.DialTCP(echoTCPAddr.String())
|
||||
}
|
||||
|
||||
t.Run("Single 500m", (&tcpStressor{DialFunc: dialFunc, Size: 524288000, Parallel: 1, Iterations: 1}).Run)
|
||||
|
||||
t.Run("Sequential 1000x1m", (&tcpStressor{DialFunc: dialFunc, Size: 1048576, Parallel: 1, Iterations: 1000}).Run)
|
||||
t.Run("Sequential 10000x100k", (&tcpStressor{DialFunc: dialFunc, Size: 102400, Parallel: 1, Iterations: 10000}).Run)
|
||||
|
||||
t.Run("Parallel 100x10m", (&tcpStressor{DialFunc: dialFunc, Size: 10485760, Parallel: 100, Iterations: 1}).Run)
|
||||
t.Run("Parallel 1000x1m", (&tcpStressor{DialFunc: dialFunc, Size: 1048576, Parallel: 1000, Iterations: 1}).Run)
|
||||
}
|
||||
|
||||
func TestClientServerUDPStress(t *testing.T) {
|
||||
// Create server
|
||||
udpAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14514}
|
||||
udpConn, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
s, err := server.NewServer(&server.Config{
|
||||
TLSConfig: serverTLSConfig(),
|
||||
Conn: udpConn,
|
||||
Authenticator: &pwAuthenticator{
|
||||
Password: "password",
|
||||
ID: "nobody",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating server:", err)
|
||||
}
|
||||
defer s.Close()
|
||||
go s.Serve()
|
||||
|
||||
// Create UDP echo server
|
||||
echoUDPAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 14515}
|
||||
echoListener, err := net.ListenUDP("udp", echoUDPAddr)
|
||||
if err != nil {
|
||||
t.Fatal("error creating UDP echo server:", err)
|
||||
}
|
||||
echoServer := &udpEchoServer{Conn: echoListener}
|
||||
defer echoServer.Close()
|
||||
go echoServer.Serve()
|
||||
|
||||
// Create client
|
||||
c, err := client.NewClient(&client.Config{
|
||||
ServerAddr: udpAddr,
|
||||
ServerName: udpAddr.String(),
|
||||
Auth: "password",
|
||||
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("error creating client:", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
t.Run("Single 1000x100b", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 100,
|
||||
Count: 1000,
|
||||
Parallel: 1,
|
||||
Iterations: 1,
|
||||
}).Run)
|
||||
t.Run("Single 1000x3k", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 3000,
|
||||
Count: 1000,
|
||||
Parallel: 1,
|
||||
Iterations: 1,
|
||||
}).Run)
|
||||
|
||||
t.Run("5 Sequential 1000x100b", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 100,
|
||||
Count: 1000,
|
||||
Parallel: 1,
|
||||
Iterations: 5,
|
||||
}).Run)
|
||||
t.Run("5 Sequential 200x3k", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 3000,
|
||||
Count: 200,
|
||||
Parallel: 1,
|
||||
Iterations: 5,
|
||||
}).Run)
|
||||
|
||||
t.Run("2 Sequential 5 Parallel 1000x100b", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 100,
|
||||
Count: 1000,
|
||||
Parallel: 5,
|
||||
Iterations: 2,
|
||||
}).Run)
|
||||
|
||||
t.Run("2 Sequential 5 Parallel 200x3k", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 3000,
|
||||
Count: 200,
|
||||
Parallel: 5,
|
||||
Iterations: 2,
|
||||
}).Run)
|
||||
|
||||
t.Run("10 Sequential 5 Parallel 200x3k", (&udpStressor{
|
||||
ListenFunc: c.ListenUDP,
|
||||
ServerAddr: echoUDPAddr.String(),
|
||||
Size: 3000,
|
||||
Count: 200,
|
||||
Parallel: 5,
|
||||
Iterations: 10,
|
||||
}).Run)
|
||||
}
|
23
core/internal/integration_tests/test.crt
Normal file
23
core/internal/integration_tests/test.crt
Normal file
|
@ -0,0 +1,23 @@
|
|||
-----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-----
|
27
core/internal/integration_tests/test.key
Normal file
27
core/internal/integration_tests/test.key
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----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-----
|
250
core/internal/integration_tests/utils_test.go
Normal file
250
core/internal/integration_tests/utils_test.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
package integration_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
// This file provides utilities for the integration tests.
|
||||
|
||||
const (
|
||||
testCertFile = "test.crt"
|
||||
testKeyFile = "test.key"
|
||||
)
|
||||
|
||||
func serverTLSConfig() server.TLSConfig {
|
||||
cert, err := tls.LoadX509KeyPair(testCertFile, testKeyFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return server.TLSConfig{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
type pwAuthenticator struct {
|
||||
Password string
|
||||
ID string
|
||||
}
|
||||
|
||||
func (a *pwAuthenticator) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
|
||||
if auth != a.Password {
|
||||
return false, ""
|
||||
}
|
||||
return true, a.ID
|
||||
}
|
||||
|
||||
// tcpEchoServer is a TCP server that echoes what it reads from the connection.
|
||||
// It will never actively close the connection.
|
||||
type tcpEchoServer struct {
|
||||
Listener net.Listener
|
||||
}
|
||||
|
||||
func (s *tcpEchoServer) Serve() error {
|
||||
for {
|
||||
conn, err := s.Listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_, _ = io.Copy(conn, conn)
|
||||
_ = conn.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *tcpEchoServer) Close() error {
|
||||
return s.Listener.Close()
|
||||
}
|
||||
|
||||
type sinkEvent struct {
|
||||
Data []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
// tcpSinkServer is a TCP server that reads data from the connection,
|
||||
// and sends what it read to the channel when the connection is closed.
|
||||
type tcpSinkServer struct {
|
||||
Listener net.Listener
|
||||
Ch chan<- sinkEvent
|
||||
}
|
||||
|
||||
func (s *tcpSinkServer) Serve() error {
|
||||
for {
|
||||
conn, err := s.Listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := io.Copy(&buf, conn)
|
||||
_ = conn.Close()
|
||||
s.Ch <- sinkEvent{Data: buf.Bytes(), Err: err}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *tcpSinkServer) Close() error {
|
||||
return s.Listener.Close()
|
||||
}
|
||||
|
||||
// tcpSenderServer is a TCP server that sends data to the connection,
|
||||
// and closes the connection when all data has been sent.
|
||||
type tcpSenderServer struct {
|
||||
Listener net.Listener
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (s *tcpSenderServer) Serve() error {
|
||||
for {
|
||||
conn, err := s.Listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_, _ = conn.Write(s.Data)
|
||||
_ = conn.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *tcpSenderServer) Close() error {
|
||||
return s.Listener.Close()
|
||||
}
|
||||
|
||||
// udpEchoServer is a UDP server that echoes what it reads from the connection.
|
||||
// It will never actively close the connection.
|
||||
type udpEchoServer struct {
|
||||
Conn net.PacketConn
|
||||
}
|
||||
|
||||
func (s *udpEchoServer) Serve() error {
|
||||
buf := make([]byte, 65536)
|
||||
for {
|
||||
n, addr, err := s.Conn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.Conn.WriteTo(buf[:n], addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *udpEchoServer) Close() error {
|
||||
return s.Conn.Close()
|
||||
}
|
||||
|
||||
type connectEvent struct {
|
||||
Addr net.Addr
|
||||
ID string
|
||||
TX uint64
|
||||
}
|
||||
|
||||
type disconnectEvent struct {
|
||||
Addr net.Addr
|
||||
ID string
|
||||
Err error
|
||||
}
|
||||
|
||||
type tcpRequestEvent struct {
|
||||
Addr net.Addr
|
||||
ID string
|
||||
ReqAddr string
|
||||
}
|
||||
|
||||
type tcpErrorEvent struct {
|
||||
Addr net.Addr
|
||||
ID string
|
||||
ReqAddr string
|
||||
Err error
|
||||
}
|
||||
|
||||
type udpRequestEvent struct {
|
||||
Addr net.Addr
|
||||
ID string
|
||||
SessionID uint32
|
||||
}
|
||||
|
||||
type udpErrorEvent struct {
|
||||
Addr net.Addr
|
||||
ID string
|
||||
SessionID uint32
|
||||
Err error
|
||||
}
|
||||
|
||||
type channelEventLogger struct {
|
||||
ConnectEventCh chan connectEvent
|
||||
DisconnectEventCh chan disconnectEvent
|
||||
TCPRequestEventCh chan tcpRequestEvent
|
||||
TCPErrorEventCh chan tcpErrorEvent
|
||||
UDPRequestEventCh chan udpRequestEvent
|
||||
UDPErrorEventCh chan udpErrorEvent
|
||||
}
|
||||
|
||||
func (l *channelEventLogger) Connect(addr net.Addr, id string, tx uint64) {
|
||||
if l.ConnectEventCh != nil {
|
||||
l.ConnectEventCh <- connectEvent{
|
||||
Addr: addr,
|
||||
ID: id,
|
||||
TX: tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *channelEventLogger) Disconnect(addr net.Addr, id string, err error) {
|
||||
if l.DisconnectEventCh != nil {
|
||||
l.DisconnectEventCh <- disconnectEvent{
|
||||
Addr: addr,
|
||||
ID: id,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *channelEventLogger) TCPRequest(addr net.Addr, id, reqAddr string) {
|
||||
if l.TCPRequestEventCh != nil {
|
||||
l.TCPRequestEventCh <- tcpRequestEvent{
|
||||
Addr: addr,
|
||||
ID: id,
|
||||
ReqAddr: reqAddr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *channelEventLogger) TCPError(addr net.Addr, id, reqAddr string, err error) {
|
||||
if l.TCPErrorEventCh != nil {
|
||||
l.TCPErrorEventCh <- tcpErrorEvent{
|
||||
Addr: addr,
|
||||
ID: id,
|
||||
ReqAddr: reqAddr,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *channelEventLogger) UDPRequest(addr net.Addr, id string, sessionID uint32) {
|
||||
if l.UDPRequestEventCh != nil {
|
||||
l.UDPRequestEventCh <- udpRequestEvent{
|
||||
Addr: addr,
|
||||
ID: id,
|
||||
SessionID: sessionID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *channelEventLogger) UDPError(addr net.Addr, id string, sessionID uint32, err error) {
|
||||
if l.UDPErrorEventCh != nil {
|
||||
l.UDPErrorEventCh <- udpErrorEvent{
|
||||
Addr: addr,
|
||||
ID: id,
|
||||
SessionID: sessionID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
7
core/internal/pmtud/avail.go
Normal file
7
core/internal/pmtud/avail.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build linux || windows
|
||||
|
||||
package pmtud
|
||||
|
||||
const (
|
||||
DisablePathMTUDiscovery = false
|
||||
)
|
12
core/internal/pmtud/unavail.go
Normal file
12
core/internal/pmtud/unavail.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
//go:build !linux && !windows
|
||||
|
||||
package pmtud
|
||||
|
||||
// quic-go's MTU discovery is enabled by default across all platforms. However, our testing has found that on certain
|
||||
// platforms (e.g. macOS) the DF bit is not set. As a result, probe packets that should never be fragmented are still
|
||||
// fragmented and transmitted. So we have decided to enable MTU discovery only on Linux and Windows for now, as we have
|
||||
// verified its functionality on these platforms.
|
||||
|
||||
const (
|
||||
DisablePathMTUDiscovery = true
|
||||
)
|
36
core/internal/protocol/http.go
Normal file
36
core/internal/protocol/http.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
URLHost = "hysteria"
|
||||
URLPath = "/auth"
|
||||
|
||||
HeaderAuth = "Hysteria-Auth"
|
||||
HeaderCCRX = "Hysteria-CC-RX"
|
||||
|
||||
StatusAuthOK = 233
|
||||
)
|
||||
|
||||
func AuthRequestDataFromHeader(h http.Header) (auth string, rx uint64) {
|
||||
auth = h.Get(HeaderAuth)
|
||||
rx, _ = strconv.ParseUint(h.Get(HeaderCCRX), 10, 64)
|
||||
return
|
||||
}
|
||||
|
||||
func AuthRequestDataToHeader(h http.Header, auth string, rx uint64) {
|
||||
h.Set(HeaderAuth, auth)
|
||||
h.Set(HeaderCCRX, strconv.FormatUint(rx, 10))
|
||||
}
|
||||
|
||||
func AuthResponseDataFromHeader(h http.Header) (rx uint64) {
|
||||
rx, _ = strconv.ParseUint(h.Get(HeaderCCRX), 10, 64)
|
||||
return
|
||||
}
|
||||
|
||||
func AuthResponseDataToHeader(h http.Header, rx uint64) {
|
||||
h.Set(HeaderCCRX, strconv.FormatUint(rx, 10))
|
||||
}
|
261
core/internal/protocol/proxy.go
Normal file
261
core/internal/protocol/proxy.go
Normal file
|
@ -0,0 +1,261 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/apernet/hysteria/core/errors"
|
||||
|
||||
"github.com/quic-go/quic-go/quicvarint"
|
||||
)
|
||||
|
||||
const (
|
||||
FrameTypeTCPRequest = 0x401
|
||||
FrameTypeUDPRequest = 0x402
|
||||
|
||||
MaxAddressLength = 2048 // for preventing DoS attack by sending a very large address length
|
||||
MaxMessageLength = 2048 // for preventing DoS attack by sending a very large message length
|
||||
|
||||
MaxUDPSize = 4096
|
||||
|
||||
maxVarInt1 = 63
|
||||
maxVarInt2 = 16383
|
||||
maxVarInt4 = 1073741823
|
||||
maxVarInt8 = 4611686018427387903
|
||||
)
|
||||
|
||||
// TCPRequest format:
|
||||
// 0x401 (QUIC varint)
|
||||
// Address length (QUIC varint)
|
||||
// Address (bytes)
|
||||
|
||||
func ReadTCPRequest(r io.Reader) (string, error) {
|
||||
bReader := quicvarint.NewReader(r)
|
||||
l, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if l == 0 || l > MaxAddressLength {
|
||||
return "", errors.ProtocolError{Message: "invalid address length"}
|
||||
}
|
||||
buf := make([]byte, l)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
return string(buf), err
|
||||
}
|
||||
|
||||
func WriteTCPRequest(w io.Writer, addr string) error {
|
||||
l := len(addr)
|
||||
sz := int(quicvarint.Len(FrameTypeTCPRequest)) + int(quicvarint.Len(uint64(l))) + l
|
||||
buf := make([]byte, sz)
|
||||
i := varintPut(buf, FrameTypeTCPRequest)
|
||||
i += varintPut(buf[i:], uint64(l))
|
||||
copy(buf[i:], addr)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// TCPResponse format:
|
||||
// Status (byte, 0=ok, 1=error)
|
||||
// Message length (QUIC varint)
|
||||
// Message (bytes)
|
||||
|
||||
func ReadTCPResponse(r io.Reader) (bool, string, error) {
|
||||
var status [1]byte
|
||||
if _, err := io.ReadFull(r, status[:]); err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
bReader := quicvarint.NewReader(r)
|
||||
l, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if l == 0 {
|
||||
// No message is ok
|
||||
return status[0] == 0, "", nil
|
||||
}
|
||||
if l > MaxMessageLength {
|
||||
return false, "", errors.ProtocolError{Message: "invalid message length"}
|
||||
}
|
||||
buf := make([]byte, l)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
return status[0] == 0, string(buf), err
|
||||
}
|
||||
|
||||
func WriteTCPResponse(w io.Writer, ok bool, msg string) error {
|
||||
l := len(msg)
|
||||
sz := 1 + int(quicvarint.Len(uint64(l))) + l
|
||||
buf := make([]byte, sz)
|
||||
if ok {
|
||||
buf[0] = 0
|
||||
} else {
|
||||
buf[0] = 1
|
||||
}
|
||||
i := varintPut(buf[1:], uint64(l))
|
||||
copy(buf[1+i:], msg)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// UDPRequest format:
|
||||
// 0x402 (QUIC varint)
|
||||
|
||||
// Nothing to read
|
||||
|
||||
func WriteUDPRequest(w io.Writer) error {
|
||||
buf := make([]byte, quicvarint.Len(FrameTypeUDPRequest))
|
||||
varintPut(buf, FrameTypeUDPRequest)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// UDPResponse format:
|
||||
// Status (byte, 0=ok, 1=error)
|
||||
// Session ID (uint32 BE)
|
||||
// Message length (QUIC varint)
|
||||
// Message (bytes)
|
||||
|
||||
func ReadUDPResponse(r io.Reader) (bool, uint32, string, error) {
|
||||
var status [1]byte
|
||||
if _, err := io.ReadFull(r, status[:]); err != nil {
|
||||
return false, 0, "", err
|
||||
}
|
||||
var sessionID uint32
|
||||
if err := binary.Read(r, binary.BigEndian, &sessionID); err != nil {
|
||||
return false, 0, "", err
|
||||
}
|
||||
bReader := quicvarint.NewReader(r)
|
||||
l, err := quicvarint.Read(bReader)
|
||||
if err != nil {
|
||||
return false, 0, "", err
|
||||
}
|
||||
if l == 0 {
|
||||
// No message is ok
|
||||
return status[0] == 0, sessionID, "", nil
|
||||
}
|
||||
if l > MaxMessageLength {
|
||||
return false, 0, "", errors.ProtocolError{Message: "invalid message length"}
|
||||
}
|
||||
buf := make([]byte, l)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
return status[0] == 0, sessionID, string(buf), err
|
||||
}
|
||||
|
||||
func WriteUDPResponse(w io.Writer, ok bool, sessionID uint32, msg string) error {
|
||||
l := len(msg)
|
||||
buf := make([]byte, 5+int(quicvarint.Len(uint64(l)))+l)
|
||||
if ok {
|
||||
buf[0] = 0
|
||||
} else {
|
||||
buf[0] = 1
|
||||
}
|
||||
binary.BigEndian.PutUint32(buf[1:], sessionID)
|
||||
i := varintPut(buf[5:], uint64(l))
|
||||
copy(buf[5+i:], msg)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// UDPMessage format:
|
||||
// Session ID (uint32 BE)
|
||||
// Packet ID (uint16 BE)
|
||||
// Fragment ID (uint8)
|
||||
// Fragment count (uint8)
|
||||
// Address length (QUIC varint)
|
||||
// Address (bytes)
|
||||
// Data...
|
||||
|
||||
type UDPMessage struct {
|
||||
SessionID uint32 // 4
|
||||
PacketID uint16 // 2
|
||||
FragID uint8 // 1
|
||||
FragCount uint8 // 1
|
||||
Addr string // varint + bytes
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (m *UDPMessage) HeaderSize() int {
|
||||
lAddr := len(m.Addr)
|
||||
return 4 + 2 + 1 + 1 + int(quicvarint.Len(uint64(lAddr))) + lAddr
|
||||
}
|
||||
|
||||
func (m *UDPMessage) Size() int {
|
||||
return m.HeaderSize() + len(m.Data)
|
||||
}
|
||||
|
||||
func (m *UDPMessage) Serialize(buf []byte) int {
|
||||
// Make sure the buffer is big enough
|
||||
if len(buf) < m.Size() {
|
||||
return -1
|
||||
}
|
||||
binary.BigEndian.PutUint32(buf, m.SessionID)
|
||||
binary.BigEndian.PutUint16(buf[4:], m.PacketID)
|
||||
buf[6] = m.FragID
|
||||
buf[7] = m.FragCount
|
||||
i := varintPut(buf[8:], uint64(len(m.Addr)))
|
||||
i += copy(buf[8+i:], m.Addr)
|
||||
i += copy(buf[8+i:], m.Data)
|
||||
return 8 + i
|
||||
}
|
||||
|
||||
func ParseUDPMessage(msg []byte) (*UDPMessage, error) {
|
||||
m := &UDPMessage{}
|
||||
buf := bytes.NewBuffer(msg)
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.SessionID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.PacketID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.FragID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(buf, binary.BigEndian, &m.FragCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lAddr, err := quicvarint.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lAddr == 0 || lAddr > MaxMessageLength {
|
||||
return nil, errors.ProtocolError{Message: "invalid address length"}
|
||||
}
|
||||
bs := buf.Bytes()
|
||||
m.Addr = string(bs[:lAddr])
|
||||
m.Data = bs[lAddr:]
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// varintPut is like quicvarint.Append, but instead of appending to a slice,
|
||||
// it writes to a fixed-size buffer. Returns the number of bytes written.
|
||||
func varintPut(b []byte, i uint64) int {
|
||||
if i <= maxVarInt1 {
|
||||
b[0] = uint8(i)
|
||||
return 1
|
||||
}
|
||||
if i <= maxVarInt2 {
|
||||
b[0] = uint8(i>>8) | 0x40
|
||||
b[1] = uint8(i)
|
||||
return 2
|
||||
}
|
||||
if i <= maxVarInt4 {
|
||||
b[0] = uint8(i>>24) | 0x80
|
||||
b[1] = uint8(i >> 16)
|
||||
b[2] = uint8(i >> 8)
|
||||
b[3] = uint8(i)
|
||||
return 4
|
||||
}
|
||||
if i <= maxVarInt8 {
|
||||
b[0] = uint8(i>>56) | 0xc0
|
||||
b[1] = uint8(i >> 48)
|
||||
b[2] = uint8(i >> 40)
|
||||
b[3] = uint8(i >> 32)
|
||||
b[4] = uint8(i >> 24)
|
||||
b[5] = uint8(i >> 16)
|
||||
b[6] = uint8(i >> 8)
|
||||
b[7] = uint8(i)
|
||||
return 8
|
||||
}
|
||||
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
|
||||
}
|
557
core/internal/protocol/proxy_test.go
Normal file
557
core/internal/protocol/proxy_test.go
Normal file
|
@ -0,0 +1,557 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadTCPRequest(t *testing.T) {
|
||||
type args struct {
|
||||
r io.Reader
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal 1",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x05hello")),
|
||||
},
|
||||
want: "hello",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "normal 2",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x41\x25We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People")),
|
||||
},
|
||||
want: "We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00")),
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "incomplete",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x06oh no")),
|
||||
},
|
||||
want: "oh no\x00",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too long",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x66\x77\x88Whatever")),
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ReadTCPRequest(tt.args.r)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReadTCPRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ReadTCPRequest() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteTCPRequest(t *testing.T) {
|
||||
type args struct {
|
||||
addr string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal 1",
|
||||
args: args{
|
||||
addr: "hello",
|
||||
},
|
||||
wantW: "\x44\x01\x05hello",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "normal 2",
|
||||
args: args{
|
||||
addr: "We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People",
|
||||
},
|
||||
wantW: "\x44\x01\x41\x25We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People We the People",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
addr: "",
|
||||
},
|
||||
wantW: "\x44\x01\x00",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteTCPRequest(w, tt.args.addr)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("WriteTCPRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("WriteTCPRequest() gotW = %v, want %v", []byte(gotW), tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUDPMessage(t *testing.T) {
|
||||
t.Run("buffer too small", func(t *testing.T) {
|
||||
// Make sure Serialize returns -1 when the buffer is too small.
|
||||
tBuf := make([]byte, 20)
|
||||
if (&UDPMessage{
|
||||
SessionID: 66,
|
||||
PacketID: 77,
|
||||
FragID: 2,
|
||||
FragCount: 5,
|
||||
Addr: "random_addr",
|
||||
Data: []byte("random_data"),
|
||||
}).Serialize(tBuf) != -1 {
|
||||
t.Error("Serialize() did not return -1 when the buffer was too small")
|
||||
}
|
||||
})
|
||||
|
||||
type fields struct {
|
||||
SessionID uint32
|
||||
PacketID uint16
|
||||
FragID uint8
|
||||
FragCount uint8
|
||||
Addr string
|
||||
Data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
name: "test 1",
|
||||
fields: fields{
|
||||
SessionID: 1,
|
||||
PacketID: 1,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
Addr: "example.com:80",
|
||||
Data: []byte("GET /nothing HTTP/1.1\r\n"),
|
||||
},
|
||||
want: []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, 0xe, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3a, 0x38, 0x30, 0x47, 0x45, 0x54, 0x20, 0x2f, 0x6e, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0xd, 0xa},
|
||||
},
|
||||
{
|
||||
name: "test 2",
|
||||
fields: fields{
|
||||
SessionID: 1329655244,
|
||||
Addr: "some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long_some_random_goofy_ahh_address_which_is_very_long:9000",
|
||||
PacketID: 62233,
|
||||
FragID: 8,
|
||||
FragCount: 19,
|
||||
Data: []byte("God is great, beer is good, and people are crazy."),
|
||||
},
|
||||
want: []byte{0x4f, 0x40, 0xed, 0xcc, 0xf3, 0x19, 0x8, 0x13, 0x41, 0xee, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x5f, 0x67, 0x6f, 0x6f, 0x66, 0x79, 0x5f, 0x61, 0x68, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x77, 0x68, 0x69, 0x63, 0x68, 0x5f, 0x69, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x3a, 0x39, 0x30, 0x30, 0x30, 0x47, 0x6f, 0x64, 0x20, 0x69, 0x73, 0x20, 0x67, 0x72, 0x65, 0x61, 0x74, 0x2c, 0x20, 0x62, 0x65, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x67, 0x6f, 0x6f, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, 0x63, 0x72, 0x61, 0x7a, 0x79, 0x2e},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &UDPMessage{
|
||||
SessionID: tt.fields.SessionID,
|
||||
Addr: tt.fields.Addr,
|
||||
PacketID: tt.fields.PacketID,
|
||||
FragID: tt.fields.FragID,
|
||||
FragCount: tt.fields.FragCount,
|
||||
Data: tt.fields.Data,
|
||||
}
|
||||
// Serialize
|
||||
buf := make([]byte, MaxUDPSize)
|
||||
n := m.Serialize(buf)
|
||||
if got := buf[:n]; !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Serialize() = %v, want %v", got, tt.want)
|
||||
}
|
||||
// Parse back
|
||||
if m2, err := ParseUDPMessage(tt.want); err != nil {
|
||||
t.Errorf("ParseUDPMessage() error = %v", err)
|
||||
} else {
|
||||
if !reflect.DeepEqual(m2, m) {
|
||||
t.Errorf("ParseUDPMessage() = %v, want %v", m2, m)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUDPMessageMalformed is to make sure ParseUDPMessage() fails (but not panic) on malformed data.
|
||||
func TestUDPMessageMalformed(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
data: []byte{},
|
||||
},
|
||||
{
|
||||
name: "zeroes 1",
|
||||
data: []byte{0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
name: "zeroes 2",
|
||||
data: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
name: "incomplete 1",
|
||||
data: []byte{0x66, 0xCC, 0xFF, 0xFF, 0x11, 0x22, 0x33, 0x44, 0x55},
|
||||
},
|
||||
{
|
||||
name: "incomplete 2",
|
||||
data: []byte{0x66, 0xCC, 0xFF, 0xFF, 0x11, 0x22, 0x33, 0x44, 0x90, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if _, err := ParseUDPMessage(tt.data); err == nil {
|
||||
t.Errorf("ParseUDPMessage() should fail")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadTCPResponse(t *testing.T) {
|
||||
type args struct {
|
||||
r io.Reader
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
want1 string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success 1",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00\x00")),
|
||||
},
|
||||
want: true,
|
||||
want1: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "success 2",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00\x12are ya winning son")),
|
||||
},
|
||||
want: true,
|
||||
want1: "are ya winning son",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "failure 1",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x01\x00")),
|
||||
},
|
||||
want: false,
|
||||
want1: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "failure 2",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x01\x15you ain't winning son")),
|
||||
},
|
||||
want: false,
|
||||
want1: "you ain't winning son",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "incomplete",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x01\x25princess peach is in another castle")),
|
||||
},
|
||||
want: false,
|
||||
want1: "princess peach is in another castle\x00\x00",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too long",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\xAA\xBB\xCCrandom stuff")),
|
||||
},
|
||||
want: false,
|
||||
want1: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1, err := ReadTCPResponse(tt.args.r)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReadTCPResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ReadTCPResponse() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("ReadTCPResponse() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteTCPResponse(t *testing.T) {
|
||||
type args struct {
|
||||
ok bool
|
||||
msg string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success 1",
|
||||
args: args{
|
||||
ok: true,
|
||||
msg: "",
|
||||
},
|
||||
wantW: "\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "success 2",
|
||||
args: args{
|
||||
ok: true,
|
||||
msg: "Welcome XDXDXD",
|
||||
},
|
||||
wantW: "\x00\x0EWelcome XDXDXD",
|
||||
},
|
||||
{
|
||||
name: "failure 1",
|
||||
args: args{
|
||||
ok: false,
|
||||
msg: "",
|
||||
},
|
||||
wantW: "\x01\x00",
|
||||
},
|
||||
{
|
||||
name: "failure 2",
|
||||
args: args{
|
||||
ok: false,
|
||||
msg: "me trying to find who u are: ...",
|
||||
},
|
||||
wantW: "\x01\x20me trying to find who u are: ...",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteTCPResponse(w, tt.args.ok, tt.args.msg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("WriteTCPResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("WriteTCPResponse() gotW = %v, want %v", gotW, tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteUDPRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
wantW: "\x44\x02",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteUDPRequest(w)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("WriteUDPRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("WriteUDPRequest() gotW = %v, want %v", gotW, tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUDPResponse(t *testing.T) {
|
||||
type args struct {
|
||||
r io.Reader
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
want1 uint32
|
||||
want2 string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success 1",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00\x00\x00\x00\x02\x00")),
|
||||
},
|
||||
want: true,
|
||||
want1: 2,
|
||||
want2: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "success 2",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00\x00\x00\x00\x03\x0EWelcome XDXDXD")),
|
||||
},
|
||||
want: true,
|
||||
want1: 3,
|
||||
want2: "Welcome XDXDXD",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "failure",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x01\x00\x00\x00\x01\x20me trying to find who u are: ...")),
|
||||
},
|
||||
want: false,
|
||||
want1: 1,
|
||||
want2: "me trying to find who u are: ...",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "incomplete",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00\x00\x00\x00\x02")),
|
||||
},
|
||||
want: false,
|
||||
want1: 0,
|
||||
want2: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too long",
|
||||
args: args{
|
||||
r: bytes.NewReader([]byte("\x00\x00\x00\x00\x02\xCC\xFF\x66no cap")),
|
||||
},
|
||||
want: false,
|
||||
want1: 0,
|
||||
want2: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1, got2, err := ReadUDPResponse(tt.args.r)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReadUDPResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ReadUDPResponse() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("ReadUDPResponse() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
if got2 != tt.want2 {
|
||||
t.Errorf("ReadUDPResponse() got2 = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteUDPResponse(t *testing.T) {
|
||||
type args struct {
|
||||
ok bool
|
||||
sessionID uint32
|
||||
msg string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success 1",
|
||||
args: args{
|
||||
ok: true,
|
||||
sessionID: 88,
|
||||
msg: "",
|
||||
},
|
||||
wantW: "\x00\x00\x00\x00\x58\x00",
|
||||
},
|
||||
{
|
||||
name: "success 2",
|
||||
args: args{
|
||||
ok: true,
|
||||
sessionID: 233,
|
||||
msg: "together forever",
|
||||
},
|
||||
wantW: "\x00\x00\x00\x00\xE9\x10together forever",
|
||||
},
|
||||
{
|
||||
name: "failure 1",
|
||||
args: args{
|
||||
ok: false,
|
||||
sessionID: 1,
|
||||
msg: "",
|
||||
},
|
||||
wantW: "\x01\x00\x00\x00\x01\x00",
|
||||
},
|
||||
{
|
||||
name: "failure 2",
|
||||
args: args{
|
||||
ok: false,
|
||||
sessionID: 696969,
|
||||
msg: "run away run away",
|
||||
},
|
||||
wantW: "\x01\x00\x0A\xA2\x89\x11run away run away",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteUDPResponse(w, tt.args.ok, tt.args.sessionID, tt.args.msg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("WriteUDPResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("WriteUDPResponse() gotW = %v, want %v", gotW, tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
62
core/internal/utils/qstream.go
Normal file
62
core/internal/utils/qstream.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// QStream is a wrapper of quic.Stream that handles Close() in a way that
|
||||
// makes more sense to us. By default, quic.Stream's Close() only closes
|
||||
// the write side of the stream, not the read side. And if there is unread
|
||||
// data, the stream is not really considered closed until either the data
|
||||
// is drained or CancelRead() is called.
|
||||
// References:
|
||||
// - https://github.com/libp2p/go-libp2p/blob/master/p2p/transport/quic/stream.go
|
||||
// - https://github.com/quic-go/quic-go/issues/3558
|
||||
// - https://github.com/quic-go/quic-go/issues/1599
|
||||
type QStream struct {
|
||||
Stream quic.Stream
|
||||
}
|
||||
|
||||
func (s *QStream) StreamID() quic.StreamID {
|
||||
return s.Stream.StreamID()
|
||||
}
|
||||
|
||||
func (s *QStream) Read(p []byte) (n int, err error) {
|
||||
return s.Stream.Read(p)
|
||||
}
|
||||
|
||||
func (s *QStream) CancelRead(code quic.StreamErrorCode) {
|
||||
s.Stream.CancelRead(code)
|
||||
}
|
||||
|
||||
func (s *QStream) SetReadDeadline(t time.Time) error {
|
||||
return s.Stream.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (s *QStream) Write(p []byte) (n int, err error) {
|
||||
return s.Stream.Write(p)
|
||||
}
|
||||
|
||||
func (s *QStream) Close() error {
|
||||
s.Stream.CancelRead(0)
|
||||
return s.Stream.Close()
|
||||
}
|
||||
|
||||
func (s *QStream) CancelWrite(code quic.StreamErrorCode) {
|
||||
s.Stream.CancelWrite(code)
|
||||
}
|
||||
|
||||
func (s *QStream) Context() context.Context {
|
||||
return s.Stream.Context()
|
||||
}
|
||||
|
||||
func (s *QStream) SetWriteDeadline(t time.Time) error {
|
||||
return s.Stream.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *QStream) SetDeadline(t time.Time) error {
|
||||
return s.Stream.SetDeadline(t)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Grabbed from https://github.com/xtaci/tcpraw with modifications
|
|
@ -1,95 +0,0 @@
|
|||
package faketcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/pktconns/obfs"
|
||||
)
|
||||
|
||||
const udpBufferSize = 4096
|
||||
|
||||
type ObfsFakeTCPPacketConn struct {
|
||||
orig *TCPConn
|
||||
obfs obfs.Obfuscator
|
||||
|
||||
readBuf []byte
|
||||
readMutex sync.Mutex
|
||||
writeBuf []byte
|
||||
writeMutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewObfsFakeTCPConn(orig *TCPConn, obfs obfs.Obfuscator) *ObfsFakeTCPPacketConn {
|
||||
return &ObfsFakeTCPPacketConn{
|
||||
orig: orig,
|
||||
obfs: obfs,
|
||||
readBuf: make([]byte, udpBufferSize),
|
||||
writeBuf: make([]byte, udpBufferSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
for {
|
||||
c.readMutex.Lock()
|
||||
n, addr, err := c.orig.ReadFrom(c.readBuf)
|
||||
if n <= 0 {
|
||||
c.readMutex.Unlock()
|
||||
return 0, addr, err
|
||||
}
|
||||
newN := c.obfs.Deobfuscate(c.readBuf[:n], p)
|
||||
c.readMutex.Unlock()
|
||||
if newN > 0 {
|
||||
// Valid packet
|
||||
return newN, addr, err
|
||||
} else if err != nil {
|
||||
// Not valid and orig.ReadFrom had some error
|
||||
return 0, addr, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.writeMutex.Lock()
|
||||
bn := c.obfs.Obfuscate(p, c.writeBuf)
|
||||
_, err = c.orig.WriteTo(c.writeBuf[:bn], addr)
|
||||
c.writeMutex.Unlock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return len(p), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) Close() error {
|
||||
return c.orig.Close()
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) LocalAddr() net.Addr {
|
||||
return c.orig.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) SetDeadline(t time.Time) error {
|
||||
return c.orig.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) SetReadDeadline(t time.Time) error {
|
||||
return c.orig.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.orig.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) SetReadBuffer(bytes int) error {
|
||||
return c.orig.SetReadBuffer(bytes)
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) SetWriteBuffer(bytes int) error {
|
||||
return c.orig.SetWriteBuffer(bytes)
|
||||
}
|
||||
|
||||
func (c *ObfsFakeTCPPacketConn) SyscallConn() (syscall.RawConn, error) {
|
||||
return c.orig.SyscallConn()
|
||||
}
|
|
@ -1,616 +0,0 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package faketcp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
var (
|
||||
errOpNotImplemented = errors.New("operation not implemented")
|
||||
errTimeout = errors.New("timeout")
|
||||
expire = time.Minute
|
||||
)
|
||||
|
||||
// a message from NIC
|
||||
type message struct {
|
||||
bts []byte
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
// a tcp flow information of a connection pair
|
||||
type tcpFlow struct {
|
||||
conn *net.TCPConn // the related system TCP connection of this flow
|
||||
handle *net.IPConn // the handle to send packets
|
||||
seq uint32 // TCP sequence number
|
||||
ack uint32 // TCP acknowledge number
|
||||
networkLayer gopacket.SerializableLayer // network layer header for tx
|
||||
ts time.Time // last packet incoming time
|
||||
buf gopacket.SerializeBuffer // a buffer for write
|
||||
tcpHeader layers.TCP
|
||||
}
|
||||
|
||||
// TCPConn defines a TCP-packet oriented connection
|
||||
type TCPConn struct {
|
||||
die chan struct{}
|
||||
dieOnce sync.Once
|
||||
|
||||
// the main golang sockets
|
||||
tcpconn *net.TCPConn // from net.Dial
|
||||
listener *net.TCPListener // from net.Listen
|
||||
|
||||
// handles
|
||||
handles []*net.IPConn
|
||||
|
||||
// packets captured from all related NICs will be delivered to this channel
|
||||
chMessage chan message
|
||||
|
||||
// all TCP flows
|
||||
flowTable map[string]*tcpFlow
|
||||
flowsLock sync.Mutex
|
||||
|
||||
// iptables
|
||||
iptables *iptables.IPTables
|
||||
iprule []string
|
||||
|
||||
ip6tables *iptables.IPTables
|
||||
ip6rule []string
|
||||
|
||||
// deadlines
|
||||
readDeadline atomic.Value
|
||||
writeDeadline atomic.Value
|
||||
|
||||
// serialization
|
||||
opts gopacket.SerializeOptions
|
||||
}
|
||||
|
||||
// lockflow locks the flow table and apply function `f` to the entry, and create one if not exist
|
||||
func (conn *TCPConn) lockflow(addr net.Addr, f func(e *tcpFlow)) {
|
||||
key := addr.String()
|
||||
conn.flowsLock.Lock()
|
||||
e := conn.flowTable[key]
|
||||
if e == nil { // entry first visit
|
||||
e = new(tcpFlow)
|
||||
e.ts = time.Now()
|
||||
e.buf = gopacket.NewSerializeBuffer()
|
||||
}
|
||||
f(e)
|
||||
conn.flowTable[key] = e
|
||||
conn.flowsLock.Unlock()
|
||||
}
|
||||
|
||||
// clean expired flows
|
||||
func (conn *TCPConn) cleaner() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
select {
|
||||
case <-conn.die:
|
||||
return
|
||||
case <-ticker.C:
|
||||
conn.flowsLock.Lock()
|
||||
for k, v := range conn.flowTable {
|
||||
if time.Now().Sub(v.ts) > expire {
|
||||
if v.conn != nil {
|
||||
setTTL(v.conn, 64)
|
||||
v.conn.Close()
|
||||
}
|
||||
delete(conn.flowTable, k)
|
||||
}
|
||||
}
|
||||
conn.flowsLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// captureFlow capture every inbound packets based on rules of BPF
|
||||
func (conn *TCPConn) captureFlow(handle *net.IPConn, port int) {
|
||||
buf := make([]byte, 2048)
|
||||
opt := gopacket.DecodeOptions{NoCopy: true, Lazy: true}
|
||||
for {
|
||||
n, addr, err := handle.ReadFromIP(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// try decoding TCP frame from buf[:n]
|
||||
packet := gopacket.NewPacket(buf[:n], layers.LayerTypeTCP, opt)
|
||||
transport := packet.TransportLayer()
|
||||
tcp, ok := transport.(*layers.TCP)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// port filtering
|
||||
if int(tcp.DstPort) != port {
|
||||
continue
|
||||
}
|
||||
|
||||
// address building
|
||||
var src net.TCPAddr
|
||||
src.IP = addr.IP
|
||||
src.Port = int(tcp.SrcPort)
|
||||
|
||||
var orphan bool
|
||||
// flow maintaince
|
||||
conn.lockflow(&src, func(e *tcpFlow) {
|
||||
if e.conn == nil { // make sure it's related to net.TCPConn
|
||||
orphan = true // mark as orphan if it's not related net.TCPConn
|
||||
}
|
||||
|
||||
// to keep track of TCP header related to this source
|
||||
e.ts = time.Now()
|
||||
if tcp.ACK {
|
||||
e.seq = tcp.Ack
|
||||
}
|
||||
if tcp.SYN {
|
||||
e.ack = tcp.Seq + 1
|
||||
}
|
||||
if tcp.PSH {
|
||||
if e.ack == tcp.Seq {
|
||||
e.ack = tcp.Seq + uint32(len(tcp.Payload))
|
||||
}
|
||||
}
|
||||
e.handle = handle
|
||||
})
|
||||
|
||||
// push data if it's not orphan
|
||||
if !orphan && tcp.PSH {
|
||||
payload := make([]byte, len(tcp.Payload))
|
||||
copy(payload, tcp.Payload)
|
||||
select {
|
||||
case conn.chMessage <- message{payload, &src}:
|
||||
case <-conn.die:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom implements the PacketConn ReadFrom method.
|
||||
func (conn *TCPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
var timer *time.Timer
|
||||
var deadline <-chan time.Time
|
||||
if d, ok := conn.readDeadline.Load().(time.Time); ok && !d.IsZero() {
|
||||
timer = time.NewTimer(time.Until(d))
|
||||
defer timer.Stop()
|
||||
deadline = timer.C
|
||||
}
|
||||
|
||||
select {
|
||||
case <-deadline:
|
||||
return 0, nil, errTimeout
|
||||
case <-conn.die:
|
||||
return 0, nil, io.EOF
|
||||
case packet := <-conn.chMessage:
|
||||
n = copy(p, packet.bts)
|
||||
return n, packet.addr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo implements the PacketConn WriteTo method.
|
||||
func (conn *TCPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
var deadline <-chan time.Time
|
||||
if d, ok := conn.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
|
||||
timer := time.NewTimer(time.Until(d))
|
||||
defer timer.Stop()
|
||||
deadline = timer.C
|
||||
}
|
||||
|
||||
select {
|
||||
case <-deadline:
|
||||
return 0, errTimeout
|
||||
case <-conn.die:
|
||||
return 0, io.EOF
|
||||
default:
|
||||
raddr, err := net.ResolveTCPAddr("tcp", addr.String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var lport int
|
||||
if conn.tcpconn != nil {
|
||||
lport = conn.tcpconn.LocalAddr().(*net.TCPAddr).Port
|
||||
} else {
|
||||
lport = conn.listener.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
conn.lockflow(addr, func(e *tcpFlow) {
|
||||
// if the flow doesn't have handle , assume this packet has lost, without notification
|
||||
if e.handle == nil {
|
||||
n = len(p)
|
||||
return
|
||||
}
|
||||
|
||||
// build tcp header with local and remote port
|
||||
e.tcpHeader.SrcPort = layers.TCPPort(lport)
|
||||
e.tcpHeader.DstPort = layers.TCPPort(raddr.Port)
|
||||
binary.Read(rand.Reader, binary.LittleEndian, &e.tcpHeader.Window)
|
||||
e.tcpHeader.Window |= 0x8000 // make sure it's larger than 32768
|
||||
e.tcpHeader.Ack = e.ack
|
||||
e.tcpHeader.Seq = e.seq
|
||||
e.tcpHeader.PSH = true
|
||||
e.tcpHeader.ACK = true
|
||||
|
||||
// build IP header with src & dst ip for TCP checksum
|
||||
if raddr.IP.To4() != nil {
|
||||
ip := &layers.IPv4{
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
SrcIP: e.handle.LocalAddr().(*net.IPAddr).IP.To4(),
|
||||
DstIP: raddr.IP.To4(),
|
||||
}
|
||||
e.tcpHeader.SetNetworkLayerForChecksum(ip)
|
||||
} else {
|
||||
ip := &layers.IPv6{
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
SrcIP: e.handle.LocalAddr().(*net.IPAddr).IP.To16(),
|
||||
DstIP: raddr.IP.To16(),
|
||||
}
|
||||
e.tcpHeader.SetNetworkLayerForChecksum(ip)
|
||||
}
|
||||
|
||||
e.buf.Clear()
|
||||
gopacket.SerializeLayers(e.buf, conn.opts, &e.tcpHeader, gopacket.Payload(p))
|
||||
if conn.tcpconn != nil {
|
||||
_, err = e.handle.Write(e.buf.Bytes())
|
||||
} else {
|
||||
_, err = e.handle.WriteToIP(e.buf.Bytes(), &net.IPAddr{IP: raddr.IP})
|
||||
}
|
||||
// increase seq in flow
|
||||
e.seq += uint32(len(p))
|
||||
n = len(p)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (conn *TCPConn) Close() error {
|
||||
var err error
|
||||
conn.dieOnce.Do(func() {
|
||||
// signal closing
|
||||
close(conn.die)
|
||||
|
||||
// close all established tcp connections
|
||||
if conn.tcpconn != nil { // client
|
||||
setTTL(conn.tcpconn, 64)
|
||||
err = conn.tcpconn.Close()
|
||||
} else if conn.listener != nil {
|
||||
err = conn.listener.Close() // server
|
||||
conn.flowsLock.Lock()
|
||||
for k, v := range conn.flowTable {
|
||||
if v.conn != nil {
|
||||
setTTL(v.conn, 64)
|
||||
v.conn.Close()
|
||||
}
|
||||
delete(conn.flowTable, k)
|
||||
}
|
||||
conn.flowsLock.Unlock()
|
||||
}
|
||||
|
||||
// close handles
|
||||
for k := range conn.handles {
|
||||
conn.handles[k].Close()
|
||||
}
|
||||
|
||||
// delete iptable
|
||||
if conn.iptables != nil {
|
||||
conn.iptables.Delete("filter", "OUTPUT", conn.iprule...)
|
||||
}
|
||||
if conn.ip6tables != nil {
|
||||
conn.ip6tables.Delete("filter", "OUTPUT", conn.ip6rule...)
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (conn *TCPConn) LocalAddr() net.Addr {
|
||||
if conn.tcpconn != nil {
|
||||
return conn.tcpconn.LocalAddr()
|
||||
} else if conn.listener != nil {
|
||||
return conn.listener.Addr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeadline implements the Conn SetDeadline method.
|
||||
func (conn *TCPConn) SetDeadline(t time.Time) error {
|
||||
if err := conn.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.SetWriteDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline implements the Conn SetReadDeadline method.
|
||||
func (conn *TCPConn) SetReadDeadline(t time.Time) error {
|
||||
conn.readDeadline.Store(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
||||
func (conn *TCPConn) SetWriteDeadline(t time.Time) error {
|
||||
conn.writeDeadline.Store(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header.
|
||||
func (conn *TCPConn) SetDSCP(dscp int) error {
|
||||
for k := range conn.handles {
|
||||
if err := setDSCP(conn.handles[k], dscp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadBuffer sets the size of the operating system's receive buffer associated with the connection.
|
||||
func (conn *TCPConn) SetReadBuffer(bytes int) error {
|
||||
var err error
|
||||
for k := range conn.handles {
|
||||
if err := conn.handles[k].SetReadBuffer(bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetWriteBuffer sets the size of the operating system's transmit buffer associated with the connection.
|
||||
func (conn *TCPConn) SetWriteBuffer(bytes int) error {
|
||||
var err error
|
||||
for k := range conn.handles {
|
||||
if err := conn.handles[k].SetWriteBuffer(bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (conn *TCPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
if len(conn.handles) == 0 {
|
||||
return nil, errors.New("no handles")
|
||||
// How is it possible?
|
||||
}
|
||||
return conn.handles[0].SyscallConn()
|
||||
}
|
||||
|
||||
// Dial connects to the remote TCP port,
|
||||
// and returns a single packet-oriented connection
|
||||
func Dial(network, address string) (*TCPConn, error) {
|
||||
// remote address resolve
|
||||
raddr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// AF_INET
|
||||
handle, err := net.DialIP("ip:tcp", nil, &net.IPAddr{IP: raddr.IP})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create an established tcp connection
|
||||
// will hack this tcp connection for packet transmission
|
||||
tcpconn, err := net.DialTCP(network, nil, raddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// fields
|
||||
conn := new(TCPConn)
|
||||
conn.die = make(chan struct{})
|
||||
conn.flowTable = make(map[string]*tcpFlow)
|
||||
conn.tcpconn = tcpconn
|
||||
conn.chMessage = make(chan message)
|
||||
conn.lockflow(tcpconn.RemoteAddr(), func(e *tcpFlow) { e.conn = tcpconn })
|
||||
conn.handles = append(conn.handles, handle)
|
||||
conn.opts = gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
go conn.captureFlow(handle, tcpconn.LocalAddr().(*net.TCPAddr).Port)
|
||||
go conn.cleaner()
|
||||
|
||||
// iptables
|
||||
err = setTTL(tcpconn, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4); err == nil {
|
||||
rule := []string{"-m", "ttl", "--ttl-eq", "1", "-p", "tcp", "-d", raddr.IP.String(), "--dport", fmt.Sprint(raddr.Port), "-j", "DROP"}
|
||||
if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil {
|
||||
if !exists {
|
||||
if err = ipt.Append("filter", "OUTPUT", rule...); err == nil {
|
||||
conn.iprule = rule
|
||||
conn.iptables = ipt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv6); err == nil {
|
||||
rule := []string{"-m", "hl", "--hl-eq", "1", "-p", "tcp", "-d", raddr.IP.String(), "--dport", fmt.Sprint(raddr.Port), "-j", "DROP"}
|
||||
if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil {
|
||||
if !exists {
|
||||
if err = ipt.Append("filter", "OUTPUT", rule...); err == nil {
|
||||
conn.ip6rule = rule
|
||||
conn.ip6tables = ipt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// discard everything
|
||||
go io.Copy(ioutil.Discard, tcpconn)
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Listen acts like net.ListenTCP,
|
||||
// and returns a single packet-oriented connection
|
||||
func Listen(network, address string) (*TCPConn, error) {
|
||||
// fields
|
||||
conn := new(TCPConn)
|
||||
conn.flowTable = make(map[string]*tcpFlow)
|
||||
conn.die = make(chan struct{})
|
||||
conn.chMessage = make(chan message)
|
||||
conn.opts = gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
|
||||
// resolve address
|
||||
laddr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// AF_INET
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if laddr.IP == nil || laddr.IP.IsUnspecified() { // if address is not specified, capture on all ifaces
|
||||
var lasterr error
|
||||
for _, iface := range ifaces {
|
||||
if addrs, err := iface.Addrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
if ipaddr, ok := addr.(*net.IPNet); ok {
|
||||
if handle, err := net.ListenIP("ip:tcp", &net.IPAddr{IP: ipaddr.IP}); err == nil {
|
||||
conn.handles = append(conn.handles, handle)
|
||||
go conn.captureFlow(handle, laddr.Port)
|
||||
} else {
|
||||
lasterr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(conn.handles) == 0 {
|
||||
return nil, lasterr
|
||||
}
|
||||
} else {
|
||||
if handle, err := net.ListenIP("ip:tcp", &net.IPAddr{IP: laddr.IP}); err == nil {
|
||||
conn.handles = append(conn.handles, handle)
|
||||
go conn.captureFlow(handle, laddr.Port)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// start listening
|
||||
l, err := net.ListenTCP(network, laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.listener = l
|
||||
|
||||
// start cleaner
|
||||
go conn.cleaner()
|
||||
|
||||
// iptables drop packets marked with TTL = 1
|
||||
// TODO: what if iptables is not available, the next hop will send back ICMP Time Exceeded,
|
||||
// is this still an acceptable behavior?
|
||||
if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4); err == nil {
|
||||
rule := []string{"-m", "ttl", "--ttl-eq", "1", "-p", "tcp", "--sport", fmt.Sprint(laddr.Port), "-j", "DROP"}
|
||||
if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil {
|
||||
if !exists {
|
||||
if err = ipt.Append("filter", "OUTPUT", rule...); err == nil {
|
||||
conn.iprule = rule
|
||||
conn.iptables = ipt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv6); err == nil {
|
||||
rule := []string{"-m", "hl", "--hl-eq", "1", "-p", "tcp", "--sport", fmt.Sprint(laddr.Port), "-j", "DROP"}
|
||||
if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil {
|
||||
if !exists {
|
||||
if err = ipt.Append("filter", "OUTPUT", rule...); err == nil {
|
||||
conn.ip6rule = rule
|
||||
conn.ip6tables = ipt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// discard everything in original connection
|
||||
go func() {
|
||||
for {
|
||||
tcpconn, err := l.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if we cannot set TTL = 1, the only thing reasonable is panic
|
||||
if err := setTTL(tcpconn, 1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// record net.Conn
|
||||
conn.lockflow(tcpconn.RemoteAddr(), func(e *tcpFlow) { e.conn = tcpconn })
|
||||
|
||||
// discard everything
|
||||
go io.Copy(ioutil.Discard, tcpconn)
|
||||
}
|
||||
}()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// setTTL sets the Time-To-Live field on a given connection
|
||||
func setTTL(c *net.TCPConn, ttl int) error {
|
||||
raw, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := c.LocalAddr().(*net.TCPAddr)
|
||||
|
||||
if addr.IP.To4() == nil {
|
||||
raw.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_UNICAST_HOPS, ttl)
|
||||
})
|
||||
} else {
|
||||
raw.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TTL, ttl)
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// setDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header.
|
||||
func setDSCP(c *net.IPConn, dscp int) error {
|
||||
raw, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := c.LocalAddr().(*net.IPAddr)
|
||||
|
||||
if addr.IP.To4() == nil {
|
||||
raw.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_TCLASS, dscp)
|
||||
})
|
||||
} else {
|
||||
raw.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, dscp<<2)
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package faketcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type TCPConn struct{ *net.UDPConn }
|
||||
|
||||
// Dial connects to the remote TCP port,
|
||||
// and returns a single packet-oriented connection
|
||||
func Dial(network, address string) (*TCPConn, error) {
|
||||
return nil, errors.New("faketcp is not supported on this platform")
|
||||
}
|
||||
|
||||
func Listen(network, address string) (*TCPConn, error) {
|
||||
return nil, errors.New("faketcp is not supported on this platform")
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue