voidlearn
 

9.1Базовый уровень

Свяжи DNS, IP, маршрутизацию, ARP, NAT и порт с этапами одного запроса. Пойми, какие сведения есть у процесса, какие у ядра и какие появляются только после разрешения имени.

Зачем вообще уровни

Идея слоёной модели одна: каждый уровень решает свою задачу и не знает, что внутри у соседа. Ethernet понятия не имеет об IP-адресах, он доставляет кадр соседу по проводу. IP не знает про соединения и доставляет отдельный пакет «куда-то туда, по возможности». TCP даёт поток байт, а про HTTP не знает. Самому HTTP неважно, что под ним: TCP или QUIC.

OSI живёт в учебниках и в разговорах («L7-балансировщик», «L3-связность»). В интернете реально работает четырёхуровневая TCP/IP (она же DoD-модель). На собесе называют обе и показывают соответствие: уровни OSI 5–6–7 в реальном стеке слиты в «прикладной».

Инкапсуляция похожа на упаковку в конверт. Уровень берёт всё, что пришло сверху, целиком считает это полезной нагрузкой, приписывает спереди свой заголовок (служебные поля: адреса, флаги, длины) и передаёт получившееся вниз. HTTP-запрос оказывается внутри TCP-сегмента, сегмент внутри IP-пакета, а пакет внутри Ethernet-кадра. Матрёшка. На приёме всё разбирается в обратном порядке, это декапсуляция: каждый уровень снимает свой заголовок и отдаёт остаток наверх.

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

OSI Стек TCP/IP Примеры протоколов Единица данных L7 Прикладной L6 Представления L5 Сеансовый L4 Транспортный L3 Сетевой L2 Канальный L1 Физический Прикладной Транспортный Межсетевой Канальный HTTP, DNS, gRPC, SMTP, SSH, AMQP данные TLS, сжатие, кодировки, сериализация данные сессии, RPC-сессии, NetBIOS данные TCP, UDP, QUIC (внутри UDP), SCTP сегмент / датаграмма IP, ICMP, IPsec; ARP на стыке L2/L3 пакет Ethernet, Wi-Fi, PPP, MAC-адреса кадр (frame) медь, оптика, радио, разъёмы, вольты биты Инкапсуляция: сверху вниз каждый уровень дописывает свой заголовок L7 данные GET / HTTP/1.1 Host: ... L4 сегмент TCP 20 Б данные L3 пакет IP 20 Б TCP данные L2 кадр Eth 14 Б IP TCP данные FCS Обратный путь на приёме - декапсуляция: каждый уровень снимает свой заголовок и отдаёт остаток наверх. Полезная нагрузка на Ethernet - 1500 байт (MTU), из них 40 съедают IP+TCP: остаётся 1460 (MSS).
Стек и инкапсуляция. Каждый уровень видит содержимое верхнего как непрозрачную полезную нагрузку. Поэтому L4-балансировщик не может посмотреть на URL, а L7 может.
Формулировка, которая сразу читается как «понимает»

«L2 доставляет кадр внутри одного сегмента сети по MAC-адресу. L3 доставляет пакет между сетями по IP-адресу, без гарантий. L4 добавляет понятие процесса (порт) и, в случае TCP, надёжность и порядок. На L7 появляется смысл: методы, ресурсы, заголовки». Дальше можно прицепить практику: L4-балансировщик (LVS, AWS NLB) видит только пятёрку и молотит пакеты, L7-балансировщик (nginx, Envoy, ALB) терминирует TCP+TLS и распределяет по URL, заголовкам, куки, но стоит дороже.

IP-адреса, маски и подсети

IPv4-адрес занимает 32 бита и записывается четырьмя октетами. Логически он делится на сетевую часть и хостовую, границу задаёт маска. Запись CIDR 10.0.5.17/24 означает: под сеть отведены первые 24 бита, под хост оставшиеся 8. Маска /24 = 255.255.255.0.

10.0.5.17/24
  адрес сети      10.0.5.0        все хостовые биты в 0
  broadcast       10.0.5.255      все хостовые биты в 1
  хостов          2^8 - 2 = 254   минус адрес сети и broadcast
  диапазон        10.0.5.1 - 10.0.5.254

Маска нужна вот для чего: хост смотрит на адрес назначения и по маске решает, сосед ли это по канальному сегменту или пакет надо отдать шлюзу. Если dst & mask == my & mask, ищем MAC соседа через ARP и шлём напрямую. Иначе шлём на MAC шлюза по умолчанию, а IP-адрес назначения при этом не меняется.

ДиапазонЧто этоГде встречается
10.0.0.0/8приватная сеть (RFC 1918)корпоративные сети, VPC, поды Kubernetes
172.16.0.0/12приватная (RFC 1918)сеть Docker по умолчанию - 172.17.0.0/16
192.168.0.0/16приватная (RFC 1918)домашние роутеры
127.0.0.0/8loopback127.0.0.1, весь /8 - тоже loopback
169.254.0.0/16link-local (APIPA)DHCP не ответил; 169.254.169.254 - метадата облака
100.64.0.0/10CGNAT (RFC 6598)операторский NAT, Tailscale
224.0.0.0/4multicastOSPF, mDNS, IPTV
0.0.0.0/0маршрут по умолчанию; как bind - «все интерфейсы»ListenAndServe(":8080")
Классическая ловушка в контейнерах

Сервис слушает 127.0.0.1:8080 внутри контейнера, а снаружи ничего не работает, хотя «порт же проброшен». Loopback виден только внутри сетевого namespace. Слушать надо 0.0.0.0:8080 (в Go достаточно ":8080"). Бывает и обратный вариант вопроса: в поде Kubernetes два контейнера, и один ходит к другому по localhost. Вот это работает, потому что сетевой namespace у них общий.

NAT: как приватные адреса выходят в интернет

Приватные адреса не маршрутизируются в интернете. Роутер на границе делает NAPT (он же PAT, он же «NAT overload»): подменяет в исходящем пакете src IP:port на свой публичный адрес и свободный порт, а соответствие записывает в таблицу трансляций. По этой таблице роутер узнаёт ответный пакет и подменяет адрес обратно.

внутри              таблица NAT на роутере            снаружи
192.168.1.5:51514   192.168.1.5:51514 -> 203.0.113.8:40001   203.0.113.8:40001 -> 142.250.74.68:443
192.168.1.9:51514   192.168.1.9:51514 -> 203.0.113.8:40002   203.0.113.8:40002 -> 142.250.74.68:443

Отсюда три следствия, о них и спрашивают. Первое: соединение может начать только внутренняя сторона. Снаружи в таблице записи нет, поэтому входящие блокируются (нужен port forwarding, UPnP или hole punching через STUN/TURN). Второе: записи протухают по таймауту - обычно через 30–120 секунд для UDP и через десятки минут для TCP. Из-за этого рвутся долгие простаивающие соединения, и ровно поэтому включают TCP keep-alive. Наконец, число портов ограничено: за одним публичным адресом больше ~64 тысяч одновременных исходящих соединений на один dst IP:port не спрячешь.

DNS: как имя превращается в адрес

DNS устроен как распределённая иерархическая база. Полное имя читается справа налево: в www.google.com. точка в конце обозначает корень, за ней зона com, потом google.com. Клиент (stub resolver внутри libc или Go) сам иерархию не обходит: он задаёт рекурсивный вопрос ближайшему резолверу (провайдерскому, 8.8.8.8, 1.1.1.1, корпоративному). Серию итеративных запросов делает уже резолвер.

Браузер + ОС Резолвер Корневой (.) TLD (.com) Авторитативный 1 A? www.google.com (RD=1) 2 A? www.google.com 3 NS для com + glue 4 A? www.google.com 5 NS для google.com 6 A? www.google.com 7 A 142.250.74.68, TTL 300, AA=1 8 ответ + TTL, кладём в кэши Кэши на пути - первый, у кого есть живая запись, отвечает сразу, и шагов 2-7 не будет: кэш браузера (десятки секунд) - кэш ОС / systemd-resolved - /etc/hosts - кэш рекурсивного резолвера. TTL задаёт владелец зоны: A обычно 60-300 с, NS - часы. NXDOMAIN тоже кэшируется, по SOA minimum.
Рекурсивный резолвинг. Клиент задаёт один рекурсивный вопрос; всю итеративную работу по иерархии делает резолвер. Флаг AA в ответе означает «отвечает авторитативный сервер зоны».
ТипЧто возвращаетЗачем на практике
AIPv4-адресосновной тип; несколько A-записей = примитивная балансировка round-robin
AAAAIPv6-адресHappy Eyeballs: клиент пробует v6 и v4 параллельно и берёт, что быстрее
CNAMEалиас на другое имяуказать на CDN/балансировщик. Нельзя на вершине зоны рядом с SOA/NS
MXпочтовый сервер + приоритетмаршрутизация почты
TXTпроизвольный текстSPF, DKIM, DMARC, подтверждение владения доменом (ACME/Let's Encrypt)
SRVимя + порт + приоритет + весservice discovery: Consul, Kubernetes headless-сервисы, SIP
NSавторитативные серверы зоныделегирование зоны
SOAпараметры зонысерийник, TTL отрицательного кэширования
PTRимя по адресуобратная зона in-addr.arpa, антиспам-проверки
Глубже, чем спросят: DNS в Go

У Go два резолвера. Чистый Go-резолвер сам читает /etc/resolv.conf и шлёт UDP; cgo-резолвер зовёт getaddrinfo из libc и умеет NSS, mDNS, sssd. Go выбирает сам, а форсировать можно через GODEBUG=netdns=go / netdns=cgo. Практическое следствие: Go не кэширует DNS сам - кэш есть только у ОС и резолвера. А о смене DNS соединение из пула keep-alive не узнаёт вовсе: адрес спрашивают только при dial, и долгоживущий http.Client может месяцами держать соединение к IP, который давно выведен из ротации. Transport.IdleConnTimeout и CloseIdleConnections() закрывают только простаивающие соединения, а ограничения на возраст занятого у http.Transport нет - его делают обёрткой в DialContext или на сервере.

ARP: от IP-адреса к MAC

IP-пакет нельзя просто положить в провод: туда кладут кадр Ethernet, а у кадра адреса MAC, не IP. Этот разрыв закрывает ARP: «у кого IP 10.0.5.1, сообщите свой MAC». Запрос идёт широковещательно на ff:ff:ff:ff:ff:ff, а отвечают юникастом. Результат живёт в ARP-кэше десятки секунд (ip neigh в Linux).

$ ip neigh
10.0.5.1     dev eth0 lladdr 02:42:ac:11:00:01 REACHABLE
10.0.5.23    dev eth0 lladdr 02:42:ac:11:00:17 STALE

$ arping -I eth0 10.0.5.1        # ручной ARP-запрос
$ tcpdump -n -i eth0 arp         # посмотреть широковещательные who-has

Для ответа запомни одно: ARP работает только внутри одного канального сегмента. Если адрес назначения в другой сети, хост никогда не спрашивает про него ARP - он спрашивает MAC шлюза. IP-адреса в пакете при этом не меняются от источника до получателя (если нет NAT), а MAC-адреса переписываются на каждом роутере. В IPv6 роль ARP играет NDP поверх ICMPv6.

Порт против сокета

Порт всего лишь 16-битное число (0–65535) в заголовке TCP/UDP, метка «какому процессу отдать». Сокет принадлежит операционной системе. Это файловый дескриптор, за которым стоят буферы, состояние и пятёрка: {протокол, локальный IP, локальный порт, удалённый IP, удалённый порт}. Уникальна именно пятёрка, а не порт.

Порт - одно число в заголовке. Сокет - пятёрка {протокол, src IP, src port, dst IP, dst port}. Клиент A 203.0.113.7 Клиент B 198.51.100.9 Клиент A, вкладка 2 203.0.113.7 TCP 203.0.113.7:51514 -> 10.0.0.5:443 TCP 198.51.100.9:40122 -> 10.0.0.5:443 TCP 203.0.113.7:51515 -> 10.0.0.5:443 Сервер 10.0.0.5 один listen-сокет :443 три установленных сокета accept() вернул три fd Все три соединения живут на ОДНОМ серверном порту 443 - ядро различает их по пятёрке, а не по порту.
Порт против сокета. Серверу не нужно «по порту на клиента»: порт один, сокетов столько, сколько соединений. Лимит упирается не в порты, а в файловые дескрипторы и память ядра.
Практика: где реально кончаются ресурсы
  • У сервера кончаются не порты, а file descriptors (ulimit -n), память под сокеты и очередь accept (somaxconn, backlog).
  • У клиента кончаются эфемерные порты: в Linux по умолчанию net.ipv4.ip_local_port_range = 32768 60999, это ~28 тысяч. Но лимит считается на пару (dst IP, dst port), поэтому к одному бэкенду выйдет 28 тысяч, а к десяти разным 280 тысяч.
  • Порты 0–1023 привилегированные: для них нужен root или CAP_NET_BIND_SERVICE.

Маршрутизация: как пакет находит дорогу

У каждого хоста есть таблица маршрутов. Маршрут выбирают по правилу longest prefix match: из всех подходящих побеждает тот, у которого длиннее маска. У 0.0.0.0/0 префикс самый короткий, поэтому он означает «всё остальное», то есть маршрут по умолчанию.

$ ip route
default via 192.168.1.1 dev wlan0            # 0.0.0.0/0  - шлюз по умолчанию
192.168.1.0/24 dev wlan0 proto kernel        # своя подсеть, без шлюза
172.17.0.0/16 dev docker0                    # мост докера

$ ip route get 142.250.74.68
142.250.74.68 via 192.168.1.1 dev wlan0 src 192.168.1.5

Дальше пакет идёт по цепочке роутеров. В IPv4-заголовке есть поле TTL (в IPv6 это Hop Limit): каждый роутер уменьшает его на 1, а на нуле пакет уничтожают и шлют источнику ICMP Time Exceeded. Так сеть защищается от петель, и на этом же работает traceroute: он шлёт пакеты с TTL 1, 2, 3… и собирает адреса тех, кто ответил ICMP-ошибкой.

Что происходит, когда вводишь google.com и жмёшь Enter

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

Холодный старт: во что уходит время до первого пикселя кэш браузера, HSTS DNS-резолвинг TCP handshake 1 RTT TLS 1.3 handshake 1 RTT, либо 0-RTT по билету HTTP GET, TTFB обработка на сервере + 1 RTT приём HTML парсинг: DOM + CSSOM подресурсы CSS/JS/img HTTP/2 мультиплекс layout, paint, composite 0 100 мс 200 мс 300 мс 400 мс
Куда уходит время. На холодном старте два RTT уходят на рукопожатия ещё до того, как сервер увидел запрос, третий - на сам запрос. Отсюда весь тюнинг: 0-RTT в TLS 1.3, QUIC, preconnect, CDN поближе к пользователю.
  1. Разбор ввода. Браузер решает, это URL или поисковый запрос. Нормализует ввод и сверяется со списком HSTS preload: если домен там есть, схема принудительно становится https ещё до какого-либо трафика.
  2. Проверка кэшей. HTTP-кэш браузера (может отдать страницу вообще без сети), Service Worker, кэш DNS браузера, кэш DNS ОС, /etc/hosts.
  3. DNS. Рекурсивный запрос к резолверу, дальше корень → TLD → авторитативный (см. схему выше). Обычно по UDP/53, а большой ответ приходит усечённым с флагом TC, и запрос повторяют по TCP. В ответе набор A/AAAA; клиент по Happy Eyeballs пробует v6, а через ~250 мс и v4 (в Go - через 300 мс).
  4. Маршрутизация и ARP. Хост по маске понимает, что адрес чужой, берёт шлюз по умолчанию, узнаёт его MAC по ARP, кладёт IP-пакет в Ethernet-кадр.
  5. TCP handshake. SYN → SYN-ACK → ACK, один RTT. Здесь же согласуются MSS, window scale, SACK. По дороге домашний роутер делает NAT.
  6. TLS handshake. ClientHello с SNI и ALPN, ответ сервера, проверка цепочки сертификатов, вывод общего секрета через ECDHE. В TLS 1.3 это один RTT, при возобновлении с early data - ноль.
  7. HTTP-запрос. GET / HTTP/1.1 плюс заголовки (или HEADERS-фрейм в HTTP/2). На пути стоят CDN, балансировщик, реверс-прокси, и каждый может ответить из кэша.
  8. Ответ и рендер. Браузер строит DOM из HTML и CSSOM из CSS, объединяет в render tree, считает layout (геометрию), paint (пиксели слоёв) и composite (сборку слоёв на GPU). Параллельно preload scanner уже тянет подресурсы, а синхронный <script> без defer/async останавливает парсер.
Чем выделиться на этом вопросе

Помогают три приёма. Назови стоимость в RTT: «до первого байта три круга - TCP, TLS и сам запрос; QUIC схлопывает первые два». Перечисли места, где ответ может прийти раньше: кэш браузера, Service Worker, кэш DNS, CDN edge, кэш реверс-прокси. И скажи, что ломается: DNS отдал протухший IP, у сервера нет промежуточного сертификата, SNI не совпал с именем, MTU меньше 1500, а PMTUD заблокирован файрволом.

Вопросы

6
Суть: каждый уровень решает ровно одну задачу и видит содержимое верхнего как непрозрачные байты. L2 - доставка кадра соседу по MAC, L3 - доставка пакета между сетями по IP без гарантий, L4 - адресация процесса (порт) плюс, у TCP, надёжность и порядок, L7 - смысл данных.

Уровни и что на них живёт

  • L1 физический - вольты, свет, радио; по нему бегут биты.
  • L2 канальный - Ethernet, Wi-Fi (802.11), PPP. Адресует по MAC, передаёт кадры и не выходит за пределы одного широковещательного сегмента. Здесь же VLAN и коммутаторы.
  • L3 сетевой - IP, ICMP, IPsec, протоколы маршрутизации (OSPF, BGP). Адресует по IP, передаёт пакеты. Гарантий никаких, только best effort: пакет может пропасть, задублироваться, прийти не в том порядке. Здесь работают роутеры.
  • L4 транспортный - TCP, UDP, QUIC (формально поверх UDP), SCTP. Здесь появляется порт, то есть адрес конкретного процесса на хосте. TCP добавляет надёжность, порядок, управление потоком и перегрузкой, UDP не добавляет ничего.
  • L5/L6 в OSI - сеансовый и представления (сессии, шифрование, кодировки). В реальном стеке их отдельно нет: TLS формально «L6», а на деле библиотека между TCP и HTTP.
  • L7 прикладной - HTTP, gRPC, DNS, SMTP, SSH, AMQP, Redis-протокол, Postgres wire protocol.

Зачем это знать на практике

Почти любой вопрос про инфраструктуру задают в терминах уровней. L4-балансировщик (LVS/IPVS, AWS NLB, HAProxy в режиме tcp) видит только пятёрку адрес/порт и перекладывает пакеты. Он дешёвый и держит миллионы соединений, но не знает ни URL, ни заголовка Host, ни куки. L7-балансировщик (nginx, Envoy, ALB, Traefik) терминирует TCP и TLS, разбирает HTTP и умеет маршрутизировать по пути, хосту, заголовкам, делать ретраи отдельных запросов и circuit breaking - но ест процессор и добавляет задержку. Из той же оперы правило безопасности «фильтруем на L3/L4 файрволом, на L7 - WAF».

Про инкапсуляцию - обязательно проговорить

На отправке каждый уровень дописывает свой заголовок вокруг данных верхнего: HTTP-текст оборачивается в TCP-сегмент, сегмент в IP-пакет, пакет в Ethernet-кадр. На приёме всё разбирается обратно. Отсюда MTU: в Ethernet-кадр влезает 1500 байт полезной нагрузки, IP-заголовок 20 и TCP-заголовок 20 съедают 40, остаётся MSS 1460. Если у кого-то на пути MTU меньше (VPN, PPPoE, туннели в облаке), а ICMP-сообщения «Fragmentation Needed» режет файрвол, получаешь классический баг: рукопожатие проходит, мелкие запросы работают, а большой POST висит намертво. Это чёрная дыра PMTU.

На чём ловят

«На каком уровне работает TLS?» Правильный ответ не «на шестом», а «формально между транспортом и приложением; в реальном стеке TCP/IP это часть прикладного уровня, отдельного уровня для него нет». «А ARP на каком?» - на стыке L2 и L3: он использует L2-широковещание, чтобы разрешить L3-адрес, поэтому его относят то к L2, то к L2.5.

Суть: маска делит 32 бита адреса на сетевую и хостовую часть; по ней хост решает «сосед по проводу или отдавать шлюзу». NAT - подмена адреса и порта на границе сети, чтобы много приватных адресов вышли в интернет через один публичный.

Маска и подсеть

10.0.5.17/24: 24 бита - сеть, 8 - хост. В адресе сети все хостовые биты нулевые (10.0.5.0), в broadcast единичные (10.0.5.255), полезных адресов 2^8 - 2 = 254. Чем короче префикс, тем больше сеть: в /16 65534 хоста, в /30 два (классический линк между роутерами), /32 означает один конкретный адрес.

$ ipcalc 10.0.5.17/24
Address:   10.0.5.17    00001010.00000000.00000101. 00010001
Netmask:   255.255.255.0 = 24
Network:   10.0.5.0/24
Broadcast: 10.0.5.255
HostMin:   10.0.5.1     HostMax: 10.0.5.254

Маршрут выбирают по правилу longest prefix match: если адрес назначения после наложения маски совпадает с собственной сетью, шлём напрямую соседу (узнав его MAC через ARP), иначе отдаём кадр на MAC шлюза по умолчанию, не трогая IP назначения.

Приватные диапазоны

Диапазоны RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) в интернете не маршрутизируются. Ещё полезно помнить 127.0.0.0/8 (loopback целиком, не только 127.0.0.1), 169.254.0.0/16 (link-local; сюда входит и 169.254.169.254, метадата-сервис в AWS/GCP/Yandex Cloud, откуда при SSRF утекают креды) и 100.64.0.0/10 (CGNAT).

NAT

Почти всегда это NAPT: роутер подменяет src IP:port на свой публичный адрес и свободный порт, запись кладёт в таблицу трансляций и по ней разворачивает обратный трафик. Услышать хотят три следствия:

  • Соединение может инициировать только внутренняя сторона - снаружи записи в таблице нет. Поэтому p2p требует port forwarding, UPnP или hole punching через STUN/TURN.
  • Записи протухают: у UDP обычно через 30–120 с, у TCP через минуты и десятки минут. Из-за этого простаивающее соединение «внезапно» перестаёт работать; лечат это TCP keep-alive с интервалом меньше таймаута NAT.
  • Порты кончаются: за одним публичным IP на одну пару (dst IP, dst port) больше ~64 тысяч одновременных соединений не спрячешь. На NAT-шлюзе перед большим кластером это реальная беда: SNAT port exhaustion в Kubernetes случается сплошь и рядом.
Смежный вопрос: почему IPv6 «не взлетел», хотя адреса кончились

Потому что NAT оказался достаточно хорошим костылём и снял остроту дефицита. Заплатили за это сквозной адресуемостью, а вокруг обхода NAT вырос целый пласт технологий. В IPv6 адресов хватает всем, NAT не нужен, а роль ARP играет NDP поверх ICMPv6.

Суть: клиент задаёт один рекурсивный вопрос резолверу; резолвер делает цепочку итеративных запросов корень → TLD → авторитативный сервер зоны. Всё кэшируется на каждом шаге по TTL, который назначает владелец зоны.

Порядок разрешения имени

  1. Кэш браузера (десятки секунд), затем кэш ОС (systemd-resolved, nscd), затем /etc/hosts - до сети дело может и не дойти.
  2. Stub resolver шлёт вопрос с флагом RD=1 («сделай за меня всю работу») на резолвер из /etc/resolv.conf. Ходит он по UDP/53; если ответ не влез, приходит флаг TC (truncated), и клиент повторяет запрос по TCP/53. С EDNS0 UDP-ответ может быть больше 512 байт.
  3. Резолвер идёт по иерархии: корневой сервер отдаёт NS для com, TLD-сервер отвечает NS для google.com, а авторитативный возвращает саму A-запись с флагом AA=1.
  4. Ответ кладётся в кэши на всём обратном пути на время TTL.

TTL и отрицательное кэширование

TTL задаёт владелец зоны: у A-записей обычно 60–300 секунд, у NS часы. Перед миграцией TTL заранее опускают до 60 секунд, переключают запись, ждут и потом поднимают обратно. NXDOMAIN тоже кэшируется, срок задаёт поле minimum в SOA. Поэтому «создал запись, а её не видно» часто означает, что ты успел спросить имя до создания и отрицательный ответ осел в кэше.

Типы записей

  • A / AAAA хранят IPv4 / IPv6. Несколько A-записей дают round-robin-балансировку «для бедных»: порядок ротируется, но клиент может закэшировать одну и сидеть на ней.
  • CNAME - алиас на другое имя, разрешение продолжается по цепочке. Нельзя на вершине зоны (рядом с SOA и NS), поэтому провайдеры придумали ALIAS/ANAME.
  • MX указывает почтовый сервер с приоритетом (чем меньше число, тем выше приоритет).
  • В TXT кладут произвольный текст: SPF, DKIM, DMARC, подтверждение владения доменом для ACME/Let's Encrypt (_acme-challenge).
  • SRV: _service._proto.name → приоритет, вес, порт и хост. Порт отдают она и записи SVCB/HTTPS (RFC 9460), а на SRV держится service discovery в Consul и headless-сервисы Kubernetes.
  • NS - делегирование зоны, SOA - параметры зоны, PTR - обратное разрешение.
Добивка про Go

Go не кэширует DNS сам: кэш только у ОС и резолвера. А соединение из пула http.Transport о смене DNS не узнаёт вовсе: адрес спрашивают только при dial, и клиент может месяцами держать соединение к IP, который давно выведен из ротации. IdleConnTimeout закрывает только простаивающие соединения; возраст занятых ограничивают обёрткой в DialContext или на сервере. И ещё: у Go два резолвера, чистый Go (сам читает /etc/resolv.conf) и cgo (getaddrinfo, умеет NSS/mDNS), а переключают их через GODEBUG=netdns=go|cgo.

Суть: протокол разрешения IP-адреса в MAC-адрес внутри одного канального сегмента. Запрос широковещательный («у кого 10.0.5.1 - скажите свой MAC»), ответ юникастом, результат кладётся в ARP-кэш.

Решает он вот что: в провод кладут не IP-пакет, а Ethernet-кадр, и адреса у кадра MAC. Значит, перед отправкой хосту нужно знать MAC следующего узла. ARP-запрос уходит на ff:ff:ff:ff:ff:ff, его видят все в сегменте, а отвечает только владелец адреса.

$ ip neigh                 # ARP-кэш в Linux
10.0.5.1   dev eth0 lladdr 02:42:ac:11:00:01 REACHABLE
10.0.5.23  dev eth0 lladdr 02:42:ac:11:00:17 STALE

$ tcpdump -n -i eth0 arp
ARP, Request who-has 10.0.5.1 tell 10.0.5.17, length 28
ARP, Reply 10.0.5.1 is-at 02:42:ac:11:00:01, length 46

Главная мысль ответа

ARP не работает через роутер. Если адрес назначения в другой сети, хост никогда не спрашивает про него ARP, он спрашивает MAC своего шлюза. По дороге IP-адреса источника и назначения не меняются (пока нет NAT), а MAC-адреса переписываются на каждом роутере. Так коротко и объясняют разницу между L2 и L3.

Что ещё бывает вокруг ARP

  • При Gratuitous ARP хост сам объявляет «этот IP теперь мой». Так переезжает виртуальный IP при failover (keepalived/VRRP), чтобы коммутаторы и соседи обновили кэш.
  • Proxy ARP - роутер отвечает за чужой адрес.
  • ARP spoofing возможен, потому что у протокола нет аутентификации: кто угодно в сегменте может ответить «это я» и устроить MITM. Защищаются Dynamic ARP Inspection, статическими записями, 802.1X.
  • В IPv6 ARP заменён на NDP поверх ICMPv6 (Neighbor Solicitation/Advertisement) и использует multicast вместо broadcast.
Суть: порт - это 16-битное число в заголовке TCP/UDP, метка «кому отдать». Сокет - объект ядра с буферами и состоянием, идентифицируемый пятёркой {протокол, локальный IP, локальный порт, удалённый IP, удалённый порт}.

Отсюда вывод: сервер обслуживает тысячи клиентов на одном порту. Порт 443 один, а сокетов столько, сколько соединений, и ядро различает их по пятёрке. Классический неверный ответ на собесе: «на каждого клиента сервер выделяет новый порт» - нет, новый порт выделяет клиент, а серверный остаётся тем же.

ln, _ := net.Listen("tcp", ":8080")   // один слушающий сокет
for {
    conn, err := ln.Accept()          // каждый Accept -> новый fd на том же :8080
    if err != nil { continue }
    go handle(conn)                   // conn.RemoteAddr() у всех разный
}

Два типа сокетов у TCP

  • Слушающий: пятёрка неполная ({tcp, *, 8080, *, *}), у него две очереди - SYN queue (полуоткрытые) и accept queue (готовые, длина = backlog, ограничена net.core.somaxconn). Когда accept queue переполнена, SYN молча отбрасываются, а клиент ловит «непонятные» таймауты.
  • Установленный: полная пятёрка, свои буферы приёма и отправки в ядре.

Где реально кончаются ресурсы

  • Сервер упирается в файловые дескрипторы (ulimit -n), память ядра под буферы и длину accept-очереди, но не в порты.
  • Клиент упирается в эфемерные порты: net.ipv4.ip_local_port_range = 32768 60999, то есть ~28 тысяч. Лимит считается на пару (dst IP, dst port): к одному бэкенду 28 тысяч, к десяти разным уже 280 тысяч. Этот лимит и выжигает TIME_WAIT, когда нет keep-alive.
  • Порты 0–1023 привилегированные: нужен root или CAP_NET_BIND_SERVICE.
Добивка

Сокеты бывают не только сетевые. У unix domain socket (net.Dial("unix", "/var/run/x.sock")) тот же API, но нет сетевого стека, портов и checksum: на одной машине он ощутимо быстрее, а доступ к нему разграничивают правами файловой системы. Это и есть ответ на «как ускорить локальный вызов, если оба процесса на одном хосте».

Суть: отвечать блоками и в каждом называть, во что уходит время. Скелет: разбор ввода → кэши → DNS → маршрутизация и ARP → TCP-рукопожатие → TLS-рукопожатие → HTTP-запрос → прокси/CDN/балансировщик → ответ → парсинг и рендер. До первого байта на холодную - три RTT.

1. Разбор строки и HSTS

Браузер решает, это URL или поисковый запрос, нормализует (punycode для кириллицы, схема, порт по умолчанию). Затем проверяет HSTS: если домен есть в preload-списке или в локальном кэше HSTS, схема принудительно становится https ещё до какого-либо трафика. Так браузер защищается от downgrade-атаки, и редиректа 301 не будет.

2. Кэши: сети может и не быть вообще

По очереди: HTTP-кэш браузера (свежий ответ отдаётся мгновенно), Service Worker (может ответить из своего кэша офлайн), кэш DNS браузера, кэш DNS операционной системы, /etc/hosts. Скажи это вслух: здесь впервые отвечают на вопрос «почему быстро/медленно».

3. DNS

Stub resolver шлёт рекурсивный запрос (RD=1) резолверу по UDP/53. Резолвер обходит иерархию: корень → NS зоны com → NS зоны google.com → авторитативный сервер отдаёт A/AAAA с TTL. Если ответ не влез в датаграмму, ставится флаг TC и запрос повторяют по TCP. Клиент получает набор адресов и по Happy Eyeballs (RFC 8305) начинает соединяться по IPv6, а через четверть секунды и по IPv4, оставляя того, кто ответил первым. В жизни ответ для google.com придёт из кэша резолвера за единицы миллисекунд, а адрес будет ближайшим GeoDNS/anycast-узлом.

4. Маршрутизация, ARP, NAT

Хост накладывает маску: адрес не свой - берём default gateway. ARP даёт MAC шлюза, IP-пакет уходит в Ethernet-кадре. Домашний роутер делает NAPT: подменяет src на публичный адрес и свободный порт. Дальше пакет идёт по цепочке роутеров, каждый уменьшает TTL и переписывает MAC-адреса, а IP остаются прежними.

5. TCP-рукопожатие - 1 RTT

SYN (со своим ISN, опциями MSS, window scale, SACK permitted, timestamps) → SYN-ACK → ACK. После третьего пакета сокет в состоянии ESTABLISHED, и данные можно слать прямо с ним. Если сервер за CDN и находится в соседней стране, это уже 20–40 мс.

6. TLS-рукопожатие - 1 RTT (TLS 1.3)

ClientHello везёт версии, шифронаборы, SNI (по нему сервер выбирает сертификат, когда на одном IP много доменов), ALPN (h2, http/1.1; h3 бывает только в TLS внутри QUIC) и уже свою долю ключа ECDHE. Сервер отвечает ServerHello, сертификатом и цепочкой, подписью и Finished - дальше трафик шифруется. Клиент проверяет цепочку до доверенного корня, срок действия, соответствие имени в SAN, отзыв (OCSP stapling). Асимметрика нужна, только чтобы аутентифицировать сервер и договориться об общем секрете, а сами данные шифруются симметрично (AES-GCM или ChaCha20-Poly1305). При возобновлении сессии по билету бывает 0-RTT.

7. HTTP-запрос и путь до приложения

Браузер шлёт GET / HTTP/1.1 с Host, User-Agent, Accept, Accept-Encoding, Cookie, а если ALPN договорился о h2, то HEADERS-фрейм с заголовками, сжатыми HPACK. По пути обычно стоят CDN edge (может ответить из кэша), L4-балансировщик, реверс-прокси/L7-балансировщик, который терминирует TLS и выбирает бэкенд, и только потом само приложение. Каждый прокси добавляет X-Forwarded-For/Forwarded и, если всё сделано правильно, X-Request-ID для сквозной трассировки.

8. Ответ и рендер

Приходят статус, заголовки и тело (обычно Transfer-Encoding: chunked или DATA-фреймы в h2, часто со сжатием gzip/br). Браузер потоково парсит HTML в DOM и CSS в CSSOM, объединяет их в render tree, считает layout (геометрию), paint (растеризацию слоёв) и composite (сборку слоёв на GPU). Параллельно preload scanner уже тянет подресурсы. Синхронный <script> без defer/async блокирует парсер, CSS блокирует рендер, и отсюда растут все метрики FCP/LCP.

Чем добить, чтобы ответ выделялся
  • Стоимость в RTT: «на холодную до первого байта три круга: TCP, TLS, сам запрос. QUIC схлопывает первые два в один, а по билету в ноль».
  • Где ответ может прийти раньше: кэш браузера, Service Worker, кэш DNS, CDN edge, кэш реверс-прокси.
  • Что ломается: протухший IP в кэше при живом keep-alive; сервер не отдал промежуточный сертификат (в браузере работает, в curl из контейнера - нет); имя не совпало с SAN; MTU меньше 1500 и PMTUD зарезан файрволом; IPv6-адрес есть, а связности нет - спасает Happy Eyeballs.

Что стоит запомнить

  • DNS превращает имя в набор адресов; IP доставляет пакеты между сетями; порт выбирает транспортную конечную точку на узле.
  • ARP разрешает локальный IPv4 адрес следующего узла в MAC, а не адрес удалённого сервера через интернет.
  • TCP-соединение различают локальный/удалённый IP и порт; несколько соединений могут слушаться на одном server port.

Частые ошибки

  • Не смешивай DNS имя, IP-адрес, порт и сокет: это разные уровни адресации.
  • NAT меняет адреса/порты на границе сети и требует состояния соответствия для ответного трафика.
  • DNS lookup не гарантирует, что найденный адрес доступен или TLS-сертификат ему соответствует.

Самопроверка

Сначала реши задачу самостоятельно, затем открой ответ и сравни ход рассуждений.

Чтение кода

1. Разбери результат DNS

Что именно гарантирует вызов и произойдёт ли TLS handshake?

d := net.Dialer{Timeout: 3 * time.Second}
conn, err := d.DialContext(ctx, "tcp", "api.example:443")
Ответ и разбор

Из snippet нельзя узнать, сколько адресов вернёт resolver и какие именно: DNS lookup зависит от текущего resolver-ответа. DialContext разрешает имя при необходимости и пытается установить TCP-соединение к адресу на порту 443; при успешном вызове вернётся TCP conn. Сам вызов не выполняет TLS handshake и не отправляет HTTP-запрос. Таймаут Dialer и ctx ограничивают установление соединения.

Код-ревью

2. Найди ошибку модели сокета

Автор говорит: "На :8080 может быть только одно соединение, потому что порт уже занят слушателем". Проверь код и исправь объяснение.

ln, _ := net.Listen("tcp", ":8080")
for {
    conn, err := ln.Accept()
    if err != nil { continue }
    go handle(conn)
}
Ответ и разбор

Listen занимает локальный listening socket на :8080, а Accept создаёт отдельное установленное соединение с собственным удалённым адресом/портом. Один listener принимает много соединений. В production также нужно ограничить обработку ошибок, закрывать соединения и завершать accept loop при shutdown.

Применение

3. Проследи Enter до первого запроса

Пользователь вводит https://shop.example:8443/orders. Перечисли основные сетевые шаги до получения HTTP-ответа и назови два независимых места, где запрос может завершиться ошибкой.

Ответ и разбор

Клиент разбирает URL, получает адрес через DNS, выбирает маршрут и next hop, устанавливает TCP к адресу на порту 8443, выполняет TLS handshake с проверкой сертификата/имени, затем отправляет HTTP-запрос и читает ответ. Ошибки возможны, например, при DNS lookup, маршруте/таймауте TCP, проверке TLS или на уровне HTTP; успех предыдущего шага не гарантирует следующий.

Применение

4. Посчитай сокетные адреса

К одному server listener 10.0.0.5:8080 подключились два клиента: 192.0.2.4:51000 и 192.0.2.7:51001. Запиши 4-кортеж каждого TCP-соединения и объясни, что у них общее.

Ответ и разбор

Кортежи: (192.0.2.4, 51000, 10.0.0.5, 8080) и (192.0.2.7, 51001, 10.0.0.5, 8080). У них общий адрес/порт сервера, но разные удалённые пары, поэтому ядро различает соединения, принятые одним listener.