tobira 0.3.2

A VMess relay written in Rust.
# TOBIRA (とびら / 扉) - Transparent Offload By Inspect-free Relay Agent

A VMess relay written in Rust.

TOBIRA accepts plain VMess+TCP inbound traffic, validates only the VMess Auth ID, and relays encrypted streams to upstream nodes without full payload inspection/decryption.

## Features

- πŸ”„ **Subscription Aggregation**: Fetch VMess subscriptions from multiple HTTP(S) sources
- 🧩 **Dual VMess Parsing**: Supports both `vmess://base64(json)` (v2rayN) and `vmess://uuid@host:port?...` URL format
- πŸ› οΈ **Processing Pipeline**: Filter/remove/rename/remove-emoji/override-security by node name and source
- πŸ” **Basic Auth for Subscriptions**: Optional HTTP Basic Auth with per-user output restrictions
- 🌐 **Multiple Output Views**: Expose multiple `/sub/<name>` outputs with independent host/port rewrite + pipeline
- ⚑ **Inspect-free Relay Path**: Route by Auth ID and forward bytes directly (TCP Fast Open on inbound/outbound TCP)
- πŸ“‘ **TCP + gRPC Upstream**: Relay to classic VMess TCP upstreams and VMess over TLS+gRPC upstreams (connection pool + stream reuse)
- ♻️ **Hot Reload**: Full reload by `SIGUSR1` or config-file change; periodic subscription-only refresh by timer
- πŸ’Ύ **Cache Fallback**: Per-source cache fallback when subscription fetch fails
- βœ… **Safety Checks**: Duplicate UUID detection during routing table build

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Subscription Sources     β”‚
β”‚ (HTTP/HTTPS)             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ fetch + parse VMess
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Process Pipeline         β”‚
β”‚ β€’ filter/source filter   β”‚
β”‚ β€’ remove/rename          β”‚
β”‚ β€’ remove_emoji/security  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ build nodes + cache
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Validator (UUID/Auth ID) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ route by first 16 bytes
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TOBIRA Relay Inbound         │─────▢│ Upstream Relay            β”‚
β”‚ β€’ VMess + TCP                β”‚      β”‚ β€’ TCP (TFO)               β”‚
β”‚ β€’ inspect-free forwarding    β”‚      β”‚ β€’ TLS + gRPC (pooled H2)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ HTTP Subscription Server β”‚
β”‚ /sub /sub/<name> ...     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Installation

### Build from Source

```bash
cd tobira
cargo build --release
```

Binary path:

```bash
./target/release/tobira
```

### Docker

```bash
cd tobira
docker build -t tobira:latest .
```

## Configuration

Create `config.toml` (see [config.example.toml](config.example.toml)).

### Configuration Sections

#### `relay`

- `listen`: relay bind address (default: `[::]`)
- `port`: relay inbound port (required)

Note: relay listener bind fields are **not** re-bound by hot reload. Changing `relay.listen`/`relay.port` requires process restart.

#### `http`

- `listen`: HTTP bind address (default: `[::]`)
- `port`: HTTP port (default: `8080`)
- `[[http.users]]`: Basic Auth users
  - `username`, `password`
  - optional `outputs = ["main", ...]` to restrict visible outputs
- `[[http.outputs]]`: named subscription outputs
  - `name`: output name for `/sub/<name>`
  - `host`, `port`: rewritten relay endpoint written into exported links
  - `sni`: optional TLS SNI for gRPC/TLS relay links
  - `skip-cert-verify`: optional TLS certificate verification skip for gRPC/TLS relay links
  - `[[http.outputs.process]]`: per-output processing pipeline

#### `subscription`

- `cache_file`: JSON cache path used as source fallback
- `update_interval`: periodic subscription refresh interval in seconds (`0` disables timer)
- `[[subscription.sources]]`: source definitions
  - `name`: source name
  - `url`: source subscription URL
  - `user_agent`: optional HTTP User-Agent
  - `[[subscription.sources.process]]`: per-source processing pipeline

#### Process Pipeline Fields

Each process step supports:

- `filter`: regex list matched against node name
- `filter_source`: regex list matched against source name
- `invert`: invert selection result
- `remove`: remove selected nodes
- `rename`: regex rename rules (`[[pattern, replacement], ...]`)
- `remove_emoji`: remove emoji characters from node name
- `override_security`: override VMess encryption/security field

## Usage

### Start the Service

```bash
./target/release/tobira --config config.toml
```

Short form:

```bash
./target/release/tobira -c config.toml
```

### Reload Behavior

- Full reload (re-read config + refetch subscriptions):
  - `SIGUSR1`
  - config file modify/create event
- Subscription-only reload (uses in-memory config):
  - periodic timer controlled by `subscription.update_interval`

Manual full reload:

```bash
kill -USR1 <pid>
```

`RUST_LOG` overrides `log_level` in `config.toml`.

## Subscription Endpoints

Supported endpoints:

- `GET /sub` and `GET /sub/base64`: all allowed outputs, VMess JSON link format
- `GET /sub/v2rayn`: alias of `/sub`, all allowed outputs, VMess JSON link format
- `GET /sub/standard`: all allowed outputs, VMess URL link format (`/sub/url` kept as compatibility alias)
- `GET /sub/<name>` and `GET /sub/<name>/base64`: one named output, VMess JSON link format
- `GET /sub/<name>/v2rayn`: alias of `/sub/<name>`, one named output, VMess JSON link format
- `GET /sub/<name>/standard`: one named output, VMess URL link format (`/sub/<name>/url` kept as compatibility alias)

Authentication:

- If `[[http.users]]` is configured, Basic Auth is required.
- If no users are configured, anonymous access is allowed.

Examples:

```bash
# JSON-format links (base64 envelope)
curl -u alice:s3cr3t http://127.0.0.1:8080/sub/main | base64 -d

# URL-format links (base64 envelope)
curl -u alice:s3cr3t http://127.0.0.1:8080/sub/main/standard | base64 -d
```

## Process Pipeline Examples

### Remove Free Source Nodes

```toml
[[subscription.sources.process]]
filter_source = ["free_sub"]
remove = true
```

### Keep Only Premium Nodes

```toml
[[http.outputs.process]]
filter = ["(?i)premium"]
invert = true
remove = true
```

### Normalize Names and Security

```toml
[[http.outputs.process]]
remove_emoji = true
rename = [["^US\\s*", "US-"], ["^HK\\s*", "HK-"]]
override_security = "aes-128-gcm"
```

## Troubleshooting

### `duplicate UUID` on startup/reload

TOBIRA requires each upstream node UUID to be unique across all loaded sources. Remove duplicates or filter them out in process pipeline steps.

### `401 Unauthorized` on `/sub/*`

Check your Basic Auth credentials, or remove `[[http.users]]` entries to allow anonymous access.

## License

This repository is licensed under [GNU Affero General Public License v3.0 or later](./LICENSE).

SPDX-License-Identifier: [AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later.html)