mirror of https://github.com/rapiz1/rathole.git
Compare commits
57 Commits
Author | SHA1 | Date |
---|---|---|
sunmy2019 | 0ee0157b4b | |
Yujia Qiao | 3ab540f19d | |
sunmy2019 | 4ac53a5a39 | |
dependabot[bot] | 7251759bda | |
dependabot[bot] | ee7561c38d | |
Vincent Young | e4766e7d90 | |
Ryan Dearing | 63221028c9 | |
sunmy2019 | 915bf4d21d | |
Thomas Fournier | 62114cde4c | |
zhfish | 65b27f076c | |
blueskea | e08b2a9e92 | |
Yujia Qiao | 84c04ab9df | |
Yujia Qiao | ebb764ae53 | |
Yujia Qiao | 2ccb386cea | |
Yujia Qiao | 97541afaed | |
Yujia Qiao | 3aa6557696 | |
I Putu Ariyasa | 5946a18370 | |
Chieh Tai | d2fe586f7b | |
boenshao | 7923dec9f5 | |
Yujia Qiao | 9727e15377 | |
dependabot[bot] | 553ff381eb | |
reemaadeniyi | d2960b5bde | |
dependabot[bot] | a1815900f9 | |
dependabot[bot] | 540ebfc33b | |
Yujia Qiao | 2acd62454d | |
Yujia Qiao | 9479b9a3a9 | |
dependabot[bot] | 80a7266212 | |
Yujia Qiao | 32a2c36fde | |
Yujia Qiao | 69d8185ab5 | |
Yujia Qiao | d079d66223 | |
Yujia Qiao | ee5c7b4a77 | |
Yujia Qiao | 1b5c892e24 | |
Yujia Qiao | 96479e498d | |
Yujia Qiao | d216d6380f | |
Orhun Parmaksız | 87d06c91b9 | |
Yujia Qiao | 353d195529 | |
Yujia Qiao | e4c6c8abce | |
Yujia Qiao | bf842b43d3 | |
Vincent Young | 8fb9304549 | |
Yujia Qiao | 5396d9e64d | |
Yujia Qiao | 76f5c7227f | |
Yujia Qiao | ea01c42da7 | |
Peter Neumark | 187f4f0335 | |
Yujia Qiao | 064bdcab8e | |
inclyc | 67182fbc10 | |
fernvenue | 881701d68f | |
Yujia Qiao | 2e9e7374bc | |
Yujia Qiao | 1f2fc5b28f | |
Yujia Qiao | ee39a8e31e | |
Yujia Qiao | 8c9527406e | |
Yujia Qiao | 15a4183ba7 | |
Yujia Qiao | f8c415c558 | |
Victor C | 8665e6a2cf | |
Yujia Qiao | 8a24723895 | |
Yujia Qiao | feb8c2dbfa | |
Takayuki Maeda | 636bdbd604 | |
Takayuki Maeda | f9ee8ec2f9 |
|
@ -28,3 +28,4 @@ If you encountered a panic, please re-run with `RUST_BACKTRACE=1` to provide the
|
|||
- OS: <!-- Please fill in distribution if you're using linux-->
|
||||
- `rathole --version` output:
|
||||
- CPU architecture:
|
||||
- rustc version:
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Ask for a new feature
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Feature Proposed**
|
||||
<!-- describe the feature -->
|
||||
|
||||
**Use Case**
|
||||
<!-- possible use case -->
|
|
@ -20,59 +20,104 @@ jobs:
|
|||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
exe: rathole
|
||||
cross: false
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
exe: rathole
|
||||
cross: false
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: arm-unknown-linux-musleabi
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: arm-unknown-linux-musleabihf
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: mips-unknown-linux-gnu
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: mips-unknown-linux-musl
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: mipsel-unknown-linux-gnu
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: mipsel-unknown-linux-musl
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: mips64-unknown-linux-gnuabi64
|
||||
exe: rathole
|
||||
cross: true
|
||||
- os: ubuntu-latest
|
||||
target: mips64el-unknown-linux-gnuabi64
|
||||
exe: rathole
|
||||
cross: true
|
||||
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
exe: rathole
|
||||
cross: false
|
||||
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
exe: rathole
|
||||
cross: false
|
||||
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
exe: rathole.exe
|
||||
cross: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
- name: Install cross
|
||||
run: cargo install --version 0.1.16 cross
|
||||
# Since rust 1.72, some platforms are tier 3
|
||||
toolchain: 1.71
|
||||
default: true
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install pkg-config libssl-dev
|
||||
- name: Install OpenSSL
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install openssl@3
|
||||
|
||||
# Native build
|
||||
- name: Install target
|
||||
if: matrix.cross == false
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
- name: Run tests
|
||||
run: cross test --release --target ${{ matrix.target }} --verbose
|
||||
if: matrix.cross == false && matrix.target != 'aarch64-apple-darwin'
|
||||
run: cargo test --release --target ${{ matrix.target }} --verbose
|
||||
- name: Build release
|
||||
run: cross build --release --target ${{ matrix.target }}
|
||||
if: matrix.cross == false
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
|
||||
# Cross build
|
||||
- name: Install cross
|
||||
if: matrix.cross
|
||||
run: cargo install --version 0.2.5 cross
|
||||
- name: Run tests
|
||||
if: matrix.cross
|
||||
run: cross test --release --target ${{ matrix.target }} --verbose --features embedded --no-default-features
|
||||
- name: Build release
|
||||
if: matrix.cross
|
||||
run: cross build --release --target ${{ matrix.target }} --features embedded --no-default-features
|
||||
|
||||
- name: Run UPX
|
||||
# Upx may not support some platforms. Ignore the errors
|
||||
continue-on-error: true
|
||||
|
@ -80,7 +125,7 @@ jobs:
|
|||
if: matrix.os == 'ubuntu-latest' && !contains(matrix.target, 'mips')
|
||||
uses: crazy-max/ghaction-upx@v1
|
||||
with:
|
||||
version: v3.96
|
||||
version: v4.0.2
|
||||
files: target/${{ matrix.target }}/release/${{ matrix.exe }}
|
||||
args: -q --best --lzma
|
||||
- uses: actions/upload-artifact@v2
|
||||
|
|
|
@ -32,7 +32,9 @@ jobs:
|
|||
- name: Setup cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
- name: Check all features
|
||||
run: cargo hack check --feature-powerset --no-dev-deps
|
||||
run: >
|
||||
cargo hack check --feature-powerset --no-dev-deps
|
||||
--mutually-exclusive-features default,native-tls,websocket-native-tls,rustls,websocket-rustls
|
||||
|
||||
build:
|
||||
name: Build for ${{ matrix.target }}
|
||||
|
@ -49,6 +51,9 @@ jobs:
|
|||
- os: macos-latest
|
||||
exe: rathole
|
||||
target: x86_64-apple-darwin
|
||||
- os: macos-latest
|
||||
exe: rathole
|
||||
target: aarch64-apple-darwin
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
@ -59,8 +64,10 @@ jobs:
|
|||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Build
|
||||
run: cargo build
|
||||
- name: Run tests
|
||||
- name: Run tests with native-tls
|
||||
run: cargo test --verbose
|
||||
- name: Run tests with rustls
|
||||
run: cargo test --verbose --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: rathole-${{ matrix.target }}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
76
Cargo.toml
76
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rathole"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
authors = ["Yujia Qiao <code@rapiz.me>"]
|
||||
description = "A reverse proxy for NAT traversal"
|
||||
|
@ -11,19 +11,55 @@ build = "build.rs"
|
|||
include = ["src/**/*", "LICENSE", "README.md", "build.rs"]
|
||||
|
||||
[features]
|
||||
default = ["server", "client", "tls", "noise", "hot-reload"]
|
||||
default = [
|
||||
"server",
|
||||
"client",
|
||||
"native-tls",
|
||||
"noise",
|
||||
"websocket-native-tls",
|
||||
"hot-reload",
|
||||
]
|
||||
|
||||
# Run as a server
|
||||
server = []
|
||||
# Run as a client
|
||||
client = []
|
||||
|
||||
# TLS support
|
||||
tls = ["tokio-native-tls"]
|
||||
native-tls = ["tokio-native-tls"]
|
||||
rustls = [
|
||||
"tokio-rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-native-certs",
|
||||
"p12",
|
||||
]
|
||||
|
||||
# Noise support
|
||||
noise = ["snowstorm", "base64"]
|
||||
|
||||
# Websocket support
|
||||
websocket-native-tls = [
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"native-tls",
|
||||
]
|
||||
websocket-rustls = [
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"rustls",
|
||||
]
|
||||
|
||||
# Configuration hot-reload support
|
||||
hot-reload = ["notify"]
|
||||
|
||||
# Default feature releasing embedded devices
|
||||
# Cross-compiling with tls is hard. So we don't :(
|
||||
embedded = ["server", "client", "hot-reload", "noise"]
|
||||
|
||||
# Feature to enable tokio-console. Disabled by default.
|
||||
# Don't enable it unless for debugging purposes.
|
||||
console = ["console-subscriber", "tokio/tracing"]
|
||||
|
@ -61,20 +97,42 @@ hex = "0.4"
|
|||
rand = "0.8"
|
||||
backoff = { version = "0.4", features = ["tokio"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
socket2 = { version = "0.4", features = ["all"] }
|
||||
fdlimit = "0.2"
|
||||
tokio-native-tls = { version = "0.3", optional = true }
|
||||
async-trait = "0.1"
|
||||
snowstorm = { version = "0.3", optional = true, features = ["stream"], default-features = false }
|
||||
snowstorm = { version = "0.4", optional = true, features = [
|
||||
"stream",
|
||||
], default-features = false }
|
||||
base64 = { version = "0.13", optional = true }
|
||||
notify = { version = "5.0.0-pre.13", optional = true }
|
||||
console-subscriber = { version = "0.1", optional = true, features = ["parking_lot"] }
|
||||
console-subscriber = { version = "0.1", optional = true, features = [
|
||||
"parking_lot",
|
||||
] }
|
||||
atty = "0.2"
|
||||
async-http-proxy = { version = "1.2", features = ["runtime-tokio", "basic-auth"] }
|
||||
async-http-proxy = { version = "1.2", features = [
|
||||
"runtime-tokio",
|
||||
"basic-auth",
|
||||
] }
|
||||
async-socks5 = "0.5"
|
||||
url = { version = "2.2", features = ["serde"] }
|
||||
tokio-tungstenite = { version = "0.20.1", optional = true }
|
||||
tokio-util = { version = "0.7.9", optional = true, features = ["io"] }
|
||||
futures-core = { version = "0.3.28", optional = true }
|
||||
futures-sink = { version = "0.3.28", optional = true }
|
||||
tokio-native-tls = { version = "0.3", optional = true }
|
||||
tokio-rustls = { version = "0.25", optional = true }
|
||||
rustls-native-certs = { version = "0.7", optional = true }
|
||||
rustls-pemfile = { version = "2.0", optional = true }
|
||||
p12 = { version = "0.6.3", optional = true }
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "6.0", default-features = false, features = ["build", "git", "cargo"] }
|
||||
vergen = { version = "7.4.2", default-features = false, features = [
|
||||
"build",
|
||||
"git",
|
||||
"cargo",
|
||||
] }
|
||||
anyhow = "1.0"
|
||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -1,11 +1,15 @@
|
|||
FROM ekidd/rust-musl-builder:latest as builder
|
||||
FROM rust:bookworm as builder
|
||||
RUN apt update && apt install -y libssl-dev
|
||||
WORKDIR /home/rust/src
|
||||
COPY . .
|
||||
RUN cargo build --locked --release
|
||||
ARG FEATURES
|
||||
RUN cargo build --locked --release --features ${FEATURES:-default}
|
||||
RUN mkdir -p build-out/
|
||||
RUN cp target/x86_64-unknown-linux-musl/release/rathole build-out/
|
||||
RUN cp target/release/rathole build-out/
|
||||
|
||||
FROM scratch
|
||||
|
||||
|
||||
FROM gcr.io/distroless/cc-debian12
|
||||
WORKDIR /app
|
||||
COPY --from=builder /home/rust/src/build-out/rathole .
|
||||
USER 1000:1000
|
||||
|
|
44
README-zh.md
44
README-zh.md
|
@ -4,7 +4,7 @@
|
|||
|
||||
[![GitHub stars](https://img.shields.io/github/stars/rapiz1/rathole)](https://github.com/rapiz1/rathole/stargazers)
|
||||
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rapiz1/rathole)](https://github.com/rapiz1/rathole/releases)
|
||||
![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/rapiz1/rathole/Rust/main)
|
||||
![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rapiz1/rathole/rust.yml?branch=main)
|
||||
[![GitHub all releases](https://img.shields.io/github/downloads/rapiz1/rathole/total)](https://github.com/rapiz1/rathole/releases)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/rapiz1/rathole)](https://hub.docker.com/r/rapiz1/rathole)
|
||||
|
||||
|
@ -16,19 +16,21 @@ rathole,类似于 [frp](https://github.com/fatedier/frp) 和 [ngrok](https://g
|
|||
|
||||
<!-- TOC -->
|
||||
|
||||
- [Features](#features)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Configuration](#configuration)
|
||||
- [Logging](#logging)
|
||||
- [Benchmark](#benchmark)
|
||||
- [Development Status](#development-status)
|
||||
- [rathole](#rathole)
|
||||
- [Features](#features)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Configuration](#configuration)
|
||||
- [Logging](#logging)
|
||||
- [Tuning](#tuning)
|
||||
- [Benchmark](#benchmark)
|
||||
- [Development Status](#development-status)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Features
|
||||
|
||||
- **高性能** 具有更高的吞吐量,高并发下更稳定。见[Benchmark](#Benchmark)
|
||||
- **低资源消耗** 内存占用远低于同类工具。见[Benchmark](#Benchmark)。[二进制文件最小](docs/build-guide.md)可以到 **~500KiB**,可以部署在嵌入式设备如路由器上。
|
||||
- **高性能** 具有更高的吞吐量,高并发下更稳定。见[Benchmark](#benchmark)
|
||||
- **低资源消耗** 内存占用远低于同类工具。见[Benchmark](#benchmark)。[二进制文件最小](docs/build-guide.md)可以到 **~500KiB**,可以部署在嵌入式设备如路由器上。
|
||||
- **安全性** 每个服务单独强制鉴权。Server 和 Client 负责各自的配置。使用 Noise Protocol 可以简单地配置传输加密,而不需要自签证书。同时也支持 TLS。
|
||||
- **热重载** 支持配置文件热重载,动态修改端口转发服务。HTTP API 正在开发中。
|
||||
|
||||
|
@ -89,7 +91,7 @@ local_addr = "127.0.0.1:22" # 需要被转发的服务的地址
|
|||
|
||||
## Configuration
|
||||
|
||||
如果只有一个 `[server]` 和 `[client]` 块存在的话,`rathole` 可以根据配置文件的内容自动决定在服务器模式或客户端模式下运行,就像 [Quickstart](#Quickstart) 中的例子。
|
||||
如果只有一个 `[server]` 和 `[client]` 块存在的话,`rathole` 可以根据配置文件的内容自动决定在服务器模式或客户端模式下运行,就像 [Quickstart](#quickstart) 中的例子。
|
||||
|
||||
但 `[client]` 和 `[server]` 块也可以放在一个文件中。然后在服务器端,运行 `rathole --server config.toml`。在客户端,运行 `rathole --client config.toml` 来明确告诉 `rathole` 运行模式。
|
||||
|
||||
|
@ -104,13 +106,14 @@ local_addr = "127.0.0.1:22" # 需要被转发的服务的地址
|
|||
remote_addr = "example.com:2333" # Necessary. The address of the server
|
||||
default_token = "default_token_if_not_specify" # Optional. The default token of services, if they don't define their own ones
|
||||
heartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds
|
||||
retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second
|
||||
|
||||
[client.transport] # The whole block is optional. Specify which transport to use
|
||||
type = "tcp" # Optional. Possible values: ["tcp", "tls", "noise"]. Default: "tcp"
|
||||
|
||||
[client.transport.tcp] # Optional. Also affects `noise` and `tls`
|
||||
proxy = "socks5://user:passwd@127.0.0.1:1080" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported.
|
||||
nodelay = false # Optional. Determine whether to enable TCP_NODELAY, if applicable, to improve the latency but decrease the bandwidth. Default: false
|
||||
nodelay = true # Optional. Override the `client.transport.nodelay` per service
|
||||
keepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds
|
||||
keepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds
|
||||
|
||||
|
@ -123,11 +126,15 @@ pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" # Optional. Default value as shown
|
|||
local_private_key = "key_encoded_in_base64" # Optional
|
||||
remote_public_key = "key_encoded_in_base64" # Optional
|
||||
|
||||
[client.transport.websocket] # Necessary if `type` is "websocket"
|
||||
tls = true # If `true` then it will use settings in `client.transport.tls`
|
||||
|
||||
[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration
|
||||
type = "tcp" # Optional. The protocol that needs forwarding. Possible values: ["tcp", "udp"]. Default: "tcp"
|
||||
token = "whatever" # Necessary if `client.default_token` not set
|
||||
local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded
|
||||
nodelay = false # Optional. Determine whether to enable TCP_NODELAY for data transmission, if applicable, to improve the latency but decrease the bandwidth. Default: false
|
||||
nodelay = true # Optional. Determine whether to enable TCP_NODELAY for data transmission, if applicable, to improve the latency but decrease the bandwidth. Default: true
|
||||
retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config
|
||||
|
||||
[client.services.service2] # Multiple services can be defined
|
||||
local_addr = "127.0.0.1:1082"
|
||||
|
@ -141,7 +148,7 @@ heartbeat_interval = 30 # Optional. The interval between two application-layer h
|
|||
type = "tcp"
|
||||
|
||||
[server.transport.tcp] # Same as the client
|
||||
nodelay = false
|
||||
nodelay = true
|
||||
keepalive_secs = 20
|
||||
keepalive_interval = 8
|
||||
|
||||
|
@ -154,11 +161,14 @@ pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s"
|
|||
local_private_key = "key_encoded_in_base64"
|
||||
remote_public_key = "key_encoded_in_base64"
|
||||
|
||||
[server.transport.websocket] # Necessary if `type` is "websocket"
|
||||
tls = true # If `true` then it will use settings in `server.transport.tls`
|
||||
|
||||
[server.services.service1] # The service name must be identical to the client side
|
||||
type = "tcp" # Optional. Same as the client `[client.services.X.type]
|
||||
token = "whatever" # Necessary if `server.default_token` not set
|
||||
bind_addr = "0.0.0.0:8081" # Necessary. The address of the service is exposed at. Generally only the port needs to be change.
|
||||
nodelay = false # Optional. Same as the client
|
||||
nodelay = true # Optional. Same as the client
|
||||
|
||||
[server.services.service2]
|
||||
bind_addr = "0.0.0.1:8082"
|
||||
|
@ -178,6 +188,12 @@ RUST_LOG=error ./rathole config.toml
|
|||
|
||||
如果 `RUST_LOG` 不存在,默认的日志级别是 `info`。
|
||||
|
||||
### Tuning
|
||||
|
||||
从 v0.4.7 开始, rathole 默认启用 TCP_NODELAY。这能够减少延迟并使交互式应用受益,比如 RDP,Minecraft 服务器。但它会减少一些带宽。
|
||||
|
||||
如果带宽更重要,比如网盘类应用,TCP_NODELAY 仍然可以通过配置 `nodelay = false` 关闭。
|
||||
|
||||
## Benchmark
|
||||
|
||||
rathole 的延迟与 [frp](https://github.com/fatedier/frp) 相近,在高并发情况下表现更好,能提供更大的带宽,内存占用更少。
|
||||
|
|
53
README.md
53
README.md
|
@ -4,7 +4,7 @@
|
|||
|
||||
[![GitHub stars](https://img.shields.io/github/stars/rapiz1/rathole)](https://github.com/rapiz1/rathole/stargazers)
|
||||
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rapiz1/rathole)](https://github.com/rapiz1/rathole/releases)
|
||||
![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/rapiz1/rathole/Rust/main)
|
||||
![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rapiz1/rathole/rust.yml?branch=main)
|
||||
[![GitHub all releases](https://img.shields.io/github/downloads/rapiz1/rathole/total)](https://github.com/rapiz1/rathole/releases)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/rapiz1/rathole)](https://hub.docker.com/r/rapiz1/rathole)
|
||||
[![Join the chat at https://gitter.im/rapiz1/rathole](https://badges.gitter.im/rapiz1/rathole.svg)](https://gitter.im/rapiz1/rathole?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
@ -17,19 +17,21 @@ rathole, like [frp](https://github.com/fatedier/frp) and [ngrok](https://github.
|
|||
|
||||
<!-- TOC -->
|
||||
|
||||
- [Features](#features)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Configuration](#configuration)
|
||||
- [Logging](#logging)
|
||||
- [Benchmark](#benchmark)
|
||||
- [Development Status](#development-status)
|
||||
- [rathole](#rathole)
|
||||
- [Features](#features)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Configuration](#configuration)
|
||||
- [Logging](#logging)
|
||||
- [Tuning](#tuning)
|
||||
- [Benchmark](#benchmark)
|
||||
- [Planning](#planning)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Features
|
||||
|
||||
- **High Performance** Much higher throughput can be achieved than frp, and more stable when handling a large volume of connections. See [Benchmark](#Benchmark)
|
||||
- **Low Resource Consumption** Consumes much fewer memory than similar tools. See [Benchmark](#Benchmark). [The binary can be](docs/build-guide.md) **as small as ~500KiB** to fit the constraints of devices, like embedded devices as routers.
|
||||
- **High Performance** Much higher throughput can be achieved than frp, and more stable when handling a large volume of connections. See [Benchmark](#benchmark)
|
||||
- **Low Resource Consumption** Consumes much fewer memory than similar tools. See [Benchmark](#benchmark). [The binary can be](docs/build-guide.md) **as small as ~500KiB** to fit the constraints of devices, like embedded devices as routers.
|
||||
- **Security** Tokens of services are mandatory and service-wise. The server and clients are responsible for their own configs. With the optional Noise Protocol, encryption can be configured at ease. No need to create a self-signed certificate! TLS is also supported.
|
||||
- **Hot Reload** Services can be added or removed dynamically by hot-reloading the configuration file. HTTP API is WIP.
|
||||
|
||||
|
@ -53,7 +55,7 @@ Create `server.toml` with the following content and accommodate it to your needs
|
|||
bind_addr = "0.0.0.0:2333" # `2333` specifies the port that rathole listens for clients
|
||||
|
||||
[server.services.my_nas_ssh]
|
||||
token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to a arbitrary value.
|
||||
token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to an arbitrary value.
|
||||
bind_addr = "0.0.0.0:5202" # `5202` specifies the port that exposes `my_nas_ssh` to the Internet
|
||||
```
|
||||
|
||||
|
@ -91,7 +93,7 @@ To run `rathole` run as a background service on Linux, checkout the [systemd exa
|
|||
|
||||
## Configuration
|
||||
|
||||
`rathole` can automatically determine to run in the server mode or the client mode, according to the content of the configuration file, if only one of `[server]` and `[client]` block is present, like the example in [Quickstart](#Quickstart).
|
||||
`rathole` can automatically determine to run in the server mode or the client mode, according to the content of the configuration file, if only one of `[server]` and `[client]` block is present, like the example in [Quickstart](#quickstart).
|
||||
|
||||
But the `[client]` and `[server]` block can also be put in one file. Then on the server side, run `rathole --server config.toml` and on the client side, run `rathole --client config.toml` to explicitly tell `rathole` the running mode.
|
||||
|
||||
|
@ -106,13 +108,14 @@ Here is the full configuration specification:
|
|||
remote_addr = "example.com:2333" # Necessary. The address of the server
|
||||
default_token = "default_token_if_not_specify" # Optional. The default token of services, if they don't define their own ones
|
||||
heartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds
|
||||
retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second
|
||||
|
||||
[client.transport] # The whole block is optional. Specify which transport to use
|
||||
type = "tcp" # Optional. Possible values: ["tcp", "tls", "noise"]. Default: "tcp"
|
||||
|
||||
[client.transport.tcp] # Optional. Also affects `noise` and `tls`
|
||||
proxy = "socks5://user:passwd@127.0.0.1:1080" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported.
|
||||
nodelay = false # Optional. Determine whether to enable TCP_NODELAY, if applicable, to improve the latency but decrease the bandwidth. Default: false
|
||||
nodelay = true # Optional. Determine whether to enable TCP_NODELAY, if applicable, to improve the latency but decrease the bandwidth. Default: true
|
||||
keepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds
|
||||
keepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds
|
||||
|
||||
|
@ -125,11 +128,15 @@ pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" # Optional. Default value as shown
|
|||
local_private_key = "key_encoded_in_base64" # Optional
|
||||
remote_public_key = "key_encoded_in_base64" # Optional
|
||||
|
||||
[client.transport.websocket] # Necessary if `type` is "websocket"
|
||||
tls = true # If `true` then it will use settings in `client.transport.tls`
|
||||
|
||||
[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration
|
||||
type = "tcp" # Optional. The protocol that needs forwarding. Possible values: ["tcp", "udp"]. Default: "tcp"
|
||||
token = "whatever" # Necessary if `client.default_token` not set
|
||||
local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded
|
||||
nodelay = false # Optional. Determine whether to enable TCP_NODELAY for data transmission, if applicable, to improve the latency but decrease the bandwidth. Default: false
|
||||
nodelay = true # Optional. Override the `client.transport.nodelay` per service
|
||||
retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config
|
||||
|
||||
[client.services.service2] # Multiple services can be defined
|
||||
local_addr = "127.0.0.1:1082"
|
||||
|
@ -143,7 +150,7 @@ heartbeat_interval = 30 # Optional. The interval between two application-layer h
|
|||
type = "tcp"
|
||||
|
||||
[server.transport.tcp] # Same as the client
|
||||
nodelay = false
|
||||
nodelay = true
|
||||
keepalive_secs = 20
|
||||
keepalive_interval = 8
|
||||
|
||||
|
@ -156,11 +163,14 @@ pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s"
|
|||
local_private_key = "key_encoded_in_base64"
|
||||
remote_public_key = "key_encoded_in_base64"
|
||||
|
||||
[server.transport.websocket] # Necessary if `type` is "websocket"
|
||||
tls = true # If `true` then it will use settings in `server.transport.tls`
|
||||
|
||||
[server.services.service1] # The service name must be identical to the client side
|
||||
type = "tcp" # Optional. Same as the client `[client.services.X.type]
|
||||
token = "whatever" # Necessary if `server.default_token` not set
|
||||
bind_addr = "0.0.0.0:8081" # Necessary. The address of the service is exposed at. Generally only the port needs to be change.
|
||||
nodelay = false # Optional. Same as the client
|
||||
nodelay = true # Optional. Same as the client
|
||||
|
||||
[server.services.service2]
|
||||
bind_addr = "0.0.0.1:8082"
|
||||
|
@ -178,6 +188,12 @@ will run `rathole` with only error level logging.
|
|||
|
||||
If `RUST_LOG` is not present, the default logging level is `info`.
|
||||
|
||||
### Tuning
|
||||
|
||||
From v0.4.7, rathole enables TCP_NODELAY by default, which should benefit the latency and interactive applications like rdp, Minecraft servers. However, it slightly decreases the bandwidth.
|
||||
|
||||
If the bandwidth is more important, TCP_NODELAY can be opted out with `nodelay = false`.
|
||||
|
||||
## Benchmark
|
||||
|
||||
rathole has similar latency to [frp](https://github.com/fatedier/frp), but can handle a more connections, provide larger bandwidth, with less memory usage.
|
||||
|
@ -191,13 +207,8 @@ For more details, see the separate page [Benchmark](./docs/benchmark.md).
|
|||
![udp_bitrate](./docs/img/udp_bitrate.svg)
|
||||
![mem](./docs/img/mem-graph.png)
|
||||
|
||||
## Development Status
|
||||
## Planning
|
||||
|
||||
`rathole` is under active development. A load of features is on the way:
|
||||
|
||||
- [x] TLS support
|
||||
- [x] UDP support
|
||||
- [x] Hot reloading
|
||||
- [ ] HTTP APIs for configuration
|
||||
|
||||
[Out of Scope](./docs/out-of-scope.md) lists features that are not planned to be implemented and why.
|
||||
|
|
|
@ -1,30 +1,54 @@
|
|||
# Build Guide
|
||||
|
||||
This is for those who want to build `rathole` themselves, possibly because the need of latest features or the minimal binary size.
|
||||
|
||||
## Build
|
||||
|
||||
To use default build settings, run:
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Customize the build
|
||||
You may need to pre-install [openssl](https://docs.rs/openssl/latest/openssl/index.html) dependencies in Unix-like systems.
|
||||
|
||||
## Customize the Build
|
||||
|
||||
`rathole` comes with lots of *crate features* that determine whether a certain feature will be compiled or not. Supported features can be checked out in `[features]` of [Cargo.toml](../Cargo.toml).
|
||||
|
||||
For example, to build `rathole` with the `client` and `noise` feature:
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo build --release --no-default-features --features client,noise
|
||||
```
|
||||
|
||||
## Rustls Support
|
||||
|
||||
`rathole` provides optional `rustls` support. It's an almost drop-in replacement of `native-tls` support. (See [Transport](transport.md) for more information.)
|
||||
|
||||
To enable this, disable the default features and enable `rustls` feature. And for websocket feature, enable `websocket-rustls` feature as well.
|
||||
|
||||
You can also use command line option for this. For example, to replace all default features with `rustls`:
|
||||
|
||||
```sh
|
||||
cargo build --release --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload
|
||||
```
|
||||
|
||||
Feature `rustls` and `websocket-rustls` cannot be enabled with `native-tls` and `websocket-native-tls` at the same time, as they are mutually exclusive. Enabling both will result in a compile error.
|
||||
|
||||
(Note that default features contains `native-tls` and `websocket-native-tls`.)
|
||||
|
||||
## Minimalize the binary
|
||||
|
||||
1. Build with the `minimal` profile
|
||||
|
||||
The `release` build profile optimize for the program running time, not the binary size.
|
||||
The `release` build profile optimize for the program running time, not the binary size.
|
||||
|
||||
However, the `minimal` profile enables lots of optimization for the binary size to produce a much smaller binary.
|
||||
|
||||
For example, to build `rathole` with `client` feature with the `minimal` profile:
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo build --profile minimal --no-default-features --features client
|
||||
```
|
||||
|
||||
|
@ -33,7 +57,8 @@ cargo build --profile minimal --no-default-features --features client
|
|||
The binary that step 1 produces can be even smaller, by using `strip` and `upx` to remove the symbols and compress the binary.
|
||||
|
||||
Like:
|
||||
```
|
||||
|
||||
```sh
|
||||
strip rathole
|
||||
upx --best --lzma rathole
|
||||
```
|
||||
|
|
|
@ -30,5 +30,5 @@ When `rathole` starts in the client mode, it creates connections to `server.comm
|
|||
|
||||
When a control channel starts, the server challenge the client by a nonce, the client is required to authenticate as the service it wants to represent. Then the forwarding of that service is set up.
|
||||
|
||||
When the server accepts a connection on a service's `bind_port`, it sends a control command to the client via the corresponding contorl channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency.
|
||||
When the server accepts a connection on a service's `bind_port`, it sends a control command to the client via the corresponding control channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency.
|
||||
|
||||
|
|
|
@ -17,3 +17,7 @@ But that doesn't mean it's not useful for other purposes. In the future, more co
|
|||
- *`frp`'s STCP or other setup that requires visitors' side configuration*
|
||||
|
||||
If that kind of setup is possible, then there are a lot more tools available. You may want to consider secure tunnels like wireguard or zerotier. `rathole` primarily focuses on NAT traversal by forwarding, which doesn't require any setup for visitors.
|
||||
|
||||
- *Caching `local_ip`'s DNS records*
|
||||
|
||||
As responded in [issue #183](https://github.com/rapiz1/rathole/issues/183), `local_ip` cache is not feasible because we have no reliable way to detect ip change. Handle DNS TTL and so on should be done with a DNS server, not a client. Caching ip is generally dangerous for clients. If you care about the `local_ip` query you can set up a local DNS server and enable caching. Then the local lookup should be trivial.
|
||||
|
|
|
@ -3,26 +3,54 @@
|
|||
By default, `rathole` forwards traffic as it is. Different options can be enabled to secure the traffic.
|
||||
|
||||
## TLS
|
||||
|
||||
Checkout the [example](../examples/tls)
|
||||
|
||||
### Client
|
||||
|
||||
Normally, a self-signed certificate is used. In this case, the client needs to trust the CA. `trusted_root` is the path to the root CA's certificate PEM file.
|
||||
`hostname` is the hostname that the client used to validate aginst the certificate that the server presents.
|
||||
```
|
||||
`hostname` is the hostname that the client used to validate aginst the certificate that the server presents. Note that it does not have to be the same with the `remote_addr` in `[client]`.
|
||||
|
||||
```toml
|
||||
[client.transport.tls]
|
||||
trusted_root = "example/tls/ca-cert.pem"
|
||||
hostname = "0.0.0.0"
|
||||
trusted_root = "example/tls/rootCA.crt"
|
||||
hostname = "localhost"
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
PKCS#12 archives are needed to run the server.
|
||||
|
||||
It can be created using openssl like:
|
||||
|
||||
```sh
|
||||
openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt
|
||||
```
|
||||
openssl pkcs12 -export -out identity.pfx -inkey server-key.pem -in server-cert.pem -certfile ca_chain_certs.pem
|
||||
|
||||
Aruguments are:
|
||||
|
||||
- `-inkey`: Server Private Key
|
||||
- `-in`: Server Certificate
|
||||
- `-certfile`: CA Certificate
|
||||
|
||||
Creating self-signed certificate with one's own CA is a non-trival task. However, a script is provided under tls example folder for reference.
|
||||
|
||||
### Rustls Support
|
||||
|
||||
`rathole` provides optional `rustls` support. [Build Guide](build-guide.md) demostrated this.
|
||||
|
||||
One difference is that, the crate we use for loading PKCS#12 archives can only handle limited types of PBE algorithms. We only support PKCS#12 archives that they (crate `p12`) support. So we need to specify the legacy format (openssl 1.x format) when creating the PKCS#12 archive.
|
||||
|
||||
In short, the command used with openssl 3 to create the PKCS#12 archive with `rustls` support is:
|
||||
|
||||
```sh
|
||||
openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt -legacy
|
||||
```
|
||||
|
||||
## Noise Protocol
|
||||
|
||||
### Quickstart for the Noise Protocl
|
||||
|
||||
In one word, the [Noise Protocol](http://noiseprotocol.org/noise.html) is a lightweigt, easy to configure and drop-in replacement of TLS. No need to create a self-sign certificate to secure the connection.
|
||||
|
||||
`rathole` comes with a reasonable default configuration for noise protocol. You can a glimpse of the minimal [example](../examples/noise_nk) for how it will look like.
|
||||
|
@ -30,12 +58,14 @@ In one word, the [Noise Protocol](http://noiseprotocol.org/noise.html) is a ligh
|
|||
The default noise protocol that `rathole` uses, which is `Noise_NK_25519_ChaChaPoly_BLAKE2s`, providing the authentication of the server, just like TLS with properly configured certificates. So MITM is no more a problem.
|
||||
|
||||
To use it, a X25519 keypair is needed.
|
||||
|
||||
#### Generate a Keypair
|
||||
|
||||
1. Run `rathole --genkey`, which will generate a keypair using the default X25519 algorithm.
|
||||
|
||||
It emits:
|
||||
```
|
||||
|
||||
```sh
|
||||
$ rathole --genkey
|
||||
Private Key:
|
||||
cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q=
|
||||
|
@ -43,11 +73,13 @@ cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q=
|
|||
Public Key:
|
||||
GQYTKSbWLBUSZiGfdWPSgek9yoOuaiwGD/GIX8Z1kkE=
|
||||
```
|
||||
|
||||
(WARNING: Don't use the keypair from the Internet, including this one)
|
||||
|
||||
2. The server should keep the private key to identify itself. And the client should keep the public key, which is used to verify whether the peer is the authentic server.
|
||||
|
||||
So relevant snippets of configuration are:
|
||||
|
||||
```toml
|
||||
# Client Side Configuration
|
||||
[client.transport]
|
||||
|
@ -65,9 +97,11 @@ local_private_key = "cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q="
|
|||
Then `rathole` will run under the protection of the Noise Protocol.
|
||||
|
||||
## Specifying the Pattern of Noise Protocol
|
||||
|
||||
The default configuration of Noise Protocol that comes with `rathole` satifies most use cases, which is described above. But there're other patterns that can be useful.
|
||||
|
||||
### No Authentication
|
||||
|
||||
This configuration provides encryption of the traffic but provides no authentication, which means it's vulnerable to MITM attack, but is resistent to the sniffing and replay attack. If MITM attack is not one of the concerns, this is more convenient to use.
|
||||
|
||||
```toml
|
||||
|
@ -99,6 +133,7 @@ remote_public_key = "server-pub-key-here"
|
|||
### Other Patterns
|
||||
|
||||
To find out which pattern to use, refer to:
|
||||
|
||||
- [7.5. Interactive handshake patterns (fundamental)](https://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental)
|
||||
- [8. Protocol names and modifiers](https://noiseprotocol.org/noise.html#protocol-names-and-modifiers)
|
||||
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
|
||||
The directory lists some systemd unit files for example, which can be used to run `rathole` as a service on Linux.
|
||||
|
||||
[The `@` symbol in name of unit files](https://superuser.com/questions/393423/the-symbol-and-systemctl-and-vsftpd) such as
|
||||
[The `@` symbol in the name of unit files](https://superuser.com/questions/393423/the-symbol-and-systemctl-and-vsftpd) such as
|
||||
`rathole@.service` facilitates the management of multiple instances of `rathole`.
|
||||
|
||||
For the naming of the example, `ratholes` stands for `rathole --server`, and `ratholec` stands for `rathole --client`, `rathole` is just `rathole`.
|
||||
|
||||
Assuming that `rathole` is installed in `/usr/bin/rathole`, and the configuration file is in `/etc/rathole/app1.toml`, the following steps shows how to run an instance of `rathole --server`.
|
||||
For security, it is suggested to store configuration files with permission `600`, that is, only the owner can read the file, preventing arbitrary users on the system from accessing the secret tokens.
|
||||
|
||||
### With root privilege
|
||||
|
||||
Assuming that `rathole` is installed in `/usr/bin/rathole`, and the configuration file is in `/etc/rathole/app1.toml`, the following steps show how to run an instance of `rathole --server` with root.
|
||||
|
||||
1. Create a service file.
|
||||
|
||||
|
@ -22,14 +26,49 @@ sudo mkdir -p /etc/rathole
|
|||
# And create the configuration file named `app1.toml` inside /etc/rathole
|
||||
```
|
||||
|
||||
3. Enable and start the service
|
||||
3. Enable and start the service.
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload # Make sure systemd find the new unit
|
||||
sudo systemctl enable ratholes@app1 --now
|
||||
```
|
||||
|
||||
And if there's another configuration named `app2.toml` in `/etc/rathole`, then
|
||||
`sudo systemctl enable ratholes@app2 --now` can start an instance for that configuration.
|
||||
### Without root privilege
|
||||
|
||||
The same applies to `rathole --client` and `rathole`.
|
||||
Assuming that `rathole` is installed in `~/.local/bin/rathole`, and the configuration file is in `~/.local/etc/rathole/app1.toml`, the following steps show how to run an instance of `rathole --server` without root.
|
||||
|
||||
1. Edit the example service file as...
|
||||
|
||||
```txt
|
||||
# with root
|
||||
# ExecStart=/usr/bin/rathole -s /etc/rathole/%i.toml
|
||||
# without root
|
||||
ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/%i.toml
|
||||
```
|
||||
|
||||
2. Create a service file.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cp ratholes@.service ~/.config/systemd/user/
|
||||
```
|
||||
|
||||
3. Create the configuration file `app1.toml`.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.local/etc/rathole
|
||||
# And create the configuration file named `app1.toml` inside ~/.local/etc/rathole
|
||||
```
|
||||
|
||||
4. Enable and start the service.
|
||||
|
||||
```bash
|
||||
systemctl --user daemon-reload # Make sure systemd find the new unit
|
||||
systemctl --user enable ratholes@app1 --now
|
||||
```
|
||||
|
||||
### Run multiple services
|
||||
|
||||
To run multiple services at once, simply add another configuration, say `app2.toml` under `/etc/rathole` (`~/.local/etc/rathole` for non-root), then run `sudo systemctl enable ratholes@app2 --now` (`systemctl --user enable ratholes@app2 --now` for non-root) to start an instance for that configuration.
|
||||
|
||||
The same applies to `ratholec@.service` for `rathole --client` and `rathole@.service` for `rathole`.
|
||||
|
|
|
@ -4,11 +4,14 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/rathole /etc/rathole/%i.toml
|
||||
LimitNOFILE=1048576
|
||||
|
||||
# with root
|
||||
ExecStart=/usr/bin/rathole /etc/rathole/%i.toml
|
||||
# without root
|
||||
# ExecStart=%h/.local/bin/rathole %h/.local/etc/rathole/%i.toml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -4,11 +4,14 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/rathole -c /etc/rathole/rathole.toml
|
||||
LimitNOFILE=1048576
|
||||
|
||||
# with root
|
||||
ExecStart=/usr/bin/rathole -c /etc/rathole/rathole.toml
|
||||
# without root
|
||||
# ExecStart=%h/.local/bin/rathole -c %h/.local/etc/rathole/rathole.toml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -4,11 +4,14 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/rathole -c /etc/rathole/%i.toml
|
||||
LimitNOFILE=1048576
|
||||
|
||||
# with root
|
||||
ExecStart=/usr/bin/rathole -c /etc/rathole/%i.toml
|
||||
# without root
|
||||
# ExecStart=%h/.local/bin/rathole -c %h/.local/etc/rathole/%i.toml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -4,11 +4,14 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/rathole -s /etc/rathole/rathole.toml
|
||||
LimitNOFILE=1048576
|
||||
|
||||
# with root
|
||||
ExecStart=/usr/bin/rathole -s /etc/rathole/rathole.toml
|
||||
# without root
|
||||
# ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/rathole.toml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -4,11 +4,14 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/rathole -s /etc/rathole/%i.toml
|
||||
LimitNOFILE=1048576
|
||||
|
||||
# with root
|
||||
ExecStart=/usr/bin/rathole -s /etc/rathole/%i.toml
|
||||
# without root
|
||||
# ExecStart=%h/.local/bin/rathole -s %h/.local/etc/rathole/%i.toml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIUXTmJtkI6aK16A8HPkP2IvowmSKwwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAxMDIwODEzMzhaFw0yMzAy
|
||||
MDMwODEzMzhaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
|
||||
AQUAA4ICDwAwggIKAoICAQDAAq3LEmJigEuRT9sswUx6Kfc4T04oZvZTSYNIRrBF
|
||||
Zcc/EGZF/t/k2ciGDSAB1mL2rUdIfWveQ/5kRCSFffX5qvKFkzogRQQjFPLFjfoC
|
||||
lKXxvy/BOIwF786gvHbz5EI1dcAL+nRco3U6dHPdewvbQwX9cZrUD3pq+r1qlipY
|
||||
w5rZL7Z5cNoczhRAgFhIBHvsgBazkkOB7PDUkmkYAYnw3uK+r4coAqnnfjpxoaCQ
|
||||
dQi4JX2VvqOdgxzw9vIRqbL+p2NBPnVjcSj067Y9sxtfR3Xmt2dlMJuReFN8phnK
|
||||
8GiYiuiYA01O84htjHt+A8oVYKalXdPeikoSgPmhoJCQQs0NkBzGCc33U7XEa6kM
|
||||
j6Y81Id4uXAK5LxyVGo5zOEvOyF3EhceIJDeGS9NsGJyT757OuKrsCK0v8KNPsEh
|
||||
VvrcngnRQOWFTg/rp/vSrj7S5i0NPjkEpRitxaYBOg40DXyG1GfYf1SvneXpT0gh
|
||||
ZbgjipPrwvuZnJVqqIv1hVVNOKo7nJS24rZ/andZS8g6OE0bL9AlE1Sp2lMXuagJ
|
||||
2haPa2rSFZPqNPrP9wh5KVreD9UNeTb37NbXWeZXwKR8v20GAWjb2QQKY92zlMpI
|
||||
gmViEvJHrHbKVoU/8gyS9R7iL9JOehk3sqVhbjaDyouC9mosPrQFzp1frKvSlKNg
|
||||
1wIDAQABo1MwUTAdBgNVHQ4EFgQU98MJp09MMFw5s4sacYozQFzTNFwwHwYDVR0j
|
||||
BBgwFoAU98MJp09MMFw5s4sacYozQFzTNFwwDwYDVR0TAQH/BAUwAwEB/zANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEABOtNqqKFEA3vynOFteZV+VquaRKqDuYn0doMMPH9cY20
|
||||
4ASioa3aqbmvBiSTDsOdvgP6j5nSVEtQCt5P3fBRMa8a3YnTGPNx8uGPuOA+ZD+b
|
||||
USR5FcXJHtkjSfpVF9DOZr34+khRpfHPEZQiaAAiKwaRnI4Gqhv6e6JoaimkQDYj
|
||||
xcKw+f1NcCdhSTkpcx9K/Qfa0cXKSL+0Hwl5AbDMsnRAkKu62YKdOv36nnBOMc2S
|
||||
6laNIx20nt8Evm3KBNDRiHAw8pwMGfnxCCG6hGo2IvYh6hOjZupVpP55iMgQUkfF
|
||||
Gmvxe/4wjuPCvI/Liy0PFfiCHVKASWIiMWG8u8WfJUw1/4RFZu4l2LVVuJOujr6n
|
||||
1k5vzIozuo6Ym8mKnnHQmYf5K9T/YuRW3EFa9Ar6/krjw6K/I97P+Wh/DVZiaGC5
|
||||
n90ZcRj+abb+zOfz0AHTOp7zlr3w4si7AF3tZ9WhW2R0BC3wwmXygli0I6iMXE7E
|
||||
tvXM5UwxLJoJen2fWqn75/91BifEqPWckPb1h14i73hAPVSte1wvstf8mER/DFSX
|
||||
Is/GxAhRsZChHn2lEJsvPlrfyMxYwcXTTvd//sp+iOZjfky5vhRuMDUYsHx6/znT
|
||||
q/rpT3CMnAVlMTf8n/0dY4mdcaQj0cRJfVnUlvZnhw0tJzCP3rH3smlpWloexds=
|
||||
-----END CERTIFICATE-----
|
|
@ -1,12 +1,12 @@
|
|||
[client]
|
||||
remote_addr = "localhost:2333"
|
||||
remote_addr = "127.0.0.1:2333"
|
||||
default_token = "123"
|
||||
|
||||
[client.transport]
|
||||
type = "tls"
|
||||
[client.transport.tls]
|
||||
trusted_root = "examples/tls/ca-cert.pem"
|
||||
hostname = "0.0.0.0"
|
||||
trusted_root = "examples/tls/rootCA.crt"
|
||||
hostname = "localhost"
|
||||
|
||||
[client.services.foo1]
|
||||
local_addr = "127.0.0.1:80"
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/sh
|
||||
|
||||
# create CA
|
||||
openssl req -x509 \
|
||||
-sha256 -days 356 \
|
||||
-nodes \
|
||||
-newkey rsa:2048 \
|
||||
-subj "/CN=MyOwnCA/C=US/L=San Fransisco" \
|
||||
-keyout rootCA.key -out rootCA.crt
|
||||
|
||||
# create server private key
|
||||
openssl genrsa -out server.key 2048
|
||||
|
||||
# create certificate signing request (CSR)
|
||||
cat > csr.conf <<EOF
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
req_extensions = req_ext
|
||||
distinguished_name = dn
|
||||
|
||||
[ dn ]
|
||||
C = US
|
||||
ST = California
|
||||
L = San Fransisco
|
||||
O = Someone
|
||||
OU = Someone
|
||||
CN = localhost
|
||||
|
||||
[ req_ext ]
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[ alt_names ]
|
||||
DNS.1 = localhost
|
||||
EOF
|
||||
|
||||
openssl req -new -key server.key -out server.csr -config csr.conf
|
||||
|
||||
# create server cert
|
||||
cat > cert.conf <<EOF
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints=CA:FALSE
|
||||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
EOF
|
||||
|
||||
openssl x509 -req \
|
||||
-in server.csr \
|
||||
-CA rootCA.crt -CAkey rootCA.key \
|
||||
-out server.crt \
|
||||
-days 365 \
|
||||
-sha256 -extfile cert.conf
|
||||
|
||||
# create pkcs12
|
||||
openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile rootCA.crt \
|
||||
-passout pass:1234 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES
|
||||
|
||||
# clean up
|
||||
rm server.csr csr.conf cert.conf
|
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDTzCCAjegAwIBAgIUHPYndZflmbDV/30C+BHQSiNvUTQwDQYJKoZIhvcNAQEL
|
||||
BQAwNzEQMA4GA1UEAwwHTXlPd25DQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNh
|
||||
biBGcmFuc2lzY28wHhcNMjQwMjE1MDUwNDQ5WhcNMjUwMjA1MDUwNDQ5WjA3MRAw
|
||||
DgYDVQQDDAdNeU93bkNBMQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5z
|
||||
aXNjbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKIx0LdgvDrXGoGw
|
||||
XJ9s3Y+nr34NMPPLTbo/C2Yj1pD4mxZKK7d1VuwuBNM1h/WQLhA9+x4ZcKYZ1S1g
|
||||
3BRMuAdm/ZJyeeI1QDRqUlZD16ehPnY0Zy9sZX7oMKVS0m7l8zDv4nvDp9prC5yf
|
||||
8eoI7zoAWiMv/xPacYXFTAJbUb0VgovFyf3rzgIzs/NBF675FxrQtbhM2j4DdMkJ
|
||||
9UwRi+qmqtH/Z/Ddy4oMkPflEgKSgDEidmqa552CRExO3c+1ZbMEzq8iOUZ3Vb+g
|
||||
enfo0SwQUxQ9PEUOAd13siEXs51jZ7JqNmj1d/lEIbAuX8znWDqLYz9FUN4QNsim
|
||||
8Q/trBcCAwEAAaNTMFEwHQYDVR0OBBYEFP7eOqvUgs8/LOMonEZ6ubRaLkQMMB8G
|
||||
A1UdIwQYMBaAFP7eOqvUgs8/LOMonEZ6ubRaLkQMMA8GA1UdEwEB/wQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQELBQADggEBABfLdbsbchr8Ep4mCv75ojWe11Mdd3Eg8EOePukC
|
||||
w918zqU6dZMmbnLtoXFk6QgFZnvD5MpmU4/d/BmvL9+CJJ9mJPwR2Vb/rIOPXV13
|
||||
+kjHo/NwNbw5TdmPMbneyCjMdxRqmYKGoWYwbsI09YCK5Cb0J2fYmMrcACSVIUvz
|
||||
WC7CPPwTA3zvzf9xab+naoE1dbThRDGvVPXEFFOSMIXC0UzCvG0Lj3NTyXyu4XJ0
|
||||
TUcQUlnptLSejb+uh/5MSqwnEoc1dm2mW/oij1Gqg29+6WNw6wPv/cnC7VvlY4Eu
|
||||
CR9tvTjMNb7G6VRok9W0HJec6dNf3FJJ1pVzVL8bKI19G54=
|
||||
-----END CERTIFICATE-----
|
105
src/client.rs
105
src/client.rs
|
@ -1,15 +1,16 @@
|
|||
use crate::config::{ClientConfig, ClientServiceConfig, Config, ServiceType, TransportType};
|
||||
use crate::config_watcher::ServiceChange;
|
||||
use crate::config_watcher::{ClientServiceChange, ConfigChange};
|
||||
use crate::helper::udp_connect;
|
||||
use crate::protocol::Hello::{self, *};
|
||||
use crate::protocol::{
|
||||
self, read_ack, read_control_cmd, read_data_cmd, read_hello, Ack, Auth, ControlChannelCmd,
|
||||
DataChannelCmd, UdpTraffic, CURRENT_PROTO_VERSION, HASH_WIDTH_IN_BYTES,
|
||||
};
|
||||
use crate::transport::{SocketOpts, TcpTransport, Transport};
|
||||
use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use backoff::backoff::Backoff;
|
||||
use backoff::future::retry_notify;
|
||||
use backoff::ExponentialBackoff;
|
||||
use backoff::{backoff::Backoff, future::retry_notify};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
|
@ -22,8 +23,10 @@ use tracing::{debug, error, info, instrument, trace, warn, Instrument, Span};
|
|||
|
||||
#[cfg(feature = "noise")]
|
||||
use crate::transport::NoiseTransport;
|
||||
#[cfg(feature = "tls")]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
use crate::transport::TlsTransport;
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
use crate::transport::WebsocketTransport;
|
||||
|
||||
use crate::constants::{run_control_chan_backoff, UDP_BUFFER_SIZE, UDP_SENDQ_SIZE, UDP_TIMEOUT};
|
||||
|
||||
|
@ -31,35 +34,46 @@ use crate::constants::{run_control_chan_backoff, UDP_BUFFER_SIZE, UDP_SENDQ_SIZE
|
|||
pub async fn run_client(
|
||||
config: Config,
|
||||
shutdown_rx: broadcast::Receiver<bool>,
|
||||
service_rx: mpsc::Receiver<ServiceChange>,
|
||||
update_rx: mpsc::Receiver<ConfigChange>,
|
||||
) -> Result<()> {
|
||||
let config = config.client.ok_or_else(|| anyhow!(
|
||||
let config = config.client.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Try to run as a client, but the configuration is missing. Please add the `[client]` block"
|
||||
))?;
|
||||
)
|
||||
})?;
|
||||
|
||||
match config.transport.transport_type {
|
||||
TransportType::Tcp => {
|
||||
let mut client = Client::<TcpTransport>::from(config).await?;
|
||||
client.run(shutdown_rx, service_rx).await
|
||||
client.run(shutdown_rx, update_rx).await
|
||||
}
|
||||
TransportType::Tls => {
|
||||
#[cfg(feature = "tls")]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
{
|
||||
let mut client = Client::<TlsTransport>::from(config).await?;
|
||||
client.run(shutdown_rx, service_rx).await
|
||||
client.run(shutdown_rx, update_rx).await
|
||||
}
|
||||
#[cfg(not(feature = "tls"))]
|
||||
crate::helper::feature_not_compile("tls")
|
||||
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
|
||||
crate::helper::feature_neither_compile("native-tls", "rustls")
|
||||
}
|
||||
TransportType::Noise => {
|
||||
#[cfg(feature = "noise")]
|
||||
{
|
||||
let mut client = Client::<NoiseTransport>::from(config).await?;
|
||||
client.run(shutdown_rx, service_rx).await
|
||||
client.run(shutdown_rx, update_rx).await
|
||||
}
|
||||
#[cfg(not(feature = "noise"))]
|
||||
crate::helper::feature_not_compile("noise")
|
||||
}
|
||||
TransportType::Websocket => {
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
{
|
||||
let mut client = Client::<WebsocketTransport>::from(config).await?;
|
||||
client.run(shutdown_rx, update_rx).await
|
||||
}
|
||||
#[cfg(not(any(feature = "websocket-native-tls", feature = "websocket-rustls")))]
|
||||
crate::helper::feature_neither_compile("websocket-native-tls", "websocket-rustls")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +103,7 @@ impl<T: 'static + Transport> Client<T> {
|
|||
async fn run(
|
||||
&mut self,
|
||||
mut shutdown_rx: broadcast::Receiver<bool>,
|
||||
mut service_rx: mpsc::Receiver<ServiceChange>,
|
||||
mut update_rx: mpsc::Receiver<ConfigChange>,
|
||||
) -> Result<()> {
|
||||
for (name, config) in &self.config.services {
|
||||
// Create a control channel for each service defined
|
||||
|
@ -114,24 +128,9 @@ impl<T: 'static + Transport> Client<T> {
|
|||
}
|
||||
break;
|
||||
},
|
||||
e = service_rx.recv() => {
|
||||
e = update_rx.recv() => {
|
||||
if let Some(e) = e {
|
||||
match e {
|
||||
ServiceChange::ClientAdd(s)=> {
|
||||
let name = s.name.clone();
|
||||
let handle = ControlChannelHandle::new(
|
||||
s,
|
||||
self.config.remote_addr.clone(),
|
||||
self.transport.clone(),
|
||||
self.config.heartbeat_timeout
|
||||
);
|
||||
let _ = self.service_handles.insert(name, handle);
|
||||
},
|
||||
ServiceChange::ClientDelete(s)=> {
|
||||
let _ = self.service_handles.remove(&s);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
self.handle_hot_reload(e).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,11 +143,32 @@ impl<T: 'static + Transport> Client<T> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_hot_reload(&mut self, e: ConfigChange) {
|
||||
match e {
|
||||
ConfigChange::ClientChange(client_change) => match client_change {
|
||||
ClientServiceChange::Add(cfg) => {
|
||||
let name = cfg.name.clone();
|
||||
let handle = ControlChannelHandle::new(
|
||||
cfg,
|
||||
self.config.remote_addr.clone(),
|
||||
self.transport.clone(),
|
||||
self.config.heartbeat_timeout,
|
||||
);
|
||||
let _ = self.service_handles.insert(name, handle);
|
||||
}
|
||||
ClientServiceChange::Delete(s) => {
|
||||
let _ = self.service_handles.remove(&s);
|
||||
}
|
||||
},
|
||||
ignored => warn!("Ignored {:?} since running as a client", ignored),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RunDataChannelArgs<T: Transport> {
|
||||
session_key: Nonce,
|
||||
remote_addr: String,
|
||||
remote_addr: AddrMaybeCached,
|
||||
connector: Arc<T>,
|
||||
socket_opts: SocketOpts,
|
||||
service: ClientServiceConfig,
|
||||
|
@ -383,9 +403,12 @@ struct ControlChannelHandle {
|
|||
impl<T: 'static + Transport> ControlChannel<T> {
|
||||
#[instrument(skip_all)]
|
||||
async fn run(&mut self) -> Result<()> {
|
||||
let mut remote_addr = AddrMaybeCached::new(&self.remote_addr);
|
||||
remote_addr.resolve().await?;
|
||||
|
||||
let mut conn = self
|
||||
.transport
|
||||
.connect(&self.remote_addr)
|
||||
.connect(&remote_addr)
|
||||
.await
|
||||
.with_context(|| format!("Failed to connect to {}", &self.remote_addr))?;
|
||||
T::hint(&conn, SocketOpts::for_control_channel());
|
||||
|
@ -430,7 +453,6 @@ impl<T: 'static + Transport> ControlChannel<T> {
|
|||
// Channel ready
|
||||
info!("Control channel established");
|
||||
|
||||
let remote_addr = self.remote_addr.clone();
|
||||
// Socket options for the data channel
|
||||
let socket_opts = SocketOpts::from_client_cfg(&self.service);
|
||||
let data_ch_args = Arc::new(RunDataChannelArgs {
|
||||
|
@ -459,8 +481,7 @@ impl<T: 'static + Transport> ControlChannel<T> {
|
|||
}
|
||||
},
|
||||
_ = time::sleep(Duration::from_secs(self.heartbeat_timeout)), if self.heartbeat_timeout != 0 => {
|
||||
warn!("Heartbeat timed out");
|
||||
break;
|
||||
return Err(anyhow!("Heartbeat timed out"))
|
||||
}
|
||||
_ = &mut self.shutdown_rx => {
|
||||
break;
|
||||
|
@ -485,6 +506,9 @@ impl ControlChannelHandle {
|
|||
|
||||
info!("Starting {}", hex::encode(digest));
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
|
||||
let mut retry_backoff = run_control_chan_backoff(service.retry_interval.unwrap());
|
||||
|
||||
let mut s = ControlChannel {
|
||||
digest,
|
||||
service,
|
||||
|
@ -496,7 +520,6 @@ impl ControlChannelHandle {
|
|||
|
||||
tokio::spawn(
|
||||
async move {
|
||||
let mut backoff = run_control_chan_backoff();
|
||||
let mut start = Instant::now();
|
||||
|
||||
while let Err(err) = s
|
||||
|
@ -510,10 +533,10 @@ impl ControlChannelHandle {
|
|||
|
||||
if start.elapsed() > Duration::from_secs(3) {
|
||||
// The client runs for at least 3 secs and then disconnects
|
||||
// Retry immediately
|
||||
backoff.reset();
|
||||
error!("{:#}. Retry...", err);
|
||||
} else if let Some(duration) = backoff.next_backoff() {
|
||||
retry_backoff.reset();
|
||||
}
|
||||
|
||||
if let Some(duration) = retry_backoff.next_backoff() {
|
||||
error!("{:#}. Retry in {:?}...", err, duration);
|
||||
time::sleep(duration).await;
|
||||
} else {
|
||||
|
|
|
@ -13,9 +13,12 @@ use crate::transport::{DEFAULT_KEEPALIVE_INTERVAL, DEFAULT_KEEPALIVE_SECS, DEFAU
|
|||
const DEFAULT_HEARTBEAT_INTERVAL_SECS: u64 = 30;
|
||||
const DEFAULT_HEARTBEAT_TIMEOUT_SECS: u64 = 40;
|
||||
|
||||
/// Client
|
||||
const DEFAULT_CLIENT_RETRY_INTERVAL_SECS: u64 = 1;
|
||||
|
||||
/// String with Debug implementation that emits "MASKED"
|
||||
/// Used to mask sensitive strings when logging
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq, Clone)]
|
||||
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone)]
|
||||
pub struct MaskedString(String);
|
||||
|
||||
impl Debug for MaskedString {
|
||||
|
@ -37,23 +40,22 @@ impl From<&str> for MaskedString {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Default)]
|
||||
pub enum TransportType {
|
||||
#[default]
|
||||
#[serde(rename = "tcp")]
|
||||
Tcp,
|
||||
#[serde(rename = "tls")]
|
||||
Tls,
|
||||
#[serde(rename = "noise")]
|
||||
Noise,
|
||||
#[serde(rename = "websocket")]
|
||||
Websocket,
|
||||
}
|
||||
|
||||
impl Default for TransportType {
|
||||
fn default() -> TransportType {
|
||||
TransportType::Tcp
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
/// Per service config
|
||||
/// All Option are optional in configuration but must be Some value in runtime
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ClientServiceConfig {
|
||||
#[serde(rename = "type", default = "default_service_type")]
|
||||
|
@ -63,6 +65,7 @@ pub struct ClientServiceConfig {
|
|||
pub local_addr: String,
|
||||
pub token: Option<MaskedString>,
|
||||
pub nodelay: Option<bool>,
|
||||
pub retry_interval: Option<u64>,
|
||||
}
|
||||
|
||||
impl ClientServiceConfig {
|
||||
|
@ -74,25 +77,22 @@ impl ClientServiceConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ServiceType {
|
||||
#[serde(rename = "tcp")]
|
||||
#[default]
|
||||
Tcp,
|
||||
#[serde(rename = "udp")]
|
||||
Udp,
|
||||
}
|
||||
|
||||
impl Default for ServiceType {
|
||||
fn default() -> Self {
|
||||
ServiceType::Tcp
|
||||
}
|
||||
}
|
||||
|
||||
fn default_service_type() -> ServiceType {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
/// Per service config
|
||||
/// All Option are optional in configuration but must be Some value in runtime
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ServerServiceConfig {
|
||||
#[serde(rename = "type", default = "default_service_type")]
|
||||
|
@ -112,7 +112,7 @@ impl ServerServiceConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TlsConfig {
|
||||
pub hostname: Option<String>,
|
||||
|
@ -125,7 +125,7 @@ fn default_noise_pattern() -> String {
|
|||
String::from("Noise_NK_25519_ChaChaPoly_BLAKE2s")
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NoiseConfig {
|
||||
#[serde(default = "default_noise_pattern")]
|
||||
|
@ -135,6 +135,12 @@ pub struct NoiseConfig {
|
|||
// TODO: Maybe psk can be added
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WebsocketConfig {
|
||||
pub tls: bool,
|
||||
}
|
||||
|
||||
fn default_nodelay() -> bool {
|
||||
DEFAULT_NODELAY
|
||||
}
|
||||
|
@ -147,7 +153,7 @@ fn default_keepalive_interval() -> u64 {
|
|||
DEFAULT_KEEPALIVE_INTERVAL
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TcpConfig {
|
||||
#[serde(default = "default_nodelay")]
|
||||
|
@ -170,7 +176,7 @@ impl Default for TcpConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TransportConfig {
|
||||
#[serde(rename = "type")]
|
||||
|
@ -179,13 +185,18 @@ pub struct TransportConfig {
|
|||
pub tcp: TcpConfig,
|
||||
pub tls: Option<TlsConfig>,
|
||||
pub noise: Option<NoiseConfig>,
|
||||
pub websocket: Option<WebsocketConfig>,
|
||||
}
|
||||
|
||||
fn default_heartbeat_timeout() -> u64 {
|
||||
DEFAULT_HEARTBEAT_TIMEOUT_SECS
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
|
||||
fn default_client_retry_interval() -> u64 {
|
||||
DEFAULT_CLIENT_RETRY_INTERVAL_SECS
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ClientConfig {
|
||||
pub remote_addr: String,
|
||||
|
@ -195,13 +206,15 @@ pub struct ClientConfig {
|
|||
pub transport: TransportConfig,
|
||||
#[serde(default = "default_heartbeat_timeout")]
|
||||
pub heartbeat_timeout: u64,
|
||||
#[serde(default = "default_client_retry_interval")]
|
||||
pub retry_interval: u64,
|
||||
}
|
||||
|
||||
fn default_heartbeat_interval() -> u64 {
|
||||
DEFAULT_HEARTBEAT_INTERVAL_SECS
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ServerConfig {
|
||||
pub bind_addr: String,
|
||||
|
@ -213,7 +226,7 @@ pub struct ServerConfig {
|
|||
pub heartbeat_interval: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
pub server: Option<ServerConfig>,
|
||||
|
@ -266,6 +279,9 @@ impl Config {
|
|||
bail!("The token of service {} is not set", name);
|
||||
}
|
||||
}
|
||||
if s.retry_interval.is_none() {
|
||||
s.retry_interval = Some(client.retry_interval);
|
||||
}
|
||||
}
|
||||
|
||||
Config::validate_transport_config(&client.transport, false)?;
|
||||
|
@ -296,11 +312,6 @@ impl Config {
|
|||
.as_ref()
|
||||
.and(tls_config.pkcs12_password.as_ref())
|
||||
.ok_or_else(|| anyhow!("Missing `pkcs12` or `pkcs12_password`"))?;
|
||||
} else {
|
||||
tls_config
|
||||
.trusted_root
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("Missing `trusted_root`"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -308,6 +319,7 @@ impl Config {
|
|||
// The check is done in transport
|
||||
Ok(())
|
||||
}
|
||||
TransportType::Websocket => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
|||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
|
@ -13,36 +14,30 @@ use tracing::{error, info, instrument};
|
|||
#[cfg(feature = "notify")]
|
||||
use notify::{EventKind, RecursiveMode, Watcher};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ConfigChange {
|
||||
General(Box<Config>), // Trigger a full restart
|
||||
ServiceChange(ServiceChange),
|
||||
ServerChange(ServerServiceChange),
|
||||
ClientChange(ClientServiceChange),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ServiceChange {
|
||||
ClientAdd(ClientServiceConfig),
|
||||
ClientDelete(String),
|
||||
ServerAdd(ServerServiceConfig),
|
||||
ServerDelete(String),
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ClientServiceChange {
|
||||
Add(ClientServiceConfig),
|
||||
Delete(String),
|
||||
}
|
||||
|
||||
impl From<ClientServiceConfig> for ServiceChange {
|
||||
fn from(c: ClientServiceConfig) -> Self {
|
||||
ServiceChange::ClientAdd(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerServiceConfig> for ServiceChange {
|
||||
fn from(c: ServerServiceConfig) -> Self {
|
||||
ServiceChange::ServerAdd(c)
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ServerServiceChange {
|
||||
Add(ServerServiceConfig),
|
||||
Delete(String),
|
||||
}
|
||||
|
||||
trait InstanceConfig: Clone {
|
||||
type ServiceConfig: Into<ServiceChange> + PartialEq + Clone;
|
||||
type ServiceConfig: PartialEq + Eq + Clone;
|
||||
fn equal_without_service(&self, rhs: &Self) -> bool;
|
||||
fn to_service_change_delete(s: String) -> ServiceChange;
|
||||
fn service_delete_change(s: String) -> ConfigChange;
|
||||
fn service_add_change(cfg: Self::ServiceConfig) -> ConfigChange;
|
||||
fn get_services(&self) -> &HashMap<String, Self::ServiceConfig>;
|
||||
}
|
||||
|
||||
|
@ -61,8 +56,11 @@ impl InstanceConfig for ServerConfig {
|
|||
|
||||
left == right
|
||||
}
|
||||
fn to_service_change_delete(s: String) -> ServiceChange {
|
||||
ServiceChange::ServerDelete(s)
|
||||
fn service_delete_change(s: String) -> ConfigChange {
|
||||
ConfigChange::ServerChange(ServerServiceChange::Delete(s))
|
||||
}
|
||||
fn service_add_change(cfg: Self::ServiceConfig) -> ConfigChange {
|
||||
ConfigChange::ServerChange(ServerServiceChange::Add(cfg))
|
||||
}
|
||||
fn get_services(&self) -> &HashMap<String, Self::ServiceConfig> {
|
||||
&self.services
|
||||
|
@ -84,8 +82,11 @@ impl InstanceConfig for ClientConfig {
|
|||
|
||||
left == right
|
||||
}
|
||||
fn to_service_change_delete(s: String) -> ServiceChange {
|
||||
ServiceChange::ClientDelete(s)
|
||||
fn service_delete_change(s: String) -> ConfigChange {
|
||||
ConfigChange::ClientChange(ClientServiceChange::Delete(s))
|
||||
}
|
||||
fn service_add_change(cfg: Self::ServiceConfig) -> ConfigChange {
|
||||
ConfigChange::ClientChange(ClientServiceChange::Add(cfg))
|
||||
}
|
||||
fn get_services(&self) -> &HashMap<String, Self::ServiceConfig> {
|
||||
&self.services
|
||||
|
@ -139,6 +140,11 @@ async fn config_watcher(
|
|||
mut old: Config,
|
||||
) -> Result<()> {
|
||||
let (fevent_tx, mut fevent_rx) = mpsc::unbounded_channel();
|
||||
let path = if path.is_absolute() {
|
||||
path
|
||||
} else {
|
||||
env::current_dir()?.join(path)
|
||||
};
|
||||
let parent_path = path.parent().expect("config file should have a parent dir");
|
||||
let path_clone = path.clone();
|
||||
let mut watcher =
|
||||
|
@ -174,8 +180,9 @@ async fn config_watcher(
|
|||
}
|
||||
};
|
||||
|
||||
for event in calculate_events(&old, &new) {
|
||||
event_tx.send(event)?;
|
||||
let events = calculate_events(&old, &new).into_iter().flatten();
|
||||
for event in events {
|
||||
event_tx.send(event)?;
|
||||
}
|
||||
|
||||
old = new;
|
||||
|
@ -192,42 +199,40 @@ async fn config_watcher(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_events(old: &Config, new: &Config) -> Vec<ConfigChange> {
|
||||
fn calculate_events(old: &Config, new: &Config) -> Option<Vec<ConfigChange>> {
|
||||
if old == new {
|
||||
return vec![];
|
||||
return None;
|
||||
}
|
||||
|
||||
if (old.server.is_some() != new.server.is_some())
|
||||
|| (old.client.is_some() != new.client.is_some())
|
||||
{
|
||||
return Some(vec![ConfigChange::General(Box::new(new.clone()))]);
|
||||
}
|
||||
|
||||
let mut ret = vec![];
|
||||
|
||||
if old.server != new.server {
|
||||
if old.server.is_some() != new.server.is_some() {
|
||||
return vec![ConfigChange::General(Box::new(new.clone()))];
|
||||
} else {
|
||||
match calculate_instance_config_events(
|
||||
old.server.as_ref().unwrap(),
|
||||
new.server.as_ref().unwrap(),
|
||||
) {
|
||||
Some(mut v) => ret.append(&mut v),
|
||||
None => return vec![ConfigChange::General(Box::new(new.clone()))],
|
||||
}
|
||||
match calculate_instance_config_events(
|
||||
old.server.as_ref().unwrap(),
|
||||
new.server.as_ref().unwrap(),
|
||||
) {
|
||||
Some(mut v) => ret.append(&mut v),
|
||||
None => return Some(vec![ConfigChange::General(Box::new(new.clone()))]),
|
||||
}
|
||||
}
|
||||
|
||||
if old.client != new.client {
|
||||
if old.client.is_some() != new.client.is_some() {
|
||||
return vec![ConfigChange::General(Box::new(new.clone()))];
|
||||
} else {
|
||||
match calculate_instance_config_events(
|
||||
old.client.as_ref().unwrap(),
|
||||
new.client.as_ref().unwrap(),
|
||||
) {
|
||||
Some(mut v) => ret.append(&mut v),
|
||||
None => return vec![ConfigChange::General(Box::new(new.clone()))],
|
||||
}
|
||||
match calculate_instance_config_events(
|
||||
old.client.as_ref().unwrap(),
|
||||
new.client.as_ref().unwrap(),
|
||||
) {
|
||||
Some(mut v) => ret.append(&mut v),
|
||||
None => return Some(vec![ConfigChange::General(Box::new(new.clone()))]),
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
// None indicates a General change needed
|
||||
|
@ -242,31 +247,17 @@ fn calculate_instance_config_events<T: InstanceConfig>(
|
|||
let old = old.get_services();
|
||||
let new = new.get_services();
|
||||
|
||||
let mut v = vec![];
|
||||
v.append(&mut calculate_service_delete_events::<T>(old, new));
|
||||
v.append(&mut calculate_service_add_events(old, new));
|
||||
|
||||
Some(v.into_iter().map(ConfigChange::ServiceChange).collect())
|
||||
}
|
||||
|
||||
fn calculate_service_delete_events<T: InstanceConfig>(
|
||||
old: &HashMap<String, T::ServiceConfig>,
|
||||
new: &HashMap<String, T::ServiceConfig>,
|
||||
) -> Vec<ServiceChange> {
|
||||
old.keys()
|
||||
let deletions = old
|
||||
.keys()
|
||||
.filter(|&name| new.get(name).is_none())
|
||||
.map(|x| T::to_service_change_delete(x.to_owned()))
|
||||
.collect()
|
||||
}
|
||||
.map(|x| T::service_delete_change(x.to_owned()));
|
||||
|
||||
fn calculate_service_add_events<T: PartialEq + Clone + Into<ServiceChange>>(
|
||||
old: &HashMap<String, T>,
|
||||
new: &HashMap<String, T>,
|
||||
) -> Vec<ServiceChange> {
|
||||
new.iter()
|
||||
let addition = new
|
||||
.iter()
|
||||
.filter(|(name, c)| old.get(*name) != Some(*c))
|
||||
.map(|(_, c)| c.clone().into())
|
||||
.collect()
|
||||
.map(|(_, c)| T::service_add_change(c.clone()));
|
||||
|
||||
Some(deletions.chain(addition).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -372,23 +363,23 @@ mod test {
|
|||
let mut expected = [
|
||||
vec![ConfigChange::General(Box::new(tests[0].new.clone()))],
|
||||
vec![ConfigChange::General(Box::new(tests[1].new.clone()))],
|
||||
vec![ConfigChange::ServiceChange(ServiceChange::ServerAdd(
|
||||
vec![ConfigChange::ServerChange(ServerServiceChange::Add(
|
||||
Default::default(),
|
||||
))],
|
||||
vec![ConfigChange::ServiceChange(ServiceChange::ServerDelete(
|
||||
vec![ConfigChange::ServerChange(ServerServiceChange::Delete(
|
||||
String::from("foo"),
|
||||
))],
|
||||
vec![
|
||||
ConfigChange::ServiceChange(ServiceChange::ServerDelete(String::from("foo1"))),
|
||||
ConfigChange::ServiceChange(ServiceChange::ServerAdd(
|
||||
ConfigChange::ServerChange(ServerServiceChange::Delete(String::from("foo1"))),
|
||||
ConfigChange::ServerChange(ServerServiceChange::Add(
|
||||
tests[4].new.server.as_ref().unwrap().services["bar1"].clone(),
|
||||
)),
|
||||
ConfigChange::ServiceChange(ServiceChange::ClientDelete(String::from("foo1"))),
|
||||
ConfigChange::ServiceChange(ServiceChange::ClientDelete(String::from("foo2"))),
|
||||
ConfigChange::ServiceChange(ServiceChange::ClientAdd(
|
||||
ConfigChange::ClientChange(ClientServiceChange::Delete(String::from("foo1"))),
|
||||
ConfigChange::ClientChange(ClientServiceChange::Delete(String::from("foo2"))),
|
||||
ConfigChange::ClientChange(ClientServiceChange::Add(
|
||||
tests[4].new.client.as_ref().unwrap().services["bar1"].clone(),
|
||||
)),
|
||||
ConfigChange::ServiceChange(ServiceChange::ClientAdd(
|
||||
ConfigChange::ClientChange(ClientServiceChange::Add(
|
||||
tests[4].new.client.as_ref().unwrap().services["bar2"].clone(),
|
||||
)),
|
||||
],
|
||||
|
@ -397,16 +388,18 @@ mod test {
|
|||
assert_eq!(tests.len(), expected.len());
|
||||
|
||||
for i in 0..tests.len() {
|
||||
let mut actual = calculate_events(&tests[i].old, &tests[i].new);
|
||||
let mut actual = calculate_events(&tests[i].old, &tests[i].new).unwrap();
|
||||
|
||||
let get_key = |x: &ConfigChange| -> String {
|
||||
match x {
|
||||
ConfigChange::General(_) => String::from("g"),
|
||||
ConfigChange::ServiceChange(sc) => match sc {
|
||||
ServiceChange::ClientAdd(c) => "c_add_".to_owned() + &c.name,
|
||||
ServiceChange::ClientDelete(s) => "c_del_".to_owned() + s,
|
||||
ServiceChange::ServerAdd(c) => "s_add_".to_owned() + &c.name,
|
||||
ServiceChange::ServerDelete(s) => "s_del_".to_owned() + s,
|
||||
ConfigChange::ServerChange(sc) => match sc {
|
||||
ServerServiceChange::Add(c) => "s_add_".to_owned() + &c.name,
|
||||
ServerServiceChange::Delete(s) => "s_del_".to_owned() + s,
|
||||
},
|
||||
ConfigChange::ClientChange(sc) => match sc {
|
||||
ClientServiceChange::Add(c) => "c_add_".to_owned() + &c.name,
|
||||
ClientServiceChange::Delete(s) => "c_del_".to_owned() + s,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -416,5 +409,20 @@ mod test {
|
|||
|
||||
assert_eq!(actual, expected[i]);
|
||||
}
|
||||
|
||||
// No changes
|
||||
assert_eq!(
|
||||
calculate_events(
|
||||
&Config {
|
||||
server: Default::default(),
|
||||
client: None,
|
||||
},
|
||||
&Config {
|
||||
server: Default::default(),
|
||||
client: None,
|
||||
},
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,12 @@ pub fn listen_backoff() -> ExponentialBackoff {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn run_control_chan_backoff() -> ExponentialBackoff {
|
||||
pub fn run_control_chan_backoff(interval: u64) -> ExponentialBackoff {
|
||||
ExponentialBackoff {
|
||||
randomization_factor: 0.1,
|
||||
randomization_factor: 0.2,
|
||||
max_elapsed_time: None,
|
||||
max_interval: Duration::from_secs(1),
|
||||
multiplier: 3.0,
|
||||
max_interval: Duration::from_secs(interval),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_http_proxy::{http_connect_tokio, http_connect_tokio_with_basic_auth};
|
||||
use backoff::{backoff::Backoff, Notify};
|
||||
use socket2::{SockRef, TcpKeepalive};
|
||||
use std::{future::Future, net::SocketAddr, time::Duration};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
use tokio::{
|
||||
net::{lookup_host, TcpStream, ToSocketAddrs, UdpSocket},
|
||||
sync::broadcast,
|
||||
|
@ -10,6 +11,8 @@ use tokio::{
|
|||
use tracing::trace;
|
||||
use url::Url;
|
||||
|
||||
use crate::transport::AddrMaybeCached;
|
||||
|
||||
// Tokio hesitates to expose this option...So we have to do it on our own :(
|
||||
// The good news is that using socket2 it can be easily done, without losing portability.
|
||||
// See https://github.com/tokio-rs/tokio/issues/3082
|
||||
|
@ -40,7 +43,15 @@ pub fn feature_not_compile(feature: &str) -> ! {
|
|||
)
|
||||
}
|
||||
|
||||
async fn to_socket_addr<A: ToSocketAddrs>(addr: A) -> Result<SocketAddr> {
|
||||
#[allow(dead_code)]
|
||||
pub fn feature_neither_compile(feature1: &str, feature2: &str) -> ! {
|
||||
panic!(
|
||||
"Neither of the feature '{}' or '{}' is compiled in this binary. Please re-compile rathole",
|
||||
feature1, feature2
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn to_socket_addr<A: ToSocketAddrs>(addr: A) -> Result<SocketAddr> {
|
||||
lookup_host(addr)
|
||||
.await?
|
||||
.next()
|
||||
|
@ -68,8 +79,12 @@ pub async fn udp_connect<A: ToSocketAddrs>(addr: A) -> Result<UdpSocket> {
|
|||
|
||||
/// Create a TcpStream using a proxy
|
||||
/// e.g. socks5://user:pass@127.0.0.1:1080 http://127.0.0.1:8080
|
||||
pub async fn tcp_connect_with_proxy(addr: &str, proxy: Option<&Url>) -> Result<TcpStream> {
|
||||
pub async fn tcp_connect_with_proxy(
|
||||
addr: &AddrMaybeCached,
|
||||
proxy: Option<&Url>,
|
||||
) -> Result<TcpStream> {
|
||||
if let Some(url) = proxy {
|
||||
let addr = &addr.addr;
|
||||
let mut s = TcpStream::connect((
|
||||
url.host_str().expect("proxy url should have host field"),
|
||||
url.port().expect("proxy url should have port field"),
|
||||
|
@ -108,7 +123,10 @@ pub async fn tcp_connect_with_proxy(addr: &str, proxy: Option<&Url>) -> Result<T
|
|||
}
|
||||
Ok(s)
|
||||
} else {
|
||||
Ok(TcpStream::connect(addr).await?)
|
||||
Ok(match addr.socket_addr {
|
||||
Some(s) => TcpStream::connect(s).await?,
|
||||
None => TcpStream::connect(&addr.addr).await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,3 +153,14 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write_and_flush<T>(conn: &mut T, data: &[u8]) -> Result<()>
|
||||
where
|
||||
T: AsyncWrite + Unpin,
|
||||
{
|
||||
conn.write_all(data)
|
||||
.await
|
||||
.with_context(|| "Failed to write data")?;
|
||||
conn.flush().await.with_context(|| "Failed to flush data")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
20
src/lib.rs
20
src/lib.rs
|
@ -10,7 +10,6 @@ mod transport;
|
|||
pub use cli::Cli;
|
||||
use cli::KeypairType;
|
||||
pub use config::Config;
|
||||
use config_watcher::ServiceChange;
|
||||
pub use constants::UDP_BUFFER_SIZE;
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -76,7 +75,7 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver<bool>) -> Result<()
|
|||
let (shutdown_tx, _) = broadcast::channel(1);
|
||||
|
||||
// (The join handle of the last instance, The service update channel sender)
|
||||
let mut last_instance: Option<(tokio::task::JoinHandle<_>, mpsc::Sender<ServiceChange>)> = None;
|
||||
let mut last_instance: Option<(tokio::task::JoinHandle<_>, mpsc::Sender<ConfigChange>)> = None;
|
||||
|
||||
while let Some(e) = cfg_watcher.event_rx.recv().await {
|
||||
match e {
|
||||
|
@ -84,7 +83,7 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver<bool>) -> Result<()
|
|||
if let Some((i, _)) = last_instance {
|
||||
info!("General configuration change detected. Restarting...");
|
||||
shutdown_tx.send(true)?;
|
||||
i.await?;
|
||||
i.await??;
|
||||
}
|
||||
|
||||
debug!("{:?}", config);
|
||||
|
@ -101,10 +100,10 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver<bool>) -> Result<()
|
|||
service_update_tx,
|
||||
));
|
||||
}
|
||||
ConfigChange::ServiceChange(service_event) => {
|
||||
info!("Service change detcted. {:?}", service_event);
|
||||
ev => {
|
||||
info!("Service change detected. {:?}", ev);
|
||||
if let Some((_, service_update_tx)) = &last_instance {
|
||||
let _ = service_update_tx.send(service_event).await;
|
||||
let _ = service_update_tx.send(ev).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,9 +118,9 @@ async fn run_instance(
|
|||
config: Config,
|
||||
args: Cli,
|
||||
shutdown_rx: broadcast::Receiver<bool>,
|
||||
service_update: mpsc::Receiver<ServiceChange>,
|
||||
) {
|
||||
let ret: Result<()> = match determine_run_mode(&config, &args) {
|
||||
service_update: mpsc::Receiver<ConfigChange>,
|
||||
) -> Result<()> {
|
||||
match determine_run_mode(&config, &args) {
|
||||
RunMode::Undetermine => panic!("Cannot determine running as a server or a client"),
|
||||
RunMode::Client => {
|
||||
#[cfg(not(feature = "client"))]
|
||||
|
@ -135,8 +134,7 @@ async fn run_instance(
|
|||
#[cfg(feature = "server")]
|
||||
run_server(config, shutdown_rx, service_update).await
|
||||
}
|
||||
};
|
||||
ret.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
|
|
|
@ -112,8 +112,7 @@ impl UdpTraffic {
|
|||
}
|
||||
|
||||
pub async fn read<T: AsyncRead + Unpin>(reader: &mut T, hdr_len: u8) -> Result<UdpTraffic> {
|
||||
let mut buf = Vec::new();
|
||||
buf.resize(hdr_len as usize, 0);
|
||||
let mut buf = vec![0; hdr_len as usize];
|
||||
reader
|
||||
.read_exact(&mut buf)
|
||||
.await
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::config::{Config, ServerConfig, ServerServiceConfig, ServiceType, TransportType};
|
||||
use crate::config_watcher::ServiceChange;
|
||||
use crate::config_watcher::{ConfigChange, ServerServiceChange};
|
||||
use crate::constants::{listen_backoff, UDP_BUFFER_SIZE};
|
||||
use crate::helper::retry_notify_with_deadline;
|
||||
use crate::helper::{retry_notify_with_deadline, write_and_flush};
|
||||
use crate::multi_map::MultiMap;
|
||||
use crate::protocol::Hello::{ControlChannelHello, DataChannelHello};
|
||||
use crate::protocol::{
|
||||
|
@ -25,8 +25,10 @@ use tracing::{debug, error, info, info_span, instrument, warn, Instrument, Span}
|
|||
|
||||
#[cfg(feature = "noise")]
|
||||
use crate::transport::NoiseTransport;
|
||||
#[cfg(feature = "tls")]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
use crate::transport::TlsTransport;
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
use crate::transport::WebsocketTransport;
|
||||
|
||||
type ServiceDigest = protocol::Digest; // SHA256 of a service name
|
||||
type Nonce = protocol::Digest; // Also called `session_key`
|
||||
|
@ -40,7 +42,7 @@ const HANDSHAKE_TIMEOUT: u64 = 5; // Timeout for transport handshake
|
|||
pub async fn run_server(
|
||||
config: Config,
|
||||
shutdown_rx: broadcast::Receiver<bool>,
|
||||
service_rx: mpsc::Receiver<ServiceChange>,
|
||||
update_rx: mpsc::Receiver<ConfigChange>,
|
||||
) -> Result<()> {
|
||||
let config = match config.server {
|
||||
Some(config) => config,
|
||||
|
@ -52,26 +54,35 @@ pub async fn run_server(
|
|||
match config.transport.transport_type {
|
||||
TransportType::Tcp => {
|
||||
let mut server = Server::<TcpTransport>::from(config).await?;
|
||||
server.run(shutdown_rx, service_rx).await?;
|
||||
server.run(shutdown_rx, update_rx).await?;
|
||||
}
|
||||
TransportType::Tls => {
|
||||
#[cfg(feature = "tls")]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
{
|
||||
let mut server = Server::<TlsTransport>::from(config).await?;
|
||||
server.run(shutdown_rx, service_rx).await?;
|
||||
server.run(shutdown_rx, update_rx).await?;
|
||||
}
|
||||
#[cfg(not(feature = "tls"))]
|
||||
crate::helper::feature_not_compile("tls")
|
||||
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
|
||||
crate::helper::feature_neither_compile("native-tls", "rustls")
|
||||
}
|
||||
TransportType::Noise => {
|
||||
#[cfg(feature = "noise")]
|
||||
{
|
||||
let mut server = Server::<NoiseTransport>::from(config).await?;
|
||||
server.run(shutdown_rx, service_rx).await?;
|
||||
server.run(shutdown_rx, update_rx).await?;
|
||||
}
|
||||
#[cfg(not(feature = "noise"))]
|
||||
crate::helper::feature_not_compile("noise")
|
||||
}
|
||||
TransportType::Websocket => {
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
{
|
||||
let mut server = Server::<WebsocketTransport>::from(config).await?;
|
||||
server.run(shutdown_rx, update_rx).await?;
|
||||
}
|
||||
#[cfg(not(any(feature = "websocket-native-tls", feature = "websocket-rustls")))]
|
||||
crate::helper::feature_neither_compile("websocket-native-tls", "websocket-rustls")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -124,7 +135,7 @@ impl<T: 'static + Transport> Server<T> {
|
|||
pub async fn run(
|
||||
&mut self,
|
||||
mut shutdown_rx: broadcast::Receiver<bool>,
|
||||
mut service_rx: mpsc::Receiver<ServiceChange>,
|
||||
mut update_rx: mpsc::Receiver<ConfigChange>,
|
||||
) -> Result<()> {
|
||||
// Listen at `server.bind_addr`
|
||||
let l = self
|
||||
|
@ -198,7 +209,7 @@ impl<T: 'static + Transport> Server<T> {
|
|||
info!("Shuting down gracefully...");
|
||||
break;
|
||||
},
|
||||
e = service_rx.recv() => {
|
||||
e = update_rx.recv() => {
|
||||
if let Some(e) = e {
|
||||
self.handle_hot_reload(e).await;
|
||||
}
|
||||
|
@ -211,24 +222,26 @@ impl<T: 'static + Transport> Server<T> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_hot_reload(&mut self, e: ServiceChange) {
|
||||
async fn handle_hot_reload(&mut self, e: ConfigChange) {
|
||||
match e {
|
||||
ServiceChange::ServerAdd(s) => {
|
||||
let hash = protocol::digest(s.name.as_bytes());
|
||||
let mut wg = self.services.write().await;
|
||||
let _ = wg.insert(hash, s);
|
||||
ConfigChange::ServerChange(server_change) => match server_change {
|
||||
ServerServiceChange::Add(cfg) => {
|
||||
let hash = protocol::digest(cfg.name.as_bytes());
|
||||
let mut wg = self.services.write().await;
|
||||
let _ = wg.insert(hash, cfg);
|
||||
|
||||
let mut wg = self.control_channels.write().await;
|
||||
let _ = wg.remove1(&hash);
|
||||
}
|
||||
ServiceChange::ServerDelete(s) => {
|
||||
let hash = protocol::digest(s.as_bytes());
|
||||
let _ = self.services.write().await.remove(&hash);
|
||||
let mut wg = self.control_channels.write().await;
|
||||
let _ = wg.remove1(&hash);
|
||||
}
|
||||
ServerServiceChange::Delete(s) => {
|
||||
let hash = protocol::digest(s.as_bytes());
|
||||
let _ = self.services.write().await.remove(&hash);
|
||||
|
||||
let mut wg = self.control_channels.write().await;
|
||||
let _ = wg.remove1(&hash);
|
||||
}
|
||||
_ => (),
|
||||
let mut wg = self.control_channels.write().await;
|
||||
let _ = wg.remove1(&hash);
|
||||
}
|
||||
},
|
||||
ignored => warn!("Ignored {:?} since running as a server", ignored),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,7 +303,7 @@ async fn do_control_channel_handshake<T: 'static + Transport>(
|
|||
None => {
|
||||
conn.write_all(&bincode::serialize(&Ack::ServiceNotExist).unwrap())
|
||||
.await?;
|
||||
bail!("No such a service {}", hex::encode(&service_digest));
|
||||
bail!("No such a service {}", hex::encode(service_digest));
|
||||
}
|
||||
}
|
||||
.to_owned();
|
||||
|
@ -485,14 +498,9 @@ struct ControlChannel<T: Transport> {
|
|||
|
||||
impl<T: Transport> ControlChannel<T> {
|
||||
async fn write_and_flush(&mut self, data: &[u8]) -> Result<()> {
|
||||
self.conn
|
||||
.write_all(data)
|
||||
write_and_flush(&mut self.conn, data)
|
||||
.await
|
||||
.with_context(|| "Failed to write control cmds")?;
|
||||
self.conn
|
||||
.flush()
|
||||
.await
|
||||
.with_context(|| "Failed to flush control cmds")?;
|
||||
Ok(())
|
||||
}
|
||||
// Run a control channel
|
||||
|
@ -627,7 +635,7 @@ async fn run_tcp_connection_pool<T: Transport>(
|
|||
'pool: while let Some(mut visitor) = visitor_rx.recv().await {
|
||||
loop {
|
||||
if let Some(mut ch) = data_ch_rx.recv().await {
|
||||
if ch.write_all(&cmd).await.is_ok() {
|
||||
if write_and_flush(&mut ch, &cmd).await.is_ok() {
|
||||
tokio::spawn(async move {
|
||||
let _ = copy_bidirectional(&mut ch, &mut visitor).await;
|
||||
});
|
||||
|
@ -677,7 +685,7 @@ async fn run_udp_connection_pool<T: Transport>(
|
|||
.recv()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("No available data channels"))?;
|
||||
conn.write_all(&cmd).await?;
|
||||
write_and_flush(&mut conn, &cmd).await?;
|
||||
|
||||
let mut buf = [0u8; UDP_BUFFER_SIZE];
|
||||
loop {
|
||||
|
|
|
@ -1,19 +1,53 @@
|
|||
use crate::config::{ClientServiceConfig, ServerServiceConfig, TcpConfig, TransportConfig};
|
||||
use crate::helper::try_set_tcp_keepalive;
|
||||
use crate::helper::{to_socket_addr, try_set_tcp_keepalive};
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::{TcpStream, ToSocketAddrs};
|
||||
use tracing::{error, trace};
|
||||
|
||||
pub const DEFAULT_NODELAY: bool = false;
|
||||
pub const DEFAULT_NODELAY: bool = true;
|
||||
|
||||
pub const DEFAULT_KEEPALIVE_SECS: u64 = 20;
|
||||
pub const DEFAULT_KEEPALIVE_INTERVAL: u64 = 8;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AddrMaybeCached {
|
||||
pub addr: String,
|
||||
pub socket_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl AddrMaybeCached {
|
||||
pub fn new(addr: &str) -> AddrMaybeCached {
|
||||
AddrMaybeCached {
|
||||
addr: addr.to_string(),
|
||||
socket_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve(&mut self) -> Result<()> {
|
||||
match to_socket_addr(&self.addr).await {
|
||||
Ok(s) => {
|
||||
self.socket_addr = Some(s);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AddrMaybeCached {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.socket_addr {
|
||||
Some(s) => f.write_fmt(format_args!("{}", s)),
|
||||
None => f.write_str(&self.addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify a transport layer, like TCP, TLS
|
||||
#[async_trait]
|
||||
pub trait Transport: Debug + Send + Sync {
|
||||
|
@ -30,21 +64,37 @@ pub trait Transport: Debug + Send + Sync {
|
|||
/// accept must be cancel safe
|
||||
async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)>;
|
||||
async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream>;
|
||||
async fn connect(&self, addr: &str) -> Result<Self::Stream>;
|
||||
async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream>;
|
||||
}
|
||||
|
||||
mod tcp;
|
||||
pub use tcp::TcpTransport;
|
||||
#[cfg(feature = "tls")]
|
||||
mod tls;
|
||||
#[cfg(feature = "tls")]
|
||||
pub use tls::TlsTransport;
|
||||
|
||||
#[cfg(all(feature = "native-tls", feature = "rustls"))]
|
||||
compile_error!("Only one of `native-tls` and `rustls` can be enabled");
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
mod native_tls;
|
||||
#[cfg(feature = "native-tls")]
|
||||
use native_tls as tls;
|
||||
#[cfg(feature = "rustls")]
|
||||
mod rustls;
|
||||
#[cfg(feature = "rustls")]
|
||||
use rustls as tls;
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
pub(crate) use tls::TlsTransport;
|
||||
|
||||
#[cfg(feature = "noise")]
|
||||
mod noise;
|
||||
#[cfg(feature = "noise")]
|
||||
pub use noise::NoiseTransport;
|
||||
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
mod websocket;
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
pub use websocket::WebsocketTransport;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Keepalive {
|
||||
// tcp_keepalive_time if the underlying protocol is TCP
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use super::{SocketOpts, TcpTransport, Transport};
|
||||
use crate::config::{TlsConfig, TransportConfig};
|
||||
use crate::helper::host_port_pair;
|
||||
use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||
use tokio_native_tls::native_tls::{self, Certificate, Identity};
|
||||
use tokio_native_tls::{TlsAcceptor, TlsConnector, TlsStream};
|
||||
pub(crate) use tokio_native_tls::TlsStream;
|
||||
use tokio_native_tls::{TlsAcceptor, TlsConnector};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TlsTransport {
|
||||
|
@ -26,7 +26,10 @@ impl Transport for TlsTransport {
|
|||
|
||||
fn new(config: &TransportConfig) -> Result<Self> {
|
||||
let tcp = TcpTransport::new(config)?;
|
||||
let config = config.tls.as_ref().ok_or_else(|| anyhow!("Missing tls config"))?;
|
||||
let config = config
|
||||
.tls
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("Missing tls config"))?;
|
||||
|
||||
let connector = match config.trusted_root.as_ref() {
|
||||
Some(path) => {
|
||||
|
@ -39,7 +42,11 @@ impl Transport for TlsTransport {
|
|||
.build()?;
|
||||
Some(TlsConnector::from(connector))
|
||||
}
|
||||
None => None,
|
||||
None => {
|
||||
// if no trusted_root is specified, allow TlsConnector to use system default
|
||||
let connector = native_tls::TlsConnector::builder().build()?;
|
||||
Some(TlsConnector::from(connector))
|
||||
}
|
||||
};
|
||||
|
||||
let tls_acceptor = match config.pkcs12.as_ref() {
|
||||
|
@ -87,7 +94,7 @@ impl Transport for TlsTransport {
|
|||
Ok(conn)
|
||||
}
|
||||
|
||||
async fn connect(&self, addr: &str) -> Result<Self::Stream> {
|
||||
async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {
|
||||
let conn = self.tcp.connect(addr).await?;
|
||||
|
||||
let connector = self.connector.as_ref().unwrap();
|
||||
|
@ -96,9 +103,14 @@ impl Transport for TlsTransport {
|
|||
self.config
|
||||
.hostname
|
||||
.as_deref()
|
||||
.unwrap_or(host_port_pair(addr)?.0),
|
||||
.unwrap_or(host_port_pair(&addr.addr)?.0),
|
||||
conn,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "websocket-native-tls")]
|
||||
pub(crate) fn get_tcpstream(s: &TlsStream<TcpStream>) -> &TcpStream {
|
||||
s.get_ref().get_ref().get_ref()
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use super::{SocketOpts, TcpTransport, Transport};
|
||||
use super::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
|
||||
use crate::config::{NoiseConfig, TransportConfig};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
|
@ -92,7 +92,7 @@ impl Transport for NoiseTransport {
|
|||
Ok(conn)
|
||||
}
|
||||
|
||||
async fn connect(&self, addr: &str) -> Result<Self::Stream> {
|
||||
async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {
|
||||
let conn = self
|
||||
.tcp
|
||||
.connect(addr)
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
use crate::config::{TlsConfig, TransportConfig};
|
||||
use crate::helper::host_port_pair;
|
||||
use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use p12::PFX;
|
||||
use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};
|
||||
pub(crate) use tokio_rustls::TlsStream;
|
||||
use tokio_rustls::{TlsAcceptor, TlsConnector};
|
||||
|
||||
pub struct TlsTransport {
|
||||
tcp: TcpTransport,
|
||||
config: TlsConfig,
|
||||
connector: Option<TlsConnector>,
|
||||
tls_acceptor: Option<TlsAcceptor>,
|
||||
}
|
||||
|
||||
// workaround for TlsConnector and TlsAcceptor not implementing Debug
|
||||
impl Debug for TlsTransport {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TlsTransport")
|
||||
.field("tcp", &self.tcp)
|
||||
.field("config", &self.config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn load_server_config(config: &TlsConfig) -> Result<Option<ServerConfig>> {
|
||||
if let Some(pkcs12_path) = config.pkcs12.as_ref() {
|
||||
let buf = fs::read(pkcs12_path)?;
|
||||
let pfx = PFX::parse(buf.as_slice())?;
|
||||
let pass = config.pkcs12_password.as_ref().unwrap();
|
||||
|
||||
let certs = pfx.cert_bags(pass)?;
|
||||
let keys = pfx.key_bags(pass)?;
|
||||
|
||||
let chain: Vec<CertificateDer> = certs.into_iter().map(CertificateDer::from).collect();
|
||||
let key = PrivatePkcs8KeyDer::from(keys.into_iter().next().unwrap());
|
||||
|
||||
Ok(Some(
|
||||
ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(chain, key.into())?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_client_config(config: &TlsConfig) -> Result<Option<ClientConfig>> {
|
||||
let cert = if let Some(path) = config.trusted_root.as_ref() {
|
||||
rustls_pemfile::certs(&mut std::io::BufReader::new(fs::File::open(path).unwrap()))
|
||||
.map(|cert| cert.unwrap())
|
||||
.next()
|
||||
.with_context(|| "Failed to read certificate")?
|
||||
} else {
|
||||
// read from native
|
||||
match rustls_native_certs::load_native_certs() {
|
||||
Ok(certs) => certs.into_iter().next().unwrap(),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load native certs: {}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut root_certs = RootCertStore::empty();
|
||||
root_certs.add(cert).unwrap();
|
||||
|
||||
Ok(Some(
|
||||
ClientConfig::builder()
|
||||
.with_root_certificates(root_certs)
|
||||
.with_no_client_auth(),
|
||||
))
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Transport for TlsTransport {
|
||||
type Acceptor = TcpListener;
|
||||
type RawStream = TcpStream;
|
||||
type Stream = TlsStream<TcpStream>;
|
||||
|
||||
fn new(config: &TransportConfig) -> Result<Self> {
|
||||
let tcp = TcpTransport::new(config)?;
|
||||
let config = config
|
||||
.tls
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("Missing tls config"))?;
|
||||
|
||||
let connector = load_client_config(config)
|
||||
.unwrap()
|
||||
.map(|c| Arc::new(c).into());
|
||||
let tls_acceptor = load_server_config(config)
|
||||
.unwrap()
|
||||
.map(|c| Arc::new(c).into());
|
||||
|
||||
Ok(TlsTransport {
|
||||
tcp,
|
||||
config: config.clone(),
|
||||
connector,
|
||||
tls_acceptor,
|
||||
})
|
||||
}
|
||||
|
||||
fn hint(conn: &Self::Stream, opt: SocketOpts) {
|
||||
opt.apply(conn.get_ref().0);
|
||||
}
|
||||
|
||||
async fn bind<A: ToSocketAddrs + Send + Sync>(&self, addr: A) -> Result<Self::Acceptor> {
|
||||
let l = TcpListener::bind(addr)
|
||||
.await
|
||||
.with_context(|| "Failed to create tcp listener")?;
|
||||
Ok(l)
|
||||
}
|
||||
|
||||
async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> {
|
||||
self.tcp
|
||||
.accept(a)
|
||||
.await
|
||||
.with_context(|| "Failed to accept TCP connection")
|
||||
}
|
||||
|
||||
async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream> {
|
||||
let conn = self.tls_acceptor.as_ref().unwrap().accept(conn).await?;
|
||||
Ok(tokio_rustls::TlsStream::Server(conn))
|
||||
}
|
||||
|
||||
async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {
|
||||
let conn = self.tcp.connect(addr).await?;
|
||||
|
||||
let connector = self.connector.as_ref().unwrap();
|
||||
|
||||
let host_name = self
|
||||
.config
|
||||
.hostname
|
||||
.as_deref()
|
||||
.unwrap_or(host_port_pair(&addr.addr)?.0);
|
||||
|
||||
Ok(tokio_rustls::TlsStream::Client(
|
||||
connector
|
||||
.connect(ServerName::try_from(host_name)?.to_owned(), conn)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_tcpstream(s: &TlsStream<TcpStream>) -> &TcpStream {
|
||||
&s.get_ref().0
|
||||
}
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
helper::tcp_connect_with_proxy,
|
||||
};
|
||||
|
||||
use super::{SocketOpts, Transport};
|
||||
use super::{AddrMaybeCached, SocketOpts, Transport};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::net::SocketAddr;
|
||||
|
@ -46,7 +46,7 @@ impl Transport for TcpTransport {
|
|||
Ok(conn)
|
||||
}
|
||||
|
||||
async fn connect(&self, addr: &str) -> Result<Self::Stream> {
|
||||
async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {
|
||||
let s = tcp_connect_with_proxy(addr, self.cfg.proxy.as_ref()).await?;
|
||||
self.socket_opts.apply(&s);
|
||||
Ok(s)
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
use core::result::Result;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
use super::{AddrMaybeCached, SocketOpts, TcpTransport, TlsTransport, Transport};
|
||||
use crate::config::TransportConfig;
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_sink::Sink;
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
use super::tls::get_tcpstream;
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
use super::tls::TlsStream;
|
||||
|
||||
use tokio_tungstenite::tungstenite::protocol::{Message, WebSocketConfig};
|
||||
use tokio_tungstenite::{accept_async_with_config, client_async_with_config, WebSocketStream};
|
||||
use tokio_util::io::StreamReader;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TransportStream {
|
||||
Insecure(TcpStream),
|
||||
Secure(TlsStream<TcpStream>),
|
||||
}
|
||||
|
||||
impl TransportStream {
|
||||
fn get_tcpstream(&self) -> &TcpStream {
|
||||
match self {
|
||||
TransportStream::Insecure(s) => s,
|
||||
TransportStream::Secure(s) => get_tcpstream(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for TransportStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
TransportStream::Insecure(s) => Pin::new(s).poll_read(cx, buf),
|
||||
TransportStream::Secure(s) => Pin::new(s).poll_read(cx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for TransportStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
match self.get_mut() {
|
||||
TransportStream::Insecure(s) => Pin::new(s).poll_write(cx, buf),
|
||||
TransportStream::Secure(s) => Pin::new(s).poll_write(cx, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
|
||||
match self.get_mut() {
|
||||
TransportStream::Insecure(s) => Pin::new(s).poll_flush(cx),
|
||||
TransportStream::Secure(s) => Pin::new(s).poll_flush(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
match self.get_mut() {
|
||||
TransportStream::Insecure(s) => Pin::new(s).poll_shutdown(cx),
|
||||
TransportStream::Secure(s) => Pin::new(s).poll_shutdown(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct StreamWrapper {
|
||||
inner: WebSocketStream<TransportStream>,
|
||||
}
|
||||
|
||||
impl Stream for StreamWrapper {
|
||||
type Item = Result<Bytes, Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match Pin::new(&mut self.get_mut().inner).poll_next(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
Poll::Ready(Some(Err(Error::new(ErrorKind::Other, err))))
|
||||
}
|
||||
Poll::Ready(Some(Ok(res))) => {
|
||||
if let Message::Binary(b) = res {
|
||||
Poll::Ready(Some(Ok(Bytes::from(b))))
|
||||
} else {
|
||||
Poll::Ready(Some(Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"unexpected frame",
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebsocketTunnel {
|
||||
inner: StreamReader<StreamWrapper, Bytes>,
|
||||
}
|
||||
|
||||
impl AsyncRead for WebsocketTunnel {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncBufRead for WebsocketTunnel {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<&[u8]>> {
|
||||
Pin::new(&mut self.get_mut().inner).poll_fill_buf(cx)
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
Pin::new(&mut self.get_mut().inner).consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for WebsocketTunnel {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
let sw = self.get_mut().inner.get_mut();
|
||||
ready!(Pin::new(&mut sw.inner)
|
||||
.poll_ready(cx)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err)))?;
|
||||
|
||||
match Pin::new(&mut sw.inner).start_send(Message::Binary(buf.to_vec())) {
|
||||
Ok(()) => Poll::Ready(Ok(buf.len())),
|
||||
Err(e) => Poll::Ready(Err(Error::new(ErrorKind::Other, e))),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Pin::new(&mut self.get_mut().inner.get_mut().inner)
|
||||
.poll_flush(cx)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Pin::new(&mut self.get_mut().inner.get_mut().inner)
|
||||
.poll_close(cx)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SubTransport {
|
||||
Secure(TlsTransport),
|
||||
Insecure(TcpTransport),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebsocketTransport {
|
||||
sub: SubTransport,
|
||||
conf: WebSocketConfig,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Transport for WebsocketTransport {
|
||||
type Acceptor = TcpListener;
|
||||
type RawStream = TcpStream;
|
||||
type Stream = WebsocketTunnel;
|
||||
|
||||
fn new(config: &TransportConfig) -> anyhow::Result<Self> {
|
||||
let wsconfig = config
|
||||
.websocket
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("Missing websocket config"))?;
|
||||
|
||||
let conf = WebSocketConfig {
|
||||
write_buffer_size: 0,
|
||||
..WebSocketConfig::default()
|
||||
};
|
||||
let sub = match wsconfig.tls {
|
||||
true => SubTransport::Secure(TlsTransport::new(config)?),
|
||||
false => SubTransport::Insecure(TcpTransport::new(config)?),
|
||||
};
|
||||
Ok(WebsocketTransport { sub, conf })
|
||||
}
|
||||
|
||||
fn hint(conn: &Self::Stream, opt: SocketOpts) {
|
||||
opt.apply(conn.inner.get_ref().inner.get_ref().get_tcpstream())
|
||||
}
|
||||
|
||||
async fn bind<A: ToSocketAddrs + Send + Sync>(
|
||||
&self,
|
||||
addr: A,
|
||||
) -> anyhow::Result<Self::Acceptor> {
|
||||
TcpListener::bind(addr).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn accept(&self, a: &Self::Acceptor) -> anyhow::Result<(Self::RawStream, SocketAddr)> {
|
||||
let (s, addr) = match &self.sub {
|
||||
SubTransport::Insecure(t) => t.accept(a).await?,
|
||||
SubTransport::Secure(t) => t.accept(a).await?,
|
||||
};
|
||||
Ok((s, addr))
|
||||
}
|
||||
|
||||
async fn handshake(&self, conn: Self::RawStream) -> anyhow::Result<Self::Stream> {
|
||||
let tsream = match &self.sub {
|
||||
SubTransport::Insecure(t) => TransportStream::Insecure(t.handshake(conn).await?),
|
||||
SubTransport::Secure(t) => TransportStream::Secure(t.handshake(conn).await?),
|
||||
};
|
||||
let wsstream = accept_async_with_config(tsream, Some(self.conf)).await?;
|
||||
let tun = WebsocketTunnel {
|
||||
inner: StreamReader::new(StreamWrapper { inner: wsstream }),
|
||||
};
|
||||
Ok(tun)
|
||||
}
|
||||
|
||||
async fn connect(&self, addr: &AddrMaybeCached) -> anyhow::Result<Self::Stream> {
|
||||
let u = format!("ws://{}", &addr.addr.as_str());
|
||||
let url = Url::parse(&u).unwrap();
|
||||
let tstream = match &self.sub {
|
||||
SubTransport::Insecure(t) => TransportStream::Insecure(t.connect(addr).await?),
|
||||
SubTransport::Secure(t) => TransportStream::Secure(t.connect(addr).await?),
|
||||
};
|
||||
let (wsstream, _) = client_async_with_config(url, tstream, Some(self.conf))
|
||||
.await
|
||||
.expect("failed to connect");
|
||||
let tun = WebsocketTunnel {
|
||||
inner: StreamReader::new(StreamWrapper { inner: wsstream }),
|
||||
};
|
||||
Ok(tun)
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@ default_token = "default_token_if_not_specify"
|
|||
[client.transport]
|
||||
type = "tls"
|
||||
[client.transport.tls]
|
||||
trusted_root = "examples/tls/ca-cert.pem"
|
||||
hostname = "0.0.0.0"
|
||||
trusted_root = "examples/tls/rootCA.crt"
|
||||
hostname = "localhost"
|
||||
|
||||
[client.services.echo]
|
||||
local_addr = "127.0.0.1:8080"
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
[client]
|
||||
remote_addr = "127.0.0.1:2333"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[client.transport]
|
||||
type = "websocket"
|
||||
[client.transport.tls]
|
||||
trusted_root = "examples/tls/rootCA.crt"
|
||||
hostname = "localhost"
|
||||
[client.transport.websocket]
|
||||
tls = true
|
||||
|
||||
[client.services.echo]
|
||||
local_addr = "127.0.0.1:8080"
|
||||
[client.services.pingpong]
|
||||
local_addr = "127.0.0.1:8081"
|
||||
|
||||
[server]
|
||||
bind_addr = "0.0.0.0:2333"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[server.transport]
|
||||
type = "websocket"
|
||||
[server.transport.tls]
|
||||
pkcs12 = "examples/tls/identity.pfx"
|
||||
pkcs12_password = "1234"
|
||||
[server.transport.websocket]
|
||||
tls = true
|
||||
|
||||
[server.services.echo]
|
||||
bind_addr = "0.0.0.0:2334"
|
||||
[server.services.pingpong]
|
||||
bind_addr = "0.0.0.0:2335"
|
|
@ -0,0 +1,27 @@
|
|||
[client]
|
||||
remote_addr = "127.0.0.1:2333"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[client.transport]
|
||||
type = "websocket"
|
||||
[client.transport.websocket]
|
||||
tls = false
|
||||
|
||||
[client.services.echo]
|
||||
local_addr = "127.0.0.1:8080"
|
||||
[client.services.pingpong]
|
||||
local_addr = "127.0.0.1:8081"
|
||||
|
||||
[server]
|
||||
bind_addr = "0.0.0.0:2333"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[server.transport]
|
||||
type = "websocket"
|
||||
[server.transport.websocket]
|
||||
tls = false
|
||||
|
||||
[server.services.echo]
|
||||
bind_addr = "0.0.0.0:2334"
|
||||
[server.services.pingpong]
|
||||
bind_addr = "0.0.0.0:2335"
|
|
@ -5,8 +5,8 @@ default_token = "default_token_if_not_specify"
|
|||
[client.transport]
|
||||
type = "tls"
|
||||
[client.transport.tls]
|
||||
trusted_root = "examples/tls/ca-cert.pem"
|
||||
hostname = "0.0.0.0"
|
||||
trusted_root = "examples/tls/rootCA.crt"
|
||||
hostname = "localhost"
|
||||
|
||||
[client.services.echo]
|
||||
type = "udp"
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
[client]
|
||||
remote_addr = "127.0.0.1:2332"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[client.transport]
|
||||
type = "websocket"
|
||||
[client.transport.tls]
|
||||
trusted_root = "examples/tls/rootCA.crt"
|
||||
hostname = "localhost"
|
||||
[client.transport.websocket]
|
||||
tls = true
|
||||
|
||||
[client.services.echo]
|
||||
type = "udp"
|
||||
local_addr = "127.0.0.1:8080"
|
||||
[client.services.pingpong]
|
||||
type = "udp"
|
||||
local_addr = "127.0.0.1:8081"
|
||||
|
||||
[server]
|
||||
bind_addr = "0.0.0.0:2332"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[server.transport]
|
||||
type = "websocket"
|
||||
[server.transport.tls]
|
||||
pkcs12 = "examples/tls/identity.pfx"
|
||||
pkcs12_password = "1234"
|
||||
[server.transport.websocket]
|
||||
tls = true
|
||||
|
||||
[server.services.echo]
|
||||
type = "udp"
|
||||
bind_addr = "0.0.0.0:2334"
|
||||
[server.services.pingpong]
|
||||
type = "udp"
|
||||
bind_addr = "0.0.0.0:2335"
|
|
@ -0,0 +1,31 @@
|
|||
[client]
|
||||
remote_addr = "127.0.0.1:2332"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[client.transport]
|
||||
type = "websocket"
|
||||
[client.transport.websocket]
|
||||
tls = false
|
||||
|
||||
[client.services.echo]
|
||||
type = "udp"
|
||||
local_addr = "127.0.0.1:8080"
|
||||
[client.services.pingpong]
|
||||
type = "udp"
|
||||
local_addr = "127.0.0.1:8081"
|
||||
|
||||
[server]
|
||||
bind_addr = "0.0.0.0:2332"
|
||||
default_token = "default_token_if_not_specify"
|
||||
|
||||
[server.transport]
|
||||
type = "websocket"
|
||||
[server.transport.websocket]
|
||||
tls = false
|
||||
|
||||
[server.services.echo]
|
||||
type = "udp"
|
||||
bind_addr = "0.0.0.0:2334"
|
||||
[server.services.pingpong]
|
||||
type = "udp"
|
||||
bind_addr = "0.0.0.0:2335"
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{Ok, Result};
|
||||
use common::{run_rathole_client, PING, PONG};
|
||||
use rand::Rng;
|
||||
use std::time::Duration;
|
||||
|
@ -57,9 +57,19 @@ async fn tcp() -> Result<()> {
|
|||
test("tests/for_tcp/tcp_transport.toml", Type::Tcp).await?;
|
||||
// FIXME: Self-signed certificate on Mac requires mannual interference. Disable CI for now
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
test("tests/for_tcp/tls_transport.toml", Type::Tcp).await?;
|
||||
|
||||
#[cfg(feature = "noise")]
|
||||
test("tests/for_tcp/noise_transport.toml", Type::Tcp).await?;
|
||||
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
test("tests/for_tcp/websocket_transport.toml", Type::Tcp).await?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
test("tests/for_tcp/websocket_tls_transport.toml", Type::Tcp).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -84,14 +94,29 @@ async fn udp() -> Result<()> {
|
|||
test("tests/for_udp/tcp_transport.toml", Type::Udp).await?;
|
||||
// See above
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls"))]
|
||||
test("tests/for_udp/tls_transport.toml", Type::Udp).await?;
|
||||
|
||||
#[cfg(feature = "noise")]
|
||||
test("tests/for_udp/noise_transport.toml", Type::Udp).await?;
|
||||
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
test("tests/for_udp/websocket_transport.toml", Type::Udp).await?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
|
||||
test("tests/for_udp/websocket_tls_transport.toml", Type::Udp).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn test(config_path: &'static str, t: Type) -> Result<()> {
|
||||
if cfg!(not(all(feature = "client", feature = "server"))) {
|
||||
// Skip the test if the client or the server is not enabled
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (client_shutdown_tx, client_shutdown_rx) = broadcast::channel(1);
|
||||
let (server_shutdown_tx, server_shutdown_rx) = broadcast::channel(1);
|
||||
|
||||
|
|
Loading…
Reference in New Issue