Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fa6867056 | ||
|
|
54ea6efdd0 | ||
|
|
27ac32a901 | ||
|
|
829f53c123 | ||
|
|
43eae6127d | ||
|
|
a03212c8cc | ||
|
|
2613969a7c | ||
|
|
be1b2db867 | ||
|
|
8fbee8701b | ||
|
|
952d160870 | ||
|
|
91ae6becde | ||
|
|
e1f576e4fe | ||
|
|
a7556cabdc | ||
|
|
b2e8d16bb1 | ||
|
|
d95e762812 | ||
|
|
384f927fc3 | ||
|
|
1b7c09ae18 |
4
.github/workflows/rust.yml
vendored
4
.github/workflows/rust.yml
vendored
@@ -10,8 +10,8 @@ env:
|
|||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build:
|
||||||
name: Build & Test
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
155
README.md
155
README.md
@@ -5,13 +5,21 @@
|
|||||||
# GOTO
|
# GOTO
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Quick Start Guide](#quick-start-guide)
|
- [Quick Start Guide](#quick-start-guide)
|
||||||
- [Build](#build)
|
|
||||||
- [How to use?](#how-to-use)
|
- [How to use?](#how-to-use)
|
||||||
- [Systemd Method](#telemt-via-systemd)
|
- [Systemd Method](#telemt-via-systemd)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Minimal Configuration](#minimal-configuration-for-first-start)
|
||||||
|
- [Advanced](#advanced)
|
||||||
|
- [Adtag](#adtag)
|
||||||
|
- [Listening and Announce IPs](#listening-and-announce-ips)
|
||||||
|
- [Upstream Manager](#upstream-manager)
|
||||||
|
- [IP](#bind-on-ip)
|
||||||
|
- [SOCKS](#socks45-as-upstream)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
- [Telegram Calls](#telegram-calls-via-mtproxy)
|
- [Telegram Calls](#telegram-calls-via-mtproxy)
|
||||||
- [DPI](#how-does-dpi-see-mtproxy-tls)
|
- [DPI](#how-does-dpi-see-mtproxy-tls)
|
||||||
- [Whitelist on Network Level](#whitelist-on-ip)
|
- [Whitelist on Network Level](#whitelist-on-ip)
|
||||||
|
- [Build](#build)
|
||||||
- [Why Rust?](#why-rust)
|
- [Why Rust?](#why-rust)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -27,25 +35,27 @@
|
|||||||
- Extensive logging via `trace` and `debug` with `RUST_LOG` method
|
- Extensive logging via `trace` and `debug` with `RUST_LOG` method
|
||||||
|
|
||||||
## Quick Start Guide
|
## Quick Start Guide
|
||||||
|
**This software is designed for Debian-based OS: in addition to Debian, these are Ubuntu, Mint, Kali, MX and many other Linux**
|
||||||
### Build
|
1. Download release
|
||||||
```bash
|
```bash
|
||||||
# Cloning repo
|
wget https://github.com/telemt/telemt/releases/latest/download/telemt
|
||||||
git clone https://github.com/telemt/telemt
|
|
||||||
# Changing Directory to telemt
|
|
||||||
cd telemt
|
|
||||||
# Starting Release Build
|
|
||||||
cargo build --release
|
|
||||||
# Move to /bin
|
|
||||||
mv ./target/release/telemt /bin
|
|
||||||
# Make executable
|
|
||||||
chmod +x /bin/telemt
|
|
||||||
# Lets go!
|
|
||||||
telemt config.toml
|
|
||||||
```
|
```
|
||||||
|
2. Move to Bin Folder
|
||||||
|
```bash
|
||||||
|
mv telemt /bin
|
||||||
|
```
|
||||||
|
4. Make Executable
|
||||||
|
```bash
|
||||||
|
chmod +x /bin/telemt
|
||||||
|
```
|
||||||
|
5. Go to [How to use?](#how-to-use) section for for further steps
|
||||||
|
|
||||||
## How to use?
|
## How to use?
|
||||||
### Telemt via Systemd
|
### Telemt via Systemd
|
||||||
|
**This instruction "assume" that you:**
|
||||||
|
- logged in as root or executed `su -` / `sudo su`
|
||||||
|
- you already have an assembled and executable `telemt` in /bin folder as a result of the [Quick Start Guide](#quick-start-guide) or [Build](#build)
|
||||||
|
|
||||||
**0. Check port and generate secrets**
|
**0. Check port and generate secrets**
|
||||||
|
|
||||||
The port you have selected for use should be MISSING from the list, when:
|
The port you have selected for use should be MISSING from the list, when:
|
||||||
@@ -72,28 +82,8 @@ Open nano
|
|||||||
```bash
|
```bash
|
||||||
nano /etc/telemt.toml
|
nano /etc/telemt.toml
|
||||||
```
|
```
|
||||||
```bash
|
paste your config from [Configuration](#configuration) section
|
||||||
port = 443 # Listening port
|
|
||||||
|
|
||||||
[users]
|
|
||||||
hello = "00000000000000000000000000000000" # Replace the secret with one generated before
|
|
||||||
|
|
||||||
[modes]
|
|
||||||
classic = false # Plain obfuscated mode
|
|
||||||
secure = false # dd-prefix mode
|
|
||||||
tls = true # Fake TLS - ee-prefix
|
|
||||||
|
|
||||||
tls_domain = "petrovich.ru" # Domain for ee-secret and masking
|
|
||||||
mask = true # Enable masking of bad traffic
|
|
||||||
mask_host = "petrovich.ru" # Optional override for mask destination
|
|
||||||
mask_port = 443 # Port for masking
|
|
||||||
|
|
||||||
prefer_ipv6 = false # Try IPv6 DCs first if true
|
|
||||||
fast_mode = true # Use "fast" obfuscation variant
|
|
||||||
|
|
||||||
client_keepalive = 600 # Seconds
|
|
||||||
client_ack_timeout = 300 # Seconds
|
|
||||||
```
|
|
||||||
then Ctrl+X -> Y -> Enter to save
|
then Ctrl+X -> Y -> Enter to save
|
||||||
|
|
||||||
**2. Create service on /etc/systemd/system/telemt.service**
|
**2. Create service on /etc/systemd/system/telemt.service**
|
||||||
@@ -125,9 +115,79 @@ then Ctrl+X -> Y -> Enter to save
|
|||||||
|
|
||||||
**5.** In Shell type `systemctl enable telemt` - then telemt will start with system startup, after the network is up
|
**5.** In Shell type `systemctl enable telemt` - then telemt will start with system startup, after the network is up
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
### Minimal Configuration for First Start
|
||||||
|
```toml
|
||||||
|
port = 443 # Listening port
|
||||||
|
show_links = ["tele", "hello"] # Specify users, for whom will be displayed the links
|
||||||
|
|
||||||
|
[users]
|
||||||
|
tele = "00000000000000000000000000000000" # Replace the secret with one generated before
|
||||||
|
hello = "00000000000000000000000000000000" # Replace the secret with one generated before
|
||||||
|
|
||||||
|
[modes]
|
||||||
|
classic = false # Plain obfuscated mode
|
||||||
|
secure = false # dd-prefix mode
|
||||||
|
tls = true # Fake TLS - ee-prefix
|
||||||
|
|
||||||
|
tls_domain = "petrovich.ru" # Domain for ee-secret and masking
|
||||||
|
mask = true # Enable masking of bad traffic
|
||||||
|
mask_host = "petrovich.ru" # Optional override for mask destination
|
||||||
|
mask_port = 443 # Port for masking
|
||||||
|
|
||||||
|
prefer_ipv6 = false # Try IPv6 DCs first if true
|
||||||
|
fast_mode = true # Use "fast" obfuscation variant
|
||||||
|
|
||||||
|
client_keepalive = 600 # Seconds
|
||||||
|
client_ack_timeout = 300 # Seconds
|
||||||
|
```
|
||||||
|
### Advanced
|
||||||
|
#### Adtag
|
||||||
|
To use channel advertising and usage statistics from Telegram, get Adtag from [@mtproxybot](https://t.me/mtproxybot), add this parameter to the end of config.toml and specify it
|
||||||
|
```toml
|
||||||
|
ad_tag = "00000000000000000000000000000000" # Replace zeros to your adtag from @mtproxybot
|
||||||
|
```
|
||||||
|
#### Listening and Announce IPs
|
||||||
|
To specify listening address and/or address in links, add to the end of config.toml:
|
||||||
|
```toml
|
||||||
|
[[listeners]]
|
||||||
|
ip = "0.0.0.0" # 0.0.0.0 = all IPs; your IP = specific listening
|
||||||
|
announce_ip = "1.2.3.4" # IP in links; comment with # if not used
|
||||||
|
```
|
||||||
|
#### Upstream Manager
|
||||||
|
To specify upstream, add to the end of config.toml:
|
||||||
|
##### Bind on IP
|
||||||
|
```toml
|
||||||
|
[[upstreams]]
|
||||||
|
type = "direct"
|
||||||
|
weight = 1
|
||||||
|
enabled = true
|
||||||
|
interface = "192.168.1.100" # Change to your outgoing IP
|
||||||
|
```
|
||||||
|
##### SOCKS4/5 as Upstream
|
||||||
|
- Without Auth:
|
||||||
|
```toml
|
||||||
|
[[upstreams]]
|
||||||
|
type = "socks5" # Specify SOCKS4 or SOCKS5
|
||||||
|
address = "1.2.3.4:1234" # SOCKS-server Address
|
||||||
|
weight = 1 # Set Weight for Scenarios
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
||||||
|
- With Auth:
|
||||||
|
```toml
|
||||||
|
[[upstreams]]
|
||||||
|
type = "socks5" # Specify SOCKS4 or SOCKS5
|
||||||
|
address = "1.2.3.4:1234" # SOCKS-server Address
|
||||||
|
username = "user" # Username for Auth on SOCKS-server
|
||||||
|
password = "pass" # Password for Auth on SOCKS-server
|
||||||
|
weight = 1 # Set Weight for Scenarios
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
### Telegram Calls via MTProxy
|
### Telegram Calls via MTProxy
|
||||||
- Telegram architecture does **NOT allow calls via MTProxy**, but only via SOCKS5, which cannot be obfuscated
|
- Telegram architecture **does NOT allow calls via MTProxy**, but only via SOCKS5, which cannot be obfuscated
|
||||||
### How does DPI see MTProxy TLS?
|
### How does DPI see MTProxy TLS?
|
||||||
- DPI sees MTProxy in Fake TLS (ee) mode as TLS 1.3
|
- DPI sees MTProxy in Fake TLS (ee) mode as TLS 1.3
|
||||||
- the SNI you specify sends both the client and the server;
|
- the SNI you specify sends both the client and the server;
|
||||||
@@ -147,6 +207,23 @@ then Ctrl+X -> Y -> Enter to save
|
|||||||
- in Russia on mobile networks, less in wired networks
|
- in Russia on mobile networks, less in wired networks
|
||||||
- in Iran during "activity"
|
- in Iran during "activity"
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
```bash
|
||||||
|
# Cloning repo
|
||||||
|
git clone https://github.com/telemt/telemt
|
||||||
|
# Changing Directory to telemt
|
||||||
|
cd telemt
|
||||||
|
# Starting Release Build
|
||||||
|
cargo build --release
|
||||||
|
# Move to /bin
|
||||||
|
mv ./target/release/telemt /bin
|
||||||
|
# Make executable
|
||||||
|
chmod +x /bin/telemt
|
||||||
|
# Lets go!
|
||||||
|
telemt config.toml
|
||||||
|
```
|
||||||
|
|
||||||
## Why Rust?
|
## Why Rust?
|
||||||
- Long-running reliability and idempotent behavior
|
- Long-running reliability and idempotent behavior
|
||||||
- Rust’s deterministic resource management - RAII
|
- Rust’s deterministic resource management - RAII
|
||||||
@@ -154,6 +231,10 @@ then Ctrl+X -> Y -> Enter to save
|
|||||||
- Memory safety and reduced attack surface
|
- Memory safety and reduced attack surface
|
||||||
- Tokio's asynchronous architecture
|
- Tokio's asynchronous architecture
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
- ✅ [SOCKS5 as Upstream](https://github.com/telemt/telemt/issues/1) -> added Upstream Management
|
||||||
|
- ⌛ [iOS - Media Upload Hanging-in-Loop](https://github.com/telemt/telemt/issues/2)
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- Public IP in links
|
- Public IP in links
|
||||||
- Config Reload-on-fly
|
- Config Reload-on-fly
|
||||||
|
|||||||
@@ -163,9 +163,12 @@ fn default_port() -> u16 { 443 }
|
|||||||
fn default_tls_domain() -> String { "www.google.com".to_string() }
|
fn default_tls_domain() -> String { "www.google.com".to_string() }
|
||||||
fn default_mask_port() -> u16 { 443 }
|
fn default_mask_port() -> u16 { 443 }
|
||||||
fn default_replay_check_len() -> usize { 65536 }
|
fn default_replay_check_len() -> usize { 65536 }
|
||||||
fn default_handshake_timeout() -> u64 { 10 }
|
// CHANGED: Increased handshake timeout for bad mobile networks
|
||||||
|
fn default_handshake_timeout() -> u64 { 15 }
|
||||||
fn default_connect_timeout() -> u64 { 10 }
|
fn default_connect_timeout() -> u64 { 10 }
|
||||||
fn default_keepalive() -> u64 { 600 }
|
// CHANGED: Reduced keepalive from 600s to 60s.
|
||||||
|
// Mobile NATs often drop idle connections after 60-120s.
|
||||||
|
fn default_keepalive() -> u64 { 60 }
|
||||||
fn default_ack_timeout() -> u64 { 300 }
|
fn default_ack_timeout() -> u64 { 300 }
|
||||||
fn default_listen_addr() -> String { "0.0.0.0".to_string() }
|
fn default_listen_addr() -> String { "0.0.0.0".to_string() }
|
||||||
fn default_fake_cert_len() -> usize { 2048 }
|
fn default_fake_cert_len() -> usize { 2048 }
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@@ -20,7 +20,7 @@ mod util;
|
|||||||
|
|
||||||
use crate::config::ProxyConfig;
|
use crate::config::ProxyConfig;
|
||||||
use crate::proxy::ClientHandler;
|
use crate::proxy::ClientHandler;
|
||||||
use crate::stats::Stats;
|
use crate::stats::{Stats, ReplayChecker};
|
||||||
use crate::transport::{create_listener, ListenOptions, UpstreamManager};
|
use crate::transport::{create_listener, ListenOptions, UpstreamManager};
|
||||||
use crate::util::ip::detect_ip;
|
use crate::util::ip::detect_ip;
|
||||||
|
|
||||||
@@ -55,6 +55,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
let stats = Arc::new(Stats::new());
|
let stats = Arc::new(Stats::new());
|
||||||
|
|
||||||
|
// CHANGED: Initialize global ReplayChecker here instead of per-connection
|
||||||
|
let replay_checker = Arc::new(ReplayChecker::new(config.replay_check_len));
|
||||||
|
|
||||||
// Initialize Upstream Manager
|
// Initialize Upstream Manager
|
||||||
let upstream_manager = Arc::new(UpstreamManager::new(config.upstreams.clone()));
|
let upstream_manager = Arc::new(UpstreamManager::new(config.upstreams.clone()));
|
||||||
|
|
||||||
@@ -145,13 +148,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Accept loop
|
// Accept loop
|
||||||
// For simplicity in this slice, we just spawn a task for each listener
|
|
||||||
// In a real high-perf scenario, we might want a more complex accept loop
|
|
||||||
|
|
||||||
for listener in listeners {
|
for listener in listeners {
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
let stats = stats.clone();
|
let stats = stats.clone();
|
||||||
let upstream_manager = upstream_manager.clone();
|
let upstream_manager = upstream_manager.clone();
|
||||||
|
let replay_checker = replay_checker.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
@@ -160,6 +161,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
let stats = stats.clone();
|
let stats = stats.clone();
|
||||||
let upstream_manager = upstream_manager.clone();
|
let upstream_manager = upstream_manager.clone();
|
||||||
|
let replay_checker = replay_checker.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = ClientHandler::new(
|
if let Err(e) = ClientHandler::new(
|
||||||
@@ -167,7 +169,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
peer_addr,
|
peer_addr,
|
||||||
config,
|
config,
|
||||||
stats,
|
stats,
|
||||||
upstream_manager
|
upstream_manager,
|
||||||
|
replay_checker // Pass global checker
|
||||||
).run().await {
|
).run().await {
|
||||||
// Log only relevant errors
|
// Log only relevant errors
|
||||||
// debug!("Connection error: {}", e);
|
// debug!("Connection error: {}", e);
|
||||||
|
|||||||
@@ -167,7 +167,10 @@ pub const DEFAULT_ACK_TIMEOUT_SECS: u64 = 300;
|
|||||||
// ============= Buffer Sizes =============
|
// ============= Buffer Sizes =============
|
||||||
|
|
||||||
/// Default buffer size
|
/// Default buffer size
|
||||||
pub const DEFAULT_BUFFER_SIZE: usize = 65536;
|
/// CHANGED: Reduced from 64KB to 16KB to match TLS record size and align with
|
||||||
|
/// the new buffering strategy for better iOS upload performance.
|
||||||
|
pub const DEFAULT_BUFFER_SIZE: usize = 16384;
|
||||||
|
|
||||||
/// Small buffer size for bad client handling
|
/// Small buffer size for bad client handling
|
||||||
pub const SMALL_BUFFER_SIZE: usize = 8192;
|
pub const SMALL_BUFFER_SIZE: usize = 8192;
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,10 @@ impl ClientHandler {
|
|||||||
config: Arc<ProxyConfig>,
|
config: Arc<ProxyConfig>,
|
||||||
stats: Arc<Stats>,
|
stats: Arc<Stats>,
|
||||||
upstream_manager: Arc<UpstreamManager>,
|
upstream_manager: Arc<UpstreamManager>,
|
||||||
|
replay_checker: Arc<ReplayChecker>, // CHANGED: Accept global checker
|
||||||
) -> RunningClientHandler {
|
) -> RunningClientHandler {
|
||||||
// Note: ReplayChecker should be shared globally for proper replay protection
|
// CHANGED: Removed local creation of ReplayChecker.
|
||||||
// Creating it per-connection disables replay protection across connections
|
// It is now passed from main.rs to ensure global replay protection.
|
||||||
// TODO: Pass Arc<ReplayChecker> from main.rs
|
|
||||||
let replay_checker = Arc::new(ReplayChecker::new(config.replay_check_len));
|
|
||||||
|
|
||||||
RunningClientHandler {
|
RunningClientHandler {
|
||||||
stream,
|
stream,
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
//! Bidirectional Relay
|
//! Bidirectional Relay
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt};
|
||||||
use tracing::{debug, trace, warn};
|
use tokio::time::Instant;
|
||||||
|
use tracing::{debug, trace, warn, info};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::stats::Stats;
|
use crate::stats::Stats;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 65536;
|
// CHANGED: Reduced from 128KB to 16KB to match TLS record size and prevent bufferbloat.
|
||||||
|
// This is critical for iOS clients to maintain proper TCP flow control during uploads.
|
||||||
|
const BUFFER_SIZE: usize = 16384;
|
||||||
|
|
||||||
|
// Activity timeout for iOS compatibility (30 minutes)
|
||||||
|
// iOS does not support TCP_USER_TIMEOUT, so we implement application-level timeout
|
||||||
|
const ACTIVITY_TIMEOUT_SECS: u64 = 1800;
|
||||||
|
|
||||||
/// Relay data bidirectionally between client and server
|
/// Relay data bidirectionally between client and server
|
||||||
pub async fn relay_bidirectional<CR, CW, SR, SW>(
|
pub async fn relay_bidirectional<CR, CW, SR, SW>(
|
||||||
@@ -36,15 +44,40 @@ where
|
|||||||
let c2s_bytes_clone = Arc::clone(&c2s_bytes);
|
let c2s_bytes_clone = Arc::clone(&c2s_bytes);
|
||||||
let s2c_bytes_clone = Arc::clone(&s2c_bytes);
|
let s2c_bytes_clone = Arc::clone(&s2c_bytes);
|
||||||
|
|
||||||
// Client -> Server task
|
// Activity timeout for iOS compatibility
|
||||||
|
let activity_timeout = Duration::from_secs(ACTIVITY_TIMEOUT_SECS);
|
||||||
|
|
||||||
|
// Client -> Server task with activity timeout
|
||||||
let c2s = tokio::spawn(async move {
|
let c2s = tokio::spawn(async move {
|
||||||
let mut buf = vec![0u8; BUFFER_SIZE];
|
let mut buf = vec![0u8; BUFFER_SIZE];
|
||||||
let mut total_bytes = 0u64;
|
let mut total_bytes = 0u64;
|
||||||
let mut msg_count = 0u64;
|
let mut msg_count = 0u64;
|
||||||
|
let mut last_activity = Instant::now();
|
||||||
|
let mut last_log = Instant::now();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match client_reader.read(&mut buf).await {
|
// Read with timeout to prevent infinite hang on iOS
|
||||||
Ok(0) => {
|
let read_result = tokio::time::timeout(
|
||||||
|
activity_timeout,
|
||||||
|
client_reader.read(&mut buf)
|
||||||
|
).await;
|
||||||
|
|
||||||
|
match read_result {
|
||||||
|
// Timeout - no activity for too long
|
||||||
|
Err(_) => {
|
||||||
|
warn!(
|
||||||
|
user = %user_c2s,
|
||||||
|
total_bytes = total_bytes,
|
||||||
|
msgs = msg_count,
|
||||||
|
idle_secs = last_activity.elapsed().as_secs(),
|
||||||
|
"Activity timeout (C->S) - no data received"
|
||||||
|
);
|
||||||
|
let _ = server_writer.shutdown().await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read successful
|
||||||
|
Ok(Ok(0)) => {
|
||||||
debug!(
|
debug!(
|
||||||
user = %user_c2s,
|
user = %user_c2s,
|
||||||
total_bytes = total_bytes,
|
total_bytes = total_bytes,
|
||||||
@@ -54,9 +87,11 @@ where
|
|||||||
let _ = server_writer.shutdown().await;
|
let _ = server_writer.shutdown().await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ok(n) => {
|
|
||||||
|
Ok(Ok(n)) => {
|
||||||
total_bytes += n as u64;
|
total_bytes += n as u64;
|
||||||
msg_count += 1;
|
msg_count += 1;
|
||||||
|
last_activity = Instant::now();
|
||||||
c2s_bytes_clone.store(total_bytes, Ordering::Relaxed);
|
c2s_bytes_clone.store(total_bytes, Ordering::Relaxed);
|
||||||
|
|
||||||
stats_c2s.add_user_octets_from(&user_c2s, n as u64);
|
stats_c2s.add_user_octets_from(&user_c2s, n as u64);
|
||||||
@@ -70,6 +105,19 @@ where
|
|||||||
"C->S data"
|
"C->S data"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Log activity every 10 seconds for large transfers
|
||||||
|
if last_log.elapsed() > Duration::from_secs(10) {
|
||||||
|
let rate = total_bytes as f64 / last_log.elapsed().as_secs_f64();
|
||||||
|
info!(
|
||||||
|
user = %user_c2s,
|
||||||
|
total_bytes = total_bytes,
|
||||||
|
msgs = msg_count,
|
||||||
|
rate_kbps = (rate / 1024.0) as u64,
|
||||||
|
"C->S transfer in progress"
|
||||||
|
);
|
||||||
|
last_log = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = server_writer.write_all(&buf[..n]).await {
|
if let Err(e) = server_writer.write_all(&buf[..n]).await {
|
||||||
debug!(user = %user_c2s, error = %e, "Failed to write to server");
|
debug!(user = %user_c2s, error = %e, "Failed to write to server");
|
||||||
break;
|
break;
|
||||||
@@ -79,7 +127,8 @@ where
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
|
Ok(Err(e)) => {
|
||||||
debug!(user = %user_c2s, error = %e, total_bytes = total_bytes, "Client read error");
|
debug!(user = %user_c2s, error = %e, total_bytes = total_bytes, "Client read error");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -87,15 +136,37 @@ where
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Server -> Client task
|
// Server -> Client task with activity timeout
|
||||||
let s2c = tokio::spawn(async move {
|
let s2c = tokio::spawn(async move {
|
||||||
let mut buf = vec![0u8; BUFFER_SIZE];
|
let mut buf = vec![0u8; BUFFER_SIZE];
|
||||||
let mut total_bytes = 0u64;
|
let mut total_bytes = 0u64;
|
||||||
let mut msg_count = 0u64;
|
let mut msg_count = 0u64;
|
||||||
|
let mut last_activity = Instant::now();
|
||||||
|
let mut last_log = Instant::now();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match server_reader.read(&mut buf).await {
|
// Read with timeout to prevent infinite hang on iOS
|
||||||
Ok(0) => {
|
let read_result = tokio::time::timeout(
|
||||||
|
activity_timeout,
|
||||||
|
server_reader.read(&mut buf)
|
||||||
|
).await;
|
||||||
|
|
||||||
|
match read_result {
|
||||||
|
// Timeout - no activity for too long
|
||||||
|
Err(_) => {
|
||||||
|
warn!(
|
||||||
|
user = %user_s2c,
|
||||||
|
total_bytes = total_bytes,
|
||||||
|
msgs = msg_count,
|
||||||
|
idle_secs = last_activity.elapsed().as_secs(),
|
||||||
|
"Activity timeout (S->C) - no data received"
|
||||||
|
);
|
||||||
|
let _ = client_writer.shutdown().await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read successful
|
||||||
|
Ok(Ok(0)) => {
|
||||||
debug!(
|
debug!(
|
||||||
user = %user_s2c,
|
user = %user_s2c,
|
||||||
total_bytes = total_bytes,
|
total_bytes = total_bytes,
|
||||||
@@ -105,9 +176,11 @@ where
|
|||||||
let _ = client_writer.shutdown().await;
|
let _ = client_writer.shutdown().await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ok(n) => {
|
|
||||||
|
Ok(Ok(n)) => {
|
||||||
total_bytes += n as u64;
|
total_bytes += n as u64;
|
||||||
msg_count += 1;
|
msg_count += 1;
|
||||||
|
last_activity = Instant::now();
|
||||||
s2c_bytes_clone.store(total_bytes, Ordering::Relaxed);
|
s2c_bytes_clone.store(total_bytes, Ordering::Relaxed);
|
||||||
|
|
||||||
stats_s2c.add_user_octets_to(&user_s2c, n as u64);
|
stats_s2c.add_user_octets_to(&user_s2c, n as u64);
|
||||||
@@ -121,6 +194,19 @@ where
|
|||||||
"S->C data"
|
"S->C data"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Log activity every 10 seconds for large transfers
|
||||||
|
if last_log.elapsed() > Duration::from_secs(10) {
|
||||||
|
let rate = total_bytes as f64 / last_log.elapsed().as_secs_f64();
|
||||||
|
info!(
|
||||||
|
user = %user_s2c,
|
||||||
|
total_bytes = total_bytes,
|
||||||
|
msgs = msg_count,
|
||||||
|
rate_kbps = (rate / 1024.0) as u64,
|
||||||
|
"S->C transfer in progress"
|
||||||
|
);
|
||||||
|
last_log = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = client_writer.write_all(&buf[..n]).await {
|
if let Err(e) = client_writer.write_all(&buf[..n]).await {
|
||||||
debug!(user = %user_s2c, error = %e, "Failed to write to client");
|
debug!(user = %user_s2c, error = %e, "Failed to write to client");
|
||||||
break;
|
break;
|
||||||
@@ -130,7 +216,8 @@ where
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
|
Ok(Err(e)) => {
|
||||||
debug!(user = %user_s2c, error = %e, total_bytes = total_bytes, "Server read error");
|
debug!(user = %user_s2c, error = %e, total_bytes = total_bytes, "Server read error");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
// ============= Configuration =============
|
// ============= Configuration =============
|
||||||
|
|
||||||
/// Default buffer size (64KB - good for MTProto)
|
/// Default buffer size
|
||||||
pub const DEFAULT_BUFFER_SIZE: usize = 64 * 1024;
|
/// CHANGED: Reduced from 64KB to 16KB to match TLS record size and prevent bufferbloat.
|
||||||
|
pub const DEFAULT_BUFFER_SIZE: usize = 16 * 1024;
|
||||||
|
|
||||||
/// Default maximum number of pooled buffers
|
/// Default maximum number of pooled buffers
|
||||||
pub const DEFAULT_MAX_BUFFERS: usize = 1024;
|
pub const DEFAULT_MAX_BUFFERS: usize = 1024;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -30,20 +30,13 @@ pub fn configure_tcp_socket(
|
|||||||
socket.set_tcp_keepalive(&keepalive)?;
|
socket.set_tcp_keepalive(&keepalive)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set buffer sizes
|
// CHANGED: Removed manual buffer size setting (was 256KB).
|
||||||
set_buffer_sizes(&socket, 65536, 65536)?;
|
// Allowing the OS kernel to handle TCP window scaling (Autotuning) is critical
|
||||||
|
// for mobile clients to avoid bufferbloat and stalled connections during uploads.
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set socket buffer sizes
|
|
||||||
fn set_buffer_sizes(socket: &socket2::SockRef, recv: usize, send: usize) -> Result<()> {
|
|
||||||
// These may fail on some systems, so we ignore errors
|
|
||||||
let _ = socket.set_recv_buffer_size(recv);
|
|
||||||
let _ = socket.set_send_buffer_size(send);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure socket for accepting client connections
|
/// Configure socket for accepting client connections
|
||||||
pub fn configure_client_socket(
|
pub fn configure_client_socket(
|
||||||
stream: &TcpStream,
|
stream: &TcpStream,
|
||||||
@@ -65,6 +58,8 @@ pub fn configure_client_socket(
|
|||||||
socket.set_tcp_keepalive(&keepalive)?;
|
socket.set_tcp_keepalive(&keepalive)?;
|
||||||
|
|
||||||
// Set TCP user timeout (Linux only)
|
// Set TCP user timeout (Linux only)
|
||||||
|
// NOTE: iOS does not support TCP_USER_TIMEOUT - application-level timeout
|
||||||
|
// is implemented in relay_bidirectional instead
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|||||||
Reference in New Issue
Block a user