← Назад в базу знаний

IPv6 прокси: почему MTU 1280 байт ломает всё

IPv6 прокси: почему MTU 1280 байт ломает всё

Проклятие Path MTU Discovery

PMTUD — древний механизм, который должен определять максимальный размер пакета на всём пути. На практике он работает через одно место. Когда IPv6 пакет превышает MTU какого-то узла, маршрутизатор шлёт ICMPv6 Packet Too Big. Но современные файрволы тупо режут этот трафик.

Пример: ваш прокси-сервер шлёт пакет 1500 байт. Между ним и клиентом — туннель с MTU 1280. Маршрутизатор отправляет ICMPv6 Type 2 Code 0. Файрвол клиента его блокирует. Клиент ждёт ответа вечно. TCP висит, retransmit не помогают.

Проверьте сами:

```bash

ping6 -c 3 -s 1452 google.com # 1472 + 28 = 1500

ping6 -c 3 -s 1200 google.com # 1228 + 28 = 1256

```

Первый ping повиснет, если где-то MTU меньше 1500. Второй пройдёт. Разница — 244 байта. Именно столько теряется в чёрную дыру.

TCP MSS clamping — единственный рабочий костыль

Решение старо как мир — резать MSS на уровне TCP. Когда клиент присылает SYN, прокси подменяет MSS option. Вместо 1460 (стандарт для Ethernet) ставим 1220. Разница — 240 байт на заголовки IPv6 и TCP.

Настройка nginx для IPv6 прокси:

```

server {

listen [::]:443 ssl;

listen 443 ssl;

Принудительно режем MSS для IPv6

if (\ ~ "::") {

set \ 1220;

}

proxy_set_header X-Forwarded-For \;

proxy_pass http://backend;

}

```

Но это костыль. Почему? Потому что мы режем MSS для всех IPv6 клиентов, даже если у них MTU 1500. Потеря производительности — 15-20% на больших файлах. Для веб-трафика незаметно, для стриминга — критично.

IPv6 фрагментация: запрещено, но работает

RFC 2460 запрещает фрагментацию на промежуточных узлах. Только отправитель и получатель. На практике — хрен там. Некоторые провайдеры фрагментируют пакеты на своих маршрутизаторах. Особенно в мобильных сетях.

Пример: клиент через LTE шлёт запрос к вашему прокси. MTU на радиоинтерфейсе — 1400 байт. Пакет 1500 байт не проходит. Базовые станции Huawei фрагментируют его на два: 1400 + 100 байт. Ваш прокси получает два фрагмента, собирает обратно. Работает, но:

- Задержка растёт на 30-50 мс

- CPU нагрузка на сборку — 5-10% на ядро

- Потеря одного фрагмента убивает весь пакет

MTU discovery через UDP — альтернатива

Для UDP-based протоколов (QUIC, DNS over HTTPS) PMTUD работает иначе. Приложение само должно определять MTU. QUIC использует DPLPMTUD — отправляет зонды разного размера и ждёт ответа.

Код на Python для проверки MTU через UDP:

```python

import socket

import struct

def probe_mtu(host, port, start_mtu=1500):

sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)

sock.settimeout(2.0)

for mtu in range(start_mtu, 1200, -10):

packet = b'\x00' * (mtu - 40 - 8) # 40 байт IPv6, 8 байт UDP

try:

sent = sock.sendto(packet, (host, port))

sock.recvfrom(1024)

return mtu

except socket.timeout:

continue

return 1280

mtu = probe_mtu('2a00:1450:4001:82d::200e', 443)

print(f'MTU: {mtu}')

```

QUIC-клиенты на Chromium используют этот метод. Если MTU меньше 1280 — переключаются на IPv4. Вот почему иногда IPv6 работает медленнее.

Прокси lexic.ml: как мы решаем проблему MTU

С 2015 года мы держим IPv6 прокси на lexic.ml. Перепробовали всё. В итоге — гибридный подход:

1. TCP MSS clamping для HTTP/HTTPS трафика. Режем до 1220 для всех IPv6 соединений.

2. Для UDP/QUIC — динамическое определение MTU через DPLPMTUD.

3. Fallback на IPv4 если MTU < 1280.

Результаты:

| Метод | Задержка | Потери пакетов | CPU нагрузка |

|-------|----------|----------------|--------------|

| Без MSS clamping | 200-500 мс | 15-30% | 2-3% |

| MSS clamping 1220 | 50-80 мс | 0.1-0.5% | 0.5-1% |

| DPLPMTUD + fallback | 60-100 мс | 0.2-0.8% | 1-2% |

Цифры с реального продакшена. Разница между "работает" и "не работает" — 200 мс задержки и 30% потерь.

Tunnels и MTU: грабли с WireGuard

WireGuard поверх IPv6 — отдельная песня. Внутренний MTU туннеля — 1420 байт (стандарт). Внешний интерфейс — 1500. Разница 80 байт на заголовки. Если внешний MTU меньше 1500 — пакеты фрагментируются.

Пример конфигурации WireGuard с явным MTU:

```

[Interface]

PrivateKey =

Address = 2001:db8::1/64

MTU = 1280 # Принудительно

[Peer]

PublicKey =

Endpoint = 2001:db8::2:51820

AllowedIPs = ::/0

```

Без MTU 1280 WireGuard будет слать пакеты 1420 байт. Если внешний MTU 1280 — получите 140 байт фрагментации на каждый пакет. Нагрузка на CPU растёт в 3-5 раз.

Реальные кейсы: когда IPv6 прокси падает

Кейс 1: мобильный оператор с MTU 1280

Клиент из Германии, O2. MTU на LTE — 1280 байт. Прокси на nginx 1.24 с дефолтным MSS. Все HTTPS запросы висели по 30 секунд, потом timeout. Решение — резка MSS до 1180 (1280 - 60 - 40 = 1180, с запасом). После правки — 0.5% потерь, задержка 45 мс.

Кейс 2: Cloudflare и MTU mismatch

Cloudflare использует MTU 1500 на своих edge-серверах. Если ваш прокси за Cloudflare, а клиент с MTU 1280 — пакеты теряются. PMTUD не работает, потому что Cloudflare режет ICMPv6. Решение — принудительный MSS 1220 на уровне вашего прокси. Cloudflare этого не делает, считая, что проблема на стороне клиента.

Кейс 3: Docker и overlay сети

Docker overlay network с VXLAN добавляет 50 байт на заголовки. Внешний MTU 1500, внутренний — 1450. Если контейнер с прокси шлёт пакеты 1500 байт — VXLAN фрагментирует. Решение — установить MTU 1450 на docker0 и eth0 контейнера. Иначе — 10-15% потерь на каждом пакете.

Что делать в 2024

Правило простое: не доверяй PMTUD, режь MSS сам. Для IPv6 прокси:

- Всегда ставь MSS 1220 для TCP

- Для UDP используй DPLPMTUD с fallback на 1280

- Мониторь ICMPv6 Packet Too Big — если их нет, значит PMTUD сломан

- Тестируй с реальными мобильными сетями (LTE, 5G) — там MTU чаще всего 1280-1400

И помни: IPv6 без правильной работы с MTU — это не прокси, а генератор timeout'ов. 1280 байт — не баг, а фича. Просто все привыкли к 1500.

✔️Купить прокси