MidТеория5 min

NAT, private IP и CIDR

Private ranges, port forwarding, STUN/TURN, CGNAT, CIDR notation, VPC subnetting, Docker/K8s networking

Почему вообще существует NAT

IPv4 даёт 2^32 ≈ 4.3 млрд адресов. Устройств в мире -- десятки миллиардов. Решение -- Network Address Translation: миллионы устройств делят один публичный IP, переписывая заголовки пакетов на границе сети.

NAT ломает "конец-в-конец" принцип интернета, создаёт проблемы для P2P, но купил IPv4 ещё 25 лет жизни. Параллельно с IPv6 (который не нуждается в NAT) большинство современных сетей -- NAT-based.

Private IP ranges (RFC 1918)

Три диапазона зарезервированы для частных сетей и не маршрутизируются в интернете:

CIDR Range Hosts Обычное применение
10.0.0.0/8 10.0.0.0 – 10.255.255.255 16.7M Крупные корпоративные сети, AWS VPC default
172.16.0.0/12 172.16.0.0 – 172.31.255.255 1M Docker default bridge (172.17.0.0/16)
192.168.0.0/16 192.168.0.0 – 192.168.255.255 65K Домашние роутеры
100.64.0.0/10 100.64.0.0 – 100.127.255.255 4M CGNAT (Carrier-Grade)
169.254.0.0/16 link-local 65K Auto-config, cloud metadata (169.254.169.254)

Пакет с source=10.0.0.5 не выйдет в публичный интернет -- провайдеры дропают такие (или должны).

CIDR notation

Classless Inter-Domain Routing (RFC 4632) записывает подсеть через префикс:

10.0.0.0/24

Бинарно:
10.0.0.0       = 00001010.00000000.00000000.00000000
Mask /24       = 11111111.11111111.11111111.00000000  (255.255.255.0)

Network     = 10.0.0.0
Broadcast   = 10.0.0.255
First host  = 10.0.0.1
Last host   = 10.0.0.254
Usable      = 254

Таблица быстрого расчёта

Prefix Netmask Total IPs Usable hosts Пример
/32 255.255.255.255 1 1 Конкретный хост
/30 255.255.255.252 4 2 Point-to-point link
/29 255.255.255.248 8 6 Мини-сеть для 4-6 устройств
/28 255.255.255.240 16 14 AWS VPC minimum subnet
/24 255.255.255.0 256 254 Типичный LAN
/20 255.255.240.0 4096 4094 Крупная подсеть VPC
/16 255.255.0.0 65536 65534 Класс B, docker bridge
/8 255.0.0.0 16.7M 16.7M Класс A, 10/8

Для сабнеттинга важно: hosts = 2^(32-prefix) - 2 (минус network и broadcast).

AWS VPC дополнительно забирает 3 IP в начале и 1 в конце каждой subnet (VPC router, DNS, future use, broadcast).

Типы NAT

SNAT (Source NAT, Masquerade)

Типичный домашний роутер: исходящие пакеты от 192.168.1.x переписываются с source IP = публичный IP роутера.

Client 192.168.1.5:51234  →  Router  →  Internet
                              SNAT:
                              rewrite src = 203.0.113.7:51234

Server 93.184.216.34:80
                              ← reply to 203.0.113.7:51234
                              Router NAT table:
                                203.0.113.7:51234 ↔ 192.168.1.5:51234
                              rewrite dst = 192.168.1.5:51234 → Client

Роутер держит NAT table -- маппинг наружного соединения на внутреннее. Entry живёт, пока идёт трафик + timeout (60-600 секунд для TCP).

DNAT (Destination NAT, Port Forwarding)

Обратное -- пакет из интернета приходит на публичный IP:port, роутер перенаправляет на внутренний хост.

# iptables: forward public port 443 to internal web server
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 \
  -j DNAT --to-destination 192.168.1.100:8443

# Plus masquerade back
iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8443 \
  -j SNAT --to-source 192.168.1.1

# Enable kernel IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

Full Cone / Restricted / Symmetric NAT

RFC 3489 (STUN original) классифицировал NAT по поведению:

  • Full Cone -- любой внешний IP:port может слать на открытую дырку. P2P-friendly.
  • Restricted Cone -- только тот внешний IP, куда клиент уже слал. Порт любой.
  • Port-Restricted Cone -- только тот внешний IP:port.
  • Symmetric -- для каждого dst маппинг разный. Убивает P2P.

Symmetric NAT (типичный carrier-grade) -- главный враг WebRTC и игровых P2P.

NAT traversal: STUN и TURN

STUN (Session Traversal Utilities for NAT)

Клиент за NAT хочет узнать, какой у него public IP:port. Он стучит на STUN-сервер, сервер отвечает: "я видел тебя с 203.0.113.7:51234". Дальше клиент сообщает пиру этот адрес.

Client (10.0.0.5:51234) → NAT (203.0.113.7:45678) → STUN server (74.125.250.129:19302)

STUN response:
  Your reflexive address: 203.0.113.7:45678

Client share это peer'у через signaling.
Peer пытается подключиться напрямую по 203.0.113.7:45678.
Если NAT симметричный -- маппинг для другого dst будет другим, не сработает.

TURN (Traversal Using Relays around NAT)

Когда STUN не помогает (symmetric NAT, CGNAT), используется TURN-сервер как relay:

Peer A  ───→  TURN server  ←───  Peer B
(10.0.0.5)    (203.0.113.20)    (10.1.2.3)

Весь трафик идёт через TURN-сервер. Дороже, но всегда работает.

WebRTC обычно пробует по порядку: host (LAN) → srflx (STUN) → relay (TURN). Это называется ICE -- Interactive Connectivity Establishment.

Hairpinning

Сценарий: два устройства за одним NAT хотят общаться через публичный IP этого NAT.

Device A (192.168.1.5) → 203.0.113.7:443 (публичный адрес своего же роутера) → Device B (192.168.1.10)

Hairpin NAT должен завернуть пакет обратно в LAN. Многие дешёвые роутеры этого не умеют -- соединение умирает. Решение -- split-horizon DNS (отдавать внутренний IP изнутри, внешний -- извне).

CGNAT: double NAT

Провайдерам не хватает публичных IPv4, они ставят свой NAT между абонентом и интернетом:

Home device → Home router NAT → CGNAT (ISP) → Internet
10.0.0.5      192.168.1.1       100.64.0.1    203.0.113.7

Абонент получает адрес из 100.64.0.0/10 (CGNAT range). Два слоя NAT ломают ещё больше P2P, inbound connection становится невозможен без провайдерского cooperation.

VPC subnet design

AWS VPC

VPC -- изолированная виртуальная сеть в облаке. Типичная топология:

VPC: 10.0.0.0/16  (65K адресов)
 │
 ├── Public subnet A:  10.0.0.0/20   (AZ-1, IGW route)
 ├── Public subnet B:  10.0.16.0/20  (AZ-2, IGW route)
 │
 ├── Private subnet A: 10.0.32.0/20  (AZ-1, NAT Gateway)
 ├── Private subnet B: 10.0.48.0/20  (AZ-2, NAT Gateway)
 │
 ├── DB subnet A:      10.0.64.0/24  (AZ-1, no internet)
 └── DB subnet B:      10.0.65.0/24  (AZ-2, no internet)

Правила

  1. Не использовать 192.168.0.0/16 для VPC -- дома тоже используется, VPN ломается.
  2. Резервировать /16 (минимум) -- потом не расширить.
  3. Подсети должны не пересекаться между VPC, если планируется peering.
  4. Одна subnet = одна AZ (нельзя растянуть).
  5. Public vs Private определяется маршрутом: public имеет route 0.0.0.0/0 → IGW, private → NAT Gateway.

Docker bridge network = NAT

Docker создаёт Linux bridge docker0 с адресом 172.17.0.1/16. Каждый контейнер получает IP в 172.17.0.0/16 и общается с миром через masquerade NAT.

# Смотрим bridge
ip addr show docker0
# inet 172.17.0.1/16 scope global docker0

# NAT правила для исходящих
iptables -t nat -L POSTROUTING -n
# MASQUERADE  all  --  172.17.0.0/16  0.0.0.0/0

# Port publishing = DNAT
docker run -p 8080:80 nginx
# → iptables DNAT: host:8080 → 172.17.0.2:80
iptables -t nat -L DOCKER -n
# DNAT  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:8080  to:172.17.0.2:80

Overlay-сети в Swarm/K8s работают поверх VXLAN и шифруются, NAT -- только на границе (ingress).

Kubernetes networking

K8s имеет три слоя IP:

Node network:    10.0.0.0/16        (реальные VM)
Pod network:     10.244.0.0/16      (каждый pod свой IP, flat)
Service network: 10.96.0.0/12       (виртуальные ClusterIP)

Pod IP

Каждый Pod получает уникальный IP в cluster. Pods могут общаться напрямую без NAT (это K8s model).

ClusterIP

Service с type: ClusterIP -- виртуальный IP в service-subnet, не принадлежит ни одному Pod. kube-proxy реализует балансировку через iptables/IPVS rules:

Client → 10.96.23.45:80 (ClusterIP)
  iptables:
    dst=10.96.23.45:80
    → DNAT to one of: 10.244.1.2:8080, 10.244.1.3:8080, 10.244.2.1:8080

NodePort / LoadBalancer / ExternalIP

  • NodePort -- открывает порт 30000-32767 на всех Node, извне доступно через any-node-ip:nodeport.
  • LoadBalancer -- облачный LB (AWS ELB, GCP LB), маршрутизирует на NodePorts.
  • ExternalIP -- Pod видит приходящий трафик с real client IP (если сохранять через externalTrafficPolicy: Local).

Практика: iptables NAT пример

Домашний шлюз с port forwarding

#!/bin/bash
# /usr/local/bin/setup-nat.sh
# Run on the gateway/router

WAN_IF=eth0
LAN_IF=eth1
LAN_NET=192.168.1.0/24

# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# Flush NAT table
iptables -t nat -F
iptables -t nat -X

# Masquerade outbound traffic from LAN
iptables -t nat -A POSTROUTING -s $LAN_NET -o $WAN_IF -j MASQUERADE

# Forward incoming HTTPS to internal web server
iptables -t nat -A PREROUTING -i $WAN_IF -p tcp --dport 443 \
  -j DNAT --to-destination 192.168.1.100:443

# Allow forwarding between interfaces
iptables -A FORWARD -i $LAN_IF -o $WAN_IF -j ACCEPT
iptables -A FORWARD -i $WAN_IF -o $LAN_IF \
  -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i $WAN_IF -o $LAN_IF -p tcp --dport 443 -j ACCEPT

# Show NAT rules
iptables -t nat -L -n -v --line-numbers

Linux routing

# Показать таблицу маршрутизации
ip route
# default via 192.168.1.1 dev eth0
# 10.0.0.0/8 via 192.168.1.1 dev eth0
# 169.254.0.0/16 dev eth0 scope link metric 1000
# 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.50

# Добавить статический маршрут для VPN подсети
ip route add 10.0.100.0/24 via 192.168.1.254 dev eth0

# Показать, какой маршрут выберется для конкретного destination
ip route get 8.8.8.8
# 8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.50

# Policy-based routing (несколько таблиц)
ip rule add from 192.168.1.100/32 lookup 100
ip route add default via 10.0.0.1 table 100

# Проверить NAT connection tracking
conntrack -L | head
# tcp 6 ESTABLISHED src=192.168.1.5 dst=93.184.216.34 sport=51234 dport=443
#   src=93.184.216.34 dst=203.0.113.7 sport=443 dport=45678 [ASSURED]

Conntrack: сердце NAT на Linux

NAT работает поверх conntrack -- per-connection state tracker в ядре. Таблица имеет лимит (net.netfilter.nf_conntrack_max, обычно 262144). Переполнение приводит к drop новых соединений.

# Current usage
cat /proc/sys/net/netfilter/nf_conntrack_count
wc -l /proc/net/nf_conntrack

# Max
sysctl net.netfilter.nf_conntrack_max

# Tune для busy gateway
sysctl -w net.netfilter.nf_conntrack_max=1048576
sysctl -w net.nf_conntrack_tcp_timeout_established=600

Когда NAT мешает

  1. Real client IP: приложение за NAT видит только адрес балансера. Решение -- X-Forwarded-For (HTTP), PROXY protocol (L4).
  2. Long-lived connections: TCP keepalive должен быть чаще NAT timeout (~180 сек), иначе roadrmap тихо умирает.
  3. P2P: WebRTC, BitTorrent, игры требуют STUN/TURN или UPnP.
  4. FTP, SIP: протоколы, передающие IP в payload, нуждаются в ALG (Application Layer Gateway).
  5. IPsec: ломается NAT, требует NAT-T (UDP encapsulation).
  6. Port exhaustion: один публичный IP = 65K портов на destination. CGNAT с 1000 абонентов на IP быстро кончается.

Выводы

  • Private ranges: 10/8, 172.16/12, 192.168/16, плюс 100.64/10 для CGNAT.
  • CIDR /prefix: hosts = 2^(32-prefix) - 2. AWS ещё вычитает 3 служебных адреса.
  • NAT типы: SNAT (outbound), DNAT (port forwarding). Симметричный NAT ломает P2P.
  • STUN даёт public reflexive address, TURN relay'ит трафик когда STUN бессилен.
  • CGNAT -- двойной NAT у провайдеров, абонент получает адрес из 100.64/10.
  • Docker = bridge + MASQUERADE. K8s = flat pod network + virtual ClusterIPs.
  • Проектируя VPC: не пересекаться с офисными сетями, резервировать /16, одна subnet = одна AZ.
  • conntrack table -- узкое место busy NAT-gateway, tune nf_conntrack_max.