Короткий ответ

Hairpin (NAT loopback): клиент в 10.0.10.0/24 стучится на публичный IP офиса, который DNAT-ится на 10.0.10.10. Без дополнительного srcnat ответы идут «мимо» клиента, сессия не собирается. Либо сделайте split DNS на внутренний A 10.0.10.10, либо добавьте узкий hairpin: DNAT + masquerade для ip saddr 10.0.10.0/24 к этому публичному/порту. Не DNAT tcp dport 1-65535 «на всякий».

Предпочтительнее split DNS — меньше NAT.

Симптомы и как отличить

  • С телефона вне офиса сайт есть;
  • из LAN nslookup www.contoso.example = публичный;
  • Test-NetConnection публичный -Port 443 False из LAN, до 10.0.10.10 True.

Отличия: NAT не работает даже наружу — NAT не для всех. Можно починить только DNS — split.

Возможные причины

  1. DNAT с WAN есть, hairpin srcnat нет.
  2. Правило hairpin не матчит LAN iif.
  3. Клиент ходит не через этот шлюз.
  4. Порт в DNAT не 443 (только 80).
  5. firewall forward не пускает LAN→LAN via NAT.
  6. MTU после двойного NAT.
  7. Прокси в LAN обходит.

Диагностика

1. Что резолвится и что слушает

nslookup www.contoso.example 10.0.10.2
Test-NetConnection 10.0.10.10 -Port 443
Test-NetConnection PUBLIC.IP.HERE -Port 443
ping -n 2 10.0.10.10

Публичный IP подставьте свой, не выдумывайте. Если внутренний 443 жив, публичный из LAN нет — hairpin/split.

2. Путь

Find-NetRoute -RemoteIPAddress PUBLIC.IP.HERE
tracert -d PUBLIC.IP.HERE

Часто первый хоп 10.0.10.1, дальше тишина или петля.

3. Правила на шлюзе

sudo nft list chain inet nat prerouting
sudo nft list chain inet nat postrouting
sudo conntrack -L | grep 10.0.10.10

Должны быть: (prerouting) DNAT daddr PUBLIC tcp dport 443 → 10.0.10.10; (postrouting) для saddr LAN daddr 10.0.10.10 masquerade (типичный hairpin). Без второго SYN-ACK уходит на клиент «от 10.0.10.10», а клиент ждал публичный IP.

tcpdump на клиенте: SYN на публичный, ответ с RFC1918 — стек отбросит.

Решение

Сценарий A. Предпочтительно split DNS

Внутренний A www10.0.10.10. Hairpin не нужен. Клиенты спрашивают 10.0.10.2.

Сценарий B. Нужен именно публичный IP из LAN

nftables, имена интерфейсов свои (eth-lan, eth-wan):

Логика:

  1. prerouting: iif eth-lan ip daddr PUBLIC tcp dport 443 dnat to 10.0.10.10
  2. postrouting: iif eth-lan ip saddr 10.0.10.0/24 ip daddr 10.0.10.10 masquerade

Плюс forward established и new к 10.0.10.10:443 только этот порт.

Сохраните текущий ruleset до правки. Откат: nft -f backup.nft.

Сценарий C. Клиенты не через 10.0.10.1

Hairpin на этом шлюзе их не увидит. Верните default. Другая сеть 10.0.20.0/24 может ходить на публичный через WAN без hairpin — это норма.

Сценарий D. Только HTTP, HTTPS нет

Добавьте 443 отдельно, сертификат на origin. Не «откройте все».

6. Доказать DNAT с LAN, не «сайт лежит»

С LAN: nslookup = публичный, Test-NetConnection 10.0.10.10 -Port 443 True, к публичному False — классика hairpin. tcpdump на клиенте: SYN на публичный, ответ с 10.0.10.10 — стек отбросит, нужен masquerade LAN→origin. Предпочтительный фикс — внутренняя A. Если ПО зашило белый IP — узкий DNAT+srcnat только 80/443, не 1-65535. Клиенты не через 10.0.10.1 hairpin не увидят: сначала шлюз. Соседняя сеть 10.0.20.0/24 может работать без hairpin, это не аргумент «правила NAT нет». После внедрения проверьте LTE, что внешний DNAT жив. Портсканом с LAN не должны открыться лишние пробросы. QUIC: либо split DNS, либо UDP/443 отдельно; иначе «Chrome не открывает, curl TCP жив». MTU на петле проверьте DF, если TCP устанавливается и виснет на больших ответах. Сертификат на origin должен покрывать имя; hairpin не чинит TLS. Откат: сохранённый nft. Не вешайте публичный IP на loopback origin.

В runbook: имя, публичный IP, origin 10.0.10.10, порты, выбрана модель split или hairpin. Смешение моделей по ПК — вечный тикет.

С Windows LAN:

nslookup www.contoso.example 10.0.10.2
Test-NetConnection 10.0.10.10 -Port 443
Test-NetConnection PUBLIC.IP.HERE -Port 443
Get-NetNeighbor -IPAddress 10.0.10.1

Если внутренний 443 True, публичный False, gw верный — hairpin/split, не «IIS лёг». Не подтверждайте успех одним ping публичного адреса шлюза: echo отвечает сам WAN-IP без DNAT. После split DNS клиенты не должны больше ходить на белый из LAN; проверьте, что GPO/DoH не возвращает публичный. Если оставили hairpin, ограничьте dport и saddr 10.0.10.0/24. Проверьте, что из 10.0.20.0/24 по-прежнему открывается через обычный WAN DNAT. Логи origin должны показать source: при рабочем hairpin вы увидите IP шлюза (после masquerade), не клиента — это норма и причина, почему логи «все с .1». Для учёта клиента split DNS лучше.

Как проверить, что проблема устранена

С ПК в 10.0.10.0/24:

nslookup www.contoso.example 10.0.10.2
Test-NetConnection www.contoso.example -Port 443
curl.exe -I https://www.contoso.example/

С внешней сети (телефон LTE) сайт не сломался. Портскан с LAN не показывает лишних DNAT. conntrack: LAN source транслируется при обращении к публичному.

Если не помогло

  • MTU на петле.
  • Origin слушает только 127.0.0.1.
  • SNI/несколько сайтов на одном IP — DNAT порта мало, нужен reverse proxy.
  • QUIC: UDP/443 отдельное правило, лучше split DNS.

Профилактика

Пользовательская проверка обязательна из той же VLAN, откуда была жалоба: не с jump-хоста в 10.0.20.0/24, где hairpin не нужен. Иначе тикет закроют ложно.

  • Для внутренних пользователей — внутренние A.
  • Hairpin только если нельзя split (кривое ПО с захардкоженным белым IP).
  • Ревизия DNAT: список портов в тикете.
  • Мониторинг с внутри LAN на публичный URL, если hairpin обязателен.

FAQ

Почему снаружи работает без hairpin?

Пакет приходит на WAN, DNAT обычный, ответ через NAT state. Из LAN пакет не на WAN.

Это то же, что NAT reflection на MikroTik?

Да, тот же класс задачи. На Ubuntu — nft DNAT+masquerade. Не включайте reflection на все порты.

Безопасен ли hairpin?

Уже, чем «forward any», но хуже split DNS: лишняя трансляция, сложнее ACL. Режьте порты.

Можно ли прописать публичный IP на loopback сервера?

Не надо. Сломаете маршрутизацию. Либо внутренний A, либо NAT на шлюзе.

ping публичного IP из LAN есть, HTTPS нет.

ICMP до WAN-адреса шлюза не равен DNAT 443. Смотрите TCP и правила портов.