mirror of https://github.com/telemt/telemt.git
Add per-user ad_tag with global fallback and hot-reload
- Per-user ad_tag in [access.user_ad_tags], global fallback in general.ad_tag - User tag overrides global; if no user tag, general.ad_tag is used - Both general.ad_tag and user_ad_tags support hot-reload (no restart)
This commit is contained in:
parent
338636ede6
commit
bc432f06e2
|
|
@ -2087,7 +2087,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "telemt"
|
name = "telemt"
|
||||||
version = "3.0.13"
|
version = "3.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -215,10 +215,12 @@ hello = "00000000000000000000000000000000"
|
||||||
|
|
||||||
```
|
```
|
||||||
### Advanced
|
### Advanced
|
||||||
#### Adtag
|
#### Adtag (per-user)
|
||||||
To use channel advertising and usage statistics from Telegram, get Adtag from [@mtproxybot](https://t.me/mtproxybot), add this parameter to section `[General]`
|
To use channel advertising and usage statistics from Telegram, get an Adtag from [@mtproxybot](https://t.me/mtproxybot). Set it per user in `[access.user_ad_tags]` (32 hex chars):
|
||||||
```toml
|
```toml
|
||||||
ad_tag = "00000000000000000000000000000000" # Replace zeros to your adtag from @mtproxybot
|
[access.user_ad_tags]
|
||||||
|
username1 = "11111111111111111111111111111111" # Replace with your tag from @mtproxybot
|
||||||
|
username2 = "22222222222222222222222222222222"
|
||||||
```
|
```
|
||||||
#### Listening and Announce IPs
|
#### Listening and Announce IPs
|
||||||
To specify listening address and/or address in links, add to section `[[server.listeners]]` of config.toml:
|
To specify listening address and/or address in links, add to section `[[server.listeners]]` of config.toml:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
# === General Settings ===
|
# === General Settings ===
|
||||||
[general]
|
[general]
|
||||||
use_middle_proxy = false
|
use_middle_proxy = false
|
||||||
|
# Global ad_tag fallback when user has no per-user tag in [access.user_ad_tags]
|
||||||
# ad_tag = "00000000000000000000000000000000"
|
# ad_tag = "00000000000000000000000000000000"
|
||||||
|
# Per-user ad_tag in [access.user_ad_tags] (32 hex from @MTProxybot)
|
||||||
|
|
||||||
# === Log Level ===
|
# === Log Level ===
|
||||||
# Log level: debug | verbose | normal | silent
|
# Log level: debug | verbose | normal | silent
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,22 @@
|
||||||
//!
|
//!
|
||||||
//! # What can be reloaded without restart
|
//! # What can be reloaded without restart
|
||||||
//!
|
//!
|
||||||
//! | Section | Field | Effect |
|
//! | Section | Field | Effect |
|
||||||
//! |-----------|-------------------------------|-----------------------------------|
|
//! |-----------|--------------------------------|------------------------------------------------|
|
||||||
//! | `general` | `log_level` | Filter updated via `log_level_tx` |
|
//! | `general` | `log_level` | Filter updated via `log_level_tx` |
|
||||||
//! | `general` | `ad_tag` | Passed on next connection |
|
//! | `access` | `user_ad_tags` | Passed on next connection |
|
||||||
//! | `general` | `middle_proxy_pool_size` | Passed on next connection |
|
//! | `general` | `ad_tag` | Passed on next connection (fallback per-user) |
|
||||||
//! | `general` | `me_keepalive_*` | Passed on next connection |
|
//! | `general` | `middle_proxy_pool_size` | Passed on next connection |
|
||||||
//! | `general` | `desync_all_full` | Applied immediately |
|
//! | `general` | `me_keepalive_*` | Passed on next connection |
|
||||||
//! | `general` | `update_every` | Applied to ME updater immediately |
|
//! | `general` | `desync_all_full` | Applied immediately |
|
||||||
//! | `general` | `hardswap` | Applied on next ME map update |
|
//! | `general` | `update_every` | Applied to ME updater immediately |
|
||||||
//! | `general` | `me_pool_drain_ttl_secs` | Applied on next ME map update |
|
//! | `general` | `hardswap` | Applied on next ME map update |
|
||||||
//! | `general` | `me_pool_min_fresh_ratio` | Applied on next ME map update |
|
//! | `general` | `me_pool_drain_ttl_secs` | Applied on next ME map update |
|
||||||
//! | `general` | `me_reinit_drain_timeout_secs`| Applied on next ME map update |
|
//! | `general` | `me_pool_min_fresh_ratio` | Applied on next ME map update |
|
||||||
//! | `general` | `telemetry` / `me_*_policy` | Applied immediately |
|
//! | `general` | `me_reinit_drain_timeout_secs` | Applied on next ME map update |
|
||||||
//! | `network` | `dns_overrides` | Applied immediately |
|
//! | `general` | `telemetry` / `me_*_policy` | Applied immediately |
|
||||||
//! | `access` | All user/quota fields | Effective immediately |
|
//! | `network` | `dns_overrides` | Applied immediately |
|
||||||
|
//! | `access` | All user/quota fields | Effective immediately |
|
||||||
//!
|
//!
|
||||||
//! Fields that require re-binding sockets (`server.port`, `censorship.*`,
|
//! Fields that require re-binding sockets (`server.port`, `censorship.*`,
|
||||||
//! `network.*`, `use_middle_proxy`) are **not** applied; a warning is emitted.
|
//! `network.*`, `use_middle_proxy`) are **not** applied; a warning is emitted.
|
||||||
|
|
@ -207,14 +208,17 @@ fn log_changes(
|
||||||
log_tx.send(new_hot.log_level.clone()).ok();
|
log_tx.send(new_hot.log_level.clone()).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
if old_hot.ad_tag != new_hot.ad_tag {
|
if old_hot.access.user_ad_tags != new_hot.access.user_ad_tags {
|
||||||
info!(
|
info!(
|
||||||
"config reload: ad_tag: {} → {}",
|
"config reload: user_ad_tags updated ({} entries)",
|
||||||
old_hot.ad_tag.as_deref().unwrap_or("none"),
|
new_hot.access.user_ad_tags.len(),
|
||||||
new_hot.ad_tag.as_deref().unwrap_or("none"),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if old_hot.ad_tag != new_hot.ad_tag {
|
||||||
|
info!("config reload: general.ad_tag updated (applied on next connection)");
|
||||||
|
}
|
||||||
|
|
||||||
if old_hot.dns_overrides != new_hot.dns_overrides {
|
if old_hot.dns_overrides != new_hot.dns_overrides {
|
||||||
info!(
|
info!(
|
||||||
"config reload: network.dns_overrides updated ({} entries)",
|
"config reload: network.dns_overrides updated ({} entries)",
|
||||||
|
|
|
||||||
|
|
@ -532,7 +532,7 @@ impl ProxyConfig {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tag) = &self.general.ad_tag {
|
for (user, tag) in &self.access.user_ad_tags {
|
||||||
let zeros = "00000000000000000000000000000000";
|
let zeros = "00000000000000000000000000000000";
|
||||||
if !is_valid_ad_tag(tag) {
|
if !is_valid_ad_tag(tag) {
|
||||||
return Err(ProxyError::Config(
|
return Err(ProxyError::Config(
|
||||||
|
|
@ -540,7 +540,7 @@ impl ProxyConfig {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if tag == zeros {
|
if tag == zeros {
|
||||||
warn!("ad_tag is all zeros; register a valid proxy tag via @MTProxybot to enable sponsored channel");
|
warn!(user = %user, "user ad_tag is all zeros; register a valid proxy tag via @MTProxybot to enable sponsored channel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,14 +247,15 @@ pub struct GeneralConfig {
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub use_middle_proxy: bool,
|
pub use_middle_proxy: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub ad_tag: Option<String>,
|
|
||||||
|
|
||||||
/// Path to proxy-secret binary file (auto-downloaded if absent).
|
/// Path to proxy-secret binary file (auto-downloaded if absent).
|
||||||
/// Infrastructure secret from https://core.telegram.org/getProxySecret.
|
/// Infrastructure secret from https://core.telegram.org/getProxySecret.
|
||||||
#[serde(default = "default_proxy_secret_path")]
|
#[serde(default = "default_proxy_secret_path")]
|
||||||
pub proxy_secret_path: Option<String>,
|
pub proxy_secret_path: Option<String>,
|
||||||
|
|
||||||
|
/// Global ad_tag (32 hex chars from @MTProxybot). Fallback when user has no per-user tag in access.user_ad_tags.
|
||||||
|
#[serde(default)]
|
||||||
|
pub ad_tag: Option<String>,
|
||||||
|
|
||||||
/// Public IP override for middle-proxy NAT environments.
|
/// Public IP override for middle-proxy NAT environments.
|
||||||
/// When set, this IP is used in ME key derivation and RPC_PROXY_REQ "our_addr".
|
/// When set, this IP is used in ME key derivation and RPC_PROXY_REQ "our_addr".
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -807,6 +808,10 @@ pub struct AccessConfig {
|
||||||
#[serde(default = "default_access_users")]
|
#[serde(default = "default_access_users")]
|
||||||
pub users: HashMap<String, String>,
|
pub users: HashMap<String, String>,
|
||||||
|
|
||||||
|
/// Per-user ad_tag (32 hex chars from @MTProxybot).
|
||||||
|
#[serde(default)]
|
||||||
|
pub user_ad_tags: HashMap<String, String>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub user_max_tcp_conns: HashMap<String, usize>,
|
pub user_max_tcp_conns: HashMap<String, usize>,
|
||||||
|
|
||||||
|
|
@ -833,6 +838,7 @@ impl Default for AccessConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
users: default_access_users(),
|
users: default_access_users(),
|
||||||
|
user_ad_tags: HashMap::new(),
|
||||||
user_max_tcp_conns: HashMap::new(),
|
user_max_tcp_conns: HashMap::new(),
|
||||||
user_expirations: HashMap::new(),
|
user_expirations: HashMap::new(),
|
||||||
user_data_quota: HashMap::new(),
|
user_data_quota: HashMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -448,7 +448,7 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
info!("Middle-proxy STUN probing disabled by network.stun_use=false");
|
info!("Middle-proxy STUN probing disabled by network.stun_use=false");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ad_tag (proxy_tag) for advertising
|
// Global ad_tag (pool default). Used when user has no per-user tag in access.user_ad_tags.
|
||||||
let proxy_tag = config
|
let proxy_tag = config
|
||||||
.general
|
.general
|
||||||
.ad_tag
|
.ad_tag
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,22 @@ where
|
||||||
stats.increment_user_connects(&user);
|
stats.increment_user_connects(&user);
|
||||||
stats.increment_user_curr_connects(&user);
|
stats.increment_user_curr_connects(&user);
|
||||||
|
|
||||||
let proto_flags = proto_flags_for_tag(proto_tag, me_pool.has_proxy_tag());
|
// Per-user ad_tag from access.user_ad_tags; fallback to general.ad_tag (hot-reloadable)
|
||||||
|
let user_tag: Option<Vec<u8>> = config
|
||||||
|
.access
|
||||||
|
.user_ad_tags
|
||||||
|
.get(&user)
|
||||||
|
.and_then(|s| hex::decode(s).ok())
|
||||||
|
.filter(|v| v.len() == 16);
|
||||||
|
let global_tag: Option<Vec<u8>> = config
|
||||||
|
.general
|
||||||
|
.ad_tag
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| hex::decode(s).ok())
|
||||||
|
.filter(|v| v.len() == 16);
|
||||||
|
let effective_tag = user_tag.or(global_tag);
|
||||||
|
|
||||||
|
let proto_flags = proto_flags_for_tag(proto_tag, effective_tag.is_some());
|
||||||
debug!(
|
debug!(
|
||||||
trace_id = format_args!("0x{:016x}", trace_id),
|
trace_id = format_args!("0x{:016x}", trace_id),
|
||||||
user = %user,
|
user = %user,
|
||||||
|
|
@ -256,6 +271,7 @@ where
|
||||||
|
|
||||||
let (c2me_tx, mut c2me_rx) = mpsc::channel::<C2MeCommand>(C2ME_CHANNEL_CAPACITY);
|
let (c2me_tx, mut c2me_rx) = mpsc::channel::<C2MeCommand>(C2ME_CHANNEL_CAPACITY);
|
||||||
let me_pool_c2me = me_pool.clone();
|
let me_pool_c2me = me_pool.clone();
|
||||||
|
let effective_tag = effective_tag;
|
||||||
let c2me_sender = tokio::spawn(async move {
|
let c2me_sender = tokio::spawn(async move {
|
||||||
let mut sent_since_yield = 0usize;
|
let mut sent_since_yield = 0usize;
|
||||||
while let Some(cmd) = c2me_rx.recv().await {
|
while let Some(cmd) = c2me_rx.recv().await {
|
||||||
|
|
@ -268,6 +284,7 @@ where
|
||||||
translated_local_addr,
|
translated_local_addr,
|
||||||
&payload,
|
&payload,
|
||||||
flags,
|
flags,
|
||||||
|
effective_tag.as_deref(),
|
||||||
).await?;
|
).await?;
|
||||||
sent_since_yield = sent_since_yield.saturating_add(1);
|
sent_since_yield = sent_since_yield.saturating_add(1);
|
||||||
if should_yield_c2me_sender(sent_since_yield, !c2me_rx.is_empty()) {
|
if should_yield_c2me_sender(sent_since_yield, !c2me_rx.is_empty()) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use rand::seq::SliceRandom;
|
||||||
use super::registry::ConnMeta;
|
use super::registry::ConnMeta;
|
||||||
|
|
||||||
impl MePool {
|
impl MePool {
|
||||||
|
/// Send RPC_PROXY_REQ. `tag_override`: per-user ad_tag (from access.user_ad_tags); if None, uses pool default.
|
||||||
pub async fn send_proxy_req(
|
pub async fn send_proxy_req(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
conn_id: u64,
|
conn_id: u64,
|
||||||
|
|
@ -26,13 +27,15 @@ impl MePool {
|
||||||
our_addr: SocketAddr,
|
our_addr: SocketAddr,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
proto_flags: u32,
|
proto_flags: u32,
|
||||||
|
tag_override: Option<&[u8]>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let tag = tag_override.or(self.proxy_tag.as_deref());
|
||||||
let payload = build_proxy_req_payload(
|
let payload = build_proxy_req_payload(
|
||||||
conn_id,
|
conn_id,
|
||||||
client_addr,
|
client_addr,
|
||||||
our_addr,
|
our_addr,
|
||||||
data,
|
data,
|
||||||
self.proxy_tag.as_deref(),
|
tag,
|
||||||
proto_flags,
|
proto_flags,
|
||||||
);
|
);
|
||||||
let meta = ConnMeta {
|
let meta = ConnMeta {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue