From 5007bfdb37132e49e28b8b5a27e0ba7d67e59e06 Mon Sep 17 00:00:00 2001 From: oleg ruchkin Date: Thu, 5 Mar 2026 12:05:39 +0300 Subject: [PATCH] feat: add per-user unique IPs metric for key sharing detection Add mtprotoproxy_user_unique_ips gauge metric that tracks the number of unique source IPs currently connected per user/secret. This helps proxy operators detect key sharing - if a single secret has connections from many different IPs, it likely means the key was shared with multiple people. Changes: - Track active connections per (user, source_ip) pair - Export user_unique_ips gauge via Prometheus metrics endpoint - Zero overhead: single dict lookup per connect/disconnect --- mtprotoproxy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mtprotoproxy.py b/mtprotoproxy.py index 10de4aa..6f44a05 100755 --- a/mtprotoproxy.py +++ b/mtprotoproxy.py @@ -94,6 +94,7 @@ stats = collections.Counter() user_stats = collections.defaultdict(collections.Counter) +user_ip_conns = collections.defaultdict(lambda: collections.defaultdict(int)) config = {} @@ -1687,6 +1688,7 @@ def decrypt(self, data): task_clt_to_tg = asyncio.ensure_future(clt_to_tg) update_user_stats(user, curr_connects=1) + user_ip_conns[user][cl_ip] += 1 tcp_limit_hit = ( user in config.USER_MAX_TCP_CONNS and @@ -1711,6 +1713,10 @@ def decrypt(self, data): update_user_stats(user, curr_connects=-1) + user_ip_conns[user][cl_ip] -= 1 + if user_ip_conns[user][cl_ip] <= 0: + del user_ip_conns[user][cl_ip] + task_tg_to_clt.cancel() task_clt_to_tg.cancel() @@ -1830,6 +1836,11 @@ async def handle_metrics(reader, writer): metric = {"user": user, "val": val} metrics.append([m_name, m_type, m_desc, metric]) + + for user in user_stats: + metric = {"user": user, "val": len(user_ip_conns[user])} + metrics.append(["user_unique_ips", "gauge", + "number of unique IPs currently connected per user", metric]) pkt = make_metrics_pkt(metrics) writer.write(pkt.encode()) await writer.drain()