#!/usr/bin/env python3 """ RKN Active Probing Monitor & Telegram Alerting Отслеживает попытки сканирования со стороны подсетей РКН / ГРЧЦ (212.192.156.0/22) и отправляет сводку в топик 18 супергруппы «Майа-Штаб» (-1004488599816). """ import os import sys import json import socket import urllib.request import subprocess from datetime import datetime from pathlib import Path # Конфигурация (читается из .env или переменных окружения) ENV_PATH = Path(__file__).resolve().parent.parent / ".env" config = {} if ENV_PATH.exists(): with open(ENV_PATH, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) config[k.strip()] = v.strip().strip("'\"") BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", config.get("TELEGRAM_BOT_TOKEN", "8065539545:AAECBnyjinUVVGv3vhtoWZ99tUmnT9sze-k")) CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", config.get("TELEGRAM_CHAT_ID", "-1004488599816")) TOPIC_ID = int(os.getenv("TELEGRAM_TOPIC_ID", config.get("TELEGRAM_TOPIC_ID", "18"))) STATE_FILE = Path("/tmp/rkn_probe_monitor_state.json") RKN_COMMENT = "RKN_ACTIVE_PROBING_DROP" def get_iptables_stats(): """Считывает счетчики пакетов и байт из iptables для правила RKN.""" try: cmd = ["iptables", "-L", "INPUT", "-v", "-n", "-x"] res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True) for line in res.stdout.splitlines(): if RKN_COMMENT in line: parts = line.strip().split() if len(parts) >= 2: packets = int(parts[0]) bytes_count = int(parts[1]) return packets, bytes_count except Exception as e: print(f"Ошибка чтения iptables: {e}", file=sys.stderr) return None, None def get_server_identity(): """Возвращает имя хоста и внешний IP.""" hostname = socket.gethostname() external_ip = "Unknown" try: req = urllib.request.Request("https://ifconfig.me/ip", headers={"User-Agent": "curl/7.68.0"}) with urllib.request.urlopen(req, timeout=5) as resp: external_ip = resp.read().decode().strip() except Exception: pass return hostname, external_ip def send_telegram_alert(text): """Отправляет сообщение в топик супергруппы Телеграм.""" url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage" payload = { "chat_id": CHAT_ID, "text": text, "parse_mode": "HTML", "message_thread_id": TOPIC_ID } req = urllib.request.Request( url, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"} ) try: with urllib.request.urlopen(req, timeout=10) as resp: data = json.loads(resp.read().decode()) return data.get("ok", False) except Exception as e: print(f"Ошибка отправки в Telegram: {e}", file=sys.stderr) return False def main(): pkts, bytes_total = get_iptables_stats() if pkts is None: print("Правило iptables с меткой RKN_ACTIVE_PROBING_DROP не найдено.") sys.exit(0) # Читаем предыдущее состояние prev_pkts = 0 prev_bytes = 0 if STATE_FILE.exists(): try: with open(STATE_FILE, "r", encoding="utf-8") as f: state = json.load(f) prev_pkts = state.get("packets", 0) prev_bytes = state.get("bytes", 0) except Exception: pass delta_pkts = pkts - prev_pkts delta_bytes = bytes_total - prev_bytes # Сохраняем текущее состояние with open(STATE_FILE, "w", encoding="utf-8") as f: json.dump({"packets": pkts, "bytes": bytes_total, "updated_at": datetime.now().isoformat()}, f) if delta_pkts > 0: hostname, ext_ip = get_server_identity() domain = config.get("DOMAIN", hostname) now_str = datetime.now().strftime("%d.%m.%Y %H:%M:%S") msg = ( f"🚨 [ACTIVE PROBING DETECTED] Попытка сканирования от РКН!\n\n" f"🌐 Сервер: {domain} ({ext_ip})\n" f"🎯 Источник: AS61280 ФГУП «ГРЧЦ» (ЦМУ ССОП)\n" f"🚫 Подсеть сканера: 212.192.156.0/22\n\n" f"📊 Новых пакетов: +{delta_pkts} ({delta_bytes / 1024:.1f} КБ)\n" f"📈 Всего заблокировано: {pkts} пакетов ({bytes_total / 1024 / 1024:.2f} МБ)\n" f"🛡️ Результат: Пакеты жестко сброшены в DROP. Сканер не получил ответа и считает хост мертвым.\n" f"🕒 {now_str}" ) print(f"Обнаружен пробинг: +{delta_pkts} пакетов! Отправка алерта...") send_telegram_alert(msg) else: print(f"Новых попыток сканирования не зафиксировано. Всего дропнуто: {pkts} пакетов.") if __name__ == "__main__": main()