Когда мы разбирали слои сети, было два ключевых уровня: сетевой, где живёт IP, и транспортный, где живут порты. Теперь спустимся к ним вплотную и разберёмся с самым практическим вопросом: как вообще пакет находит нужную машину в интернете, а на этой машине — нужный сервис. Ответ складывается из трёх вещей — IP-адреса, порта и хитрости под названием NAT, которая позволяет целой сети прятаться за одним внешним адресом.
Это ровно те понятия, о которые backend-разработчик спотыкается каждый день: сервис не хочет слушать снаружи, «port already in use», под в Kubernetes получил какой-то странный адрес. Разберёмся по порядку.
Вот вся картина целиком — адрес, порт и NAT в одной схеме; дальше разберём каждую часть по отдельности.
Что такое IP-адрес
IP-адрес — это адрес машины в сети, аналог почтового адреса дома. Чтобы пакет доехал от отправителя к получателю, у получателя должен быть адрес, по которому его найдут промежуточные узлы.
Самый привычный вид — IPv4: четыре числа от 0 до 255 через точку, например 192.168.1.10. Каждое число — это один байт, всего четыре байта, то есть примерно четыре миллиарда возможных адресов. Звучит много, но интернет давно перерос эту цифру — устройств в мире куда больше.
Именно поэтому появился IPv6: адреса вида 2001:0db8:85a3::8a2e:0370:7334, длиннее и записанные шестнадцатеричными группами через двоеточие. Их запас практически неисчерпаем. IPv6 медленно вытесняет IPv4, но на практике вы ещё долго будете встречать оба, и IPv4 остаётся тем, с чем сталкиваешься чаще всего. Дальше в статье примеры будут на IPv4. Адрес, порт, сокет в IPv6 устроены так же — а вот вторая половина статьи, про приватные диапазоны и подмену адресов, выросла ровно из нехватки адресов IPv4. В IPv6 их хватает каждому устройству с запасом: домашней подмены адресов там обычно нет вовсе, и машина адресуема снаружи напрямую — закрывает её межсетевой экран, а не табличка в роутере.
Приватные и публичные адреса
Не все адреса одинаковы. Есть публичные — уникальные во всём интернете, по ним машину видно снаружи. И есть приватные — специально зарезервированные диапазоны, которые используются только внутри локальных сетей и в интернете не маршрутизируются.
Приватных диапазонов три, их полезно узнавать в лицо:
| Диапазон | Где встретишь |
|---|---|
10.0.0.0 – 10.255.255.255 | Облачные сети и кластеры контейнеров |
172.16.0.0 – 172.31.255.255 | Сети Docker по умолчанию |
192.168.0.0 – 192.168.255.255 | Домашний роутер, офисный Wi-Fi |
Если вы видите адрес, начинающийся на 192.168. или 10. — это почти наверняка внутренняя сеть: домашний роутер, офис, кластер контейнеров. Ваш ноутбук дома получил от роутера что-то вроде 192.168.1.42 — это приватный адрес, снаружи по нему до вас не достучаться. А вот у роутера есть один публичный адрес, выданный провайдером, и именно он виден всему интернету.
Такое разделение экономит дефицитные публичные адреса: тысячи устройств внутри сети обходятся приватными адресами, а наружу выходят через один общий публичный. Как именно — разберём в разделе про NAT.
Маска и подсеть на пальцах
Сервис в одной подсети облака не видит базу в другой, а в правиле файрвола надо перечислить, каким адресам можно: не списком из двухсот адресов, а одной записью вроде 10.0.1.0/24. Для этого и служит маска подсети: она говорит, где заканчивается «моя» сеть и начинается остальной мир.
Проще всего думать так: адрес делится на две части — «номер сети» и «номер устройства в этой сети». Маска говорит, где проходит граница. Запись 192.168.1.0/24 означает: первые 24 бита (то есть 192.168.1) — это номер сети, а последний байт — номера устройств. Значит, в этой сети живут адреса от 192.168.1.1 до 192.168.1.254, и все они — «соседи», которые общаются напрямую.
Маска /24 говорит, что первые 24 бита — это адрес сети, а на устройства остаётся последний байт. Отсюда и знакомые 254 адреса в домашней сети.
Практический смысл простой: устройства из одной подсети видят друг друга напрямую, а чтобы достучаться до другой подсети или до интернета, пакет идёт через шлюз (обычно роутер). Число после слэша (/24, /16) — это просто размер сети: чем меньше число, тем больше адресов помещается.
Откуда берутся «1…254», а не «0…255»: два адреса в каждой подсети заняты служебно. Самый младший (192.168.1.0 для маски /24) — это адрес самой сети, им обозначают подсеть целиком, например в таблице маршрутов. Самый старший (192.168.1.255) — широковещательный: пакет на него получают все машины подсети сразу. Поэтому на машины остаётся 254 адреса, а не 256.
Тот же счёт работает для любой маски: /25 даёт 128 адресов, из них 126 доступных; /30 — четыре адреса, два доступных (такие подсети используют для соединения двух маршрутизаторов); /31 — особый случай для тех же пар без служебных адресов. Отсюда и практическая проверка: если в подсети /24 «кончились адреса» при двадцати машинах, дело не в маске, а в том, что кто-то раздаёт их пулом с запасом.
Что такое порт
IP-адрес приводит пакет к машине. Но на одной машине крутится куча программ одновременно: веб-сервер, база данных, SSH. Кому из них отдать пришедший пакет? Вот тут и нужен порт.
Порт — это число от 1 до 65535, которое указывает на конкретный сервис на машине. Ноль в этом ряду не адрес, а просьба: попросили порт 0 — значит «дай любой свободный», номер выберет операционная система. Ниже увидим, где это пригождается. Если IP — это адрес дома, то порт — номер квартиры. Пакет с адресом 192.168.1.10:443 означает «дом 192.168.1.10, квартира 443».
Часть портов закреплена по договорённости за стандартными сервисами — их называют well-known:
| Порт | Сервис |
|---|---|
80 | HTTP |
443 | HTTPS |
5432 | PostgreSQL |
6379 | Redis |
22 | SSH |
Поэтому в браузере достаточно написать https://example.com — порт 443 подставляется автоматически, все знают, что HTTPS живёт там.
А когда ваш сервис сам подключается куда-то как клиент, ему тоже нужен порт — но конкретный номер не важен, поэтому операционная система выдаёт первый свободный из высокого диапазона (на Linux по умолчанию это 32768–60999, то есть примерно 28 тысяч штук — цифра, в которую упираются при большом числе исходящих соединений). Такие временные порты называют эфемерными: соединение закрылось — порт вернулся в общий котёл.
Сокет: полный адрес сервиса
Соединив адрес и порт, получаем сокет — полный адрес конкретного сервиса: IP:порт. Именно пара 192.168.1.10:5432 однозначно указывает «PostgreSQL вот на этой машине». Только IP мало (непонятно, какой сервис), только порт мало (непонятно, на какой машине) — нужны оба.
Сокет это пара: слева адрес приводит пакет к машине, справа порт приводит его к нужной программе на ней.
Отдельно стоит запомнить localhost и его адрес 127.0.0.1. Это специальный адрес «сам себя»: пакет на 127.0.0.1 никуда не уходит по сети, а возвращается той же машине. Удобно для локальной разработки — база на localhost:5432 означает «PostgreSQL прямо на моём компьютере».
NAT: как за одним адресом прячется целая сеть
Вернёмся к загадке: дома у вас десять устройств с приватными адресами 192.168.1.x, а публичный адрес от провайдера — один. Как они все выходят в интернет через него?
Роутер запоминает пару «приватный адрес и порт ↔ публичный адрес и порт»: по ней ответ находит нужное устройство. А пакету снаружи, для которого строки в таблице нет, роутеру отдать некуда — потому машина за NAT и недоступна, пока сама не начала разговор.
Механизм называется NAT (Network Address Translation) — трансляция адресов. Роутер, когда пакет от вашего ноутбука уходит наружу, подменяет приватный адрес отправителя на свой единственный публичный и запоминает в табличке: «ответ на этот разговор надо вернуть ноутбуку». Когда приходит ответ — роутер смотрит в табличку и разворачивает подмену обратно.
Снаружи весь ваш дом выглядит как один адрес. NAT — это то, что позволило IPv4 дожить до сегодня, несмотря на дефицит адресов. Побочный эффект: устройство за NAT само по себе недоступно снаружи — к нему нельзя подключиться извне, пока оно первым не начало разговор. Именно поэтому домашний компьютер не торчит в интернете открытым, а чтобы сервис был доступен снаружи, его размещают на машине с публичным адресом.
Проброс порта и почему он не всегда помогает
Первый вопрос читателя здесь — «а можно сделать так, чтобы снаружи всё-таки достучались». Можно: это проброс порта (переадресация). На маршрутизаторе настраивают правило «всё, что пришло на внешний адрес и порт 2222, отправлять на 192.168.1.50 порт 22» — и внутренняя машина становится доступна извне по внешнему адресу.
Работает это ровно до одного условия: внешний адрес маршрутизатора должен быть настоящим публичным. И вот здесь у домашних подключений сегодня чаще всего проблема.
CGNAT: наружного адреса у вас нет
Публичных адресов не хватает на всех, поэтому провайдеры давно применяют вторую ступень трансляции — так называемый CGNAT (трансляция на уровне провайдера). Работает она так же, как NAT в вашем маршрутизаторе, только на стороне провайдера и для тысяч абонентов сразу.
Признак узнаётся мгновенно: внешний адрес, который показывает маршрутизатор, начинается на 100.64. — 100.127. (диапазон 100.64.0.0/10, выделенный специально для этого) или вообще на 10./192.168.. То есть адрес маршрутизатора тоже приватный, а настоящий публичный — у оборудования провайдера, и он общий с другими абонентами.
Отсюда и ответ на вопрос «почему коллега из другого города не может достучаться до моей домашней машины, хотя я настроил проброс порта»: проброс работает на вашем маршрутизаторе, а входящее соединение до него не доходит — оно останавливается на трансляции провайдера, где правила для вас никто не настраивал. Двойной NAT, две ступени, и вторая вам не подчиняется.
Что с этим делают:
- Попросить публичный адрес у провайдера (обычно платная услуга) — тогда проброс начинает работать.
- Соединение наружу вместо входящего. Домашняя машина сама устанавливает соединение с сервером в интернете, и трафик идёт по нему в обе стороны: так работают туннели, службы удалённого доступа и любые «агенты», которые подключаются сами. NAT не мешает исходящим соединениям, и это главное свойство, на котором всё держится.
- IPv6. Адресов в нём столько, что трансляция не нужна: каждое устройство получает свой адрес, и входящие соединения работают напрямую — если и провайдер, и обе стороны его поддерживают. Практическая ловушка: у вас может быть IPv6, а у коллеги нет, и тогда связи снова нет.
Эфемерные порты можно исчерпать
Цифра «примерно 28 тысяч» — это не абстракция, а предел, в который упираются в проде. Каждое исходящее соединение занимает один порт на стороне клиента, и порт освобождается не сразу после закрытия: соединение проходит через состояние ожидания (TIME_WAIT), которое длится порядка минуты — так операционная система страхуется от опоздавших пакетов прошлого соединения.
Отсюда арифметика: сервис, который открывает 500 новых соединений в секунду к одному адресу и порту, через минуту накопит около 30 тысяч занятых портов — и новые соединения начнут падать с ошибкой «не удалось назначить адрес». Симптом узнаваемый: приложение работает, а потом резко перестаёт открывать соединения, причём к одному конкретному получателю.
Лечение не в увеличении диапазона портов, а в том, чтобы не открывать соединения заново: постоянные соединения и пул (о них в статье про соединения и пулы). Диапазон и время ожидания тоже настраиваются, но это лечение симптома. Посмотреть картину можно одной командой — ss -tan state time-wait | wc -l.
0.0.0.0 против 127.0.0.1: запущен, но недоступен
Когда сервис слушает 127.0.0.1:8080, к нему можно подключиться только с той же машины — снаружи и из других контейнеров он невидим. Чтобы принимать соединения отовсюду, сервис слушает 0.0.0.0:8080 («все сетевые интерфейсы»). Классическая ошибка новичка: сервис в контейнере слушает 127.0.0.1, а снаружи к нему не достучаться — потому что для внешнего мира это «сам себя» внутри контейнера.
Адрес, названный при запуске, решает, кто сможет подключиться: loopback отвечает только своей машине, а 0.0.0.0 занимает все интерфейсы сразу.
Где это применяется
Для backend всё это — не теория, а ежедневные грабли и решения.
- «Port already in use». Занят на самом деле не порт, а пара «адрес и порт»: на
127.0.0.1:8080и на10.0.0.5:8080спокойно уживаются два разных процесса. А вот0.0.0.0:8080забирает порт сразу на всех интерфейсах и конфликтует с обоими. Внутри одной такой пары операционная система второму процессу встать не даст. (Исключение есть: с флагомSO_REUSEPORTнесколько процессов могут слушать один порт, и ядро само распределяет между ними соединения — так масштабируют nginx и подобные серверы. Но обычное приложение этим не пользуется.) Если при запуске сервиса вылезаетAddress already in use: 8080, значит пару уже держат — другой экземпляр вашего же приложения, забытый процесс или чужой сервис. Бывает и без живого процесса: соединения только что убитого сервера ещё около минуты висят в состоянииTIME-WAIT(его разбираем в диагностике), и порт из-под них не отдают — если сокет не попросил об этом флагомSO_REUSEADDR. В Java, Go и Node просить не нужно:ServerSocket,net.Listenиnet.createServerвыставляют этот флаг сами, потому сервис и перезапускается без обид; а вот сервер, написанный руками на C или на голых сокетах Python, спотыкается здесь регулярно. Лечится сменой порта или остановкой того, кто его держит. - IP пода в Kubernetes. В кластере каждый под получает свой приватный IP из внутренней подсети — эфемерный: под пересоздался, адрес сменился. Поэтому обращаться к поду напрямую по IP нельзя, для этого есть стабильные имена сервисов, за которыми прячется меняющийся набор адресов. Про это — раздел Kubernetes и статья про балансировщики.
Где спотыкаются начинающие:
- Путают IP-адрес и порт. IP приводит к машине, порт — к сервису на ней. «Не могу подключиться» — всегда стоит уточнить, что именно неверно: адрес машины или номер порта.
- Слушают
127.0.0.1там, где нужно0.0.0.0. Сервис вроде запущен, но снаружи или из соседнего контейнера недоступен — почти всегда дело в этом. - Думают, что приватный адрес виден из интернета. По
192.168.x.xили10.x.x.xснаружи достучаться нельзя — это внутренние сети, спрятанные за NAT. - Считают IP пода постоянным. В Kubernetes адрес пода живёт ровно до пересоздания — привязываться к нему нельзя.
Глубже: IPv6 и CGNAT: почему домашнюю машину не видно даже с пробросом портарасширенное
Раздел про NAT объясняет, как за одним публичным адресом прячется сеть, и оставляет впечатление, что проброс порта на роутере открывает домашнюю машину снаружи. Часто он не работает, и причина в том, что NAT стоит дважды.
Публичных адресов IPv4 не хватило ещё в 2011 году, и провайдер выдаёт домашнему роутеру не публичный адрес, а адрес из диапазона 100.64.0.0/10, зарезервированного как раз для этого. Дальше провайдер ставит свой NAT, на тысячи абонентов, и уже он выходит в интернет с одним публичным адресом. Это CGNAT, NAT уровня оператора. Ваш роутер честно пробросил порт на себя, но до роутера снаружи никто не доходит: у него нет публичного адреса, а провайдерский NAT входящие соединения не принимает. Узнать просто: адрес WAN на роутере начинается на 100.64–100.127 или на 10., а сайт «мой IP» показывает другой адрес. Обходы: платный публичный адрес у провайдера, туннель к серверу с публичным адресом (WireGuard, ngrok-подобные сервисы) или IPv6.
В IPv6 адресов столько, что NAT не нужен: каждое устройство получает публичный адрес, и приватных диапазонов с трансляцией в нём нет. «Спрятанность за NAT» перестаёт быть защитой, которой она никогда и не была задумана, и её роль целиком выполняет межсетевой экран на роутере, по умолчанию закрывающий входящие. Что ещё меняется: адрес записывается шестнадцатеричными группами через двоеточие, в URL его берут в квадратные скобки, http://[2001:db8::1]:8080/; у машины несколько адресов сразу (локальный fe80::, глобальный, временные для исходящих); DNS отдаёт AAAA, и клиент с обоими стеками пробует IPv6 первым и откатывается на IPv4, если тот не отвечает, что даёт странные задержки при полусломанном IPv6.
Для бэкенда IPv6 всплывает в трёх местах. Сервис, слушающий 0.0.0.0, слушает только IPv4; чтобы принимать оба, слушают :: (у большинства платформ это включает и IPv4). Проверка «адрес из приватного диапазона» по трём диапазонам IPv4 не отсеет fc00::/7 и ::1, и это дыра в защите вебхуков и прокси. И облачные балансировщики уже давно принимают IPv6 снаружи, отдавая сервису IPv4 внутри, поэтому адрес клиента в логах бывает и таким, и таким, а поля в базе под адрес должны вмещать 45 символов или тип inet.
Коротко
- IP-адрес адресует машину, порт адресует программу на ней; сокет это пара адрес и порт.
- Приватные диапазоны
10.,172.16–31.,192.168.не маршрутизируются в интернете; наружу сеть ходит через NAT с одним публичным адресом. 0.0.0.0слушает все интерфейсы,127.0.0.1только себя; в контейнере это разница между «доступен» и «не доступен».- Домашнюю машину не видно даже с пробросом порта из-за CGNAT у провайдера (
100.64.0.0/10); в IPv6 NAT нет, защиту даёт межсетевой экран, слушать нужно::, а проверки приватных диапазонов дописывать подfc00::/7и::1. - В подсети два адреса служебные — сети и широковещательный, поэтому в /24 доступны 254 адреса, а не 256.
- Проброс порта работает только при настоящем публичном адресе; у домашних подключений он часто из диапазона
100.64.0.0/10(CGNAT), и тогда входящее соединение не доходит — нужен публичный адрес, туннель наружу или IPv6. - Эфемерные порты кончаются из-за состояния
TIME_WAITна минуту: 500 новых соединений в секунду к одному получателю исчерпают диапазон, и лечится это постоянными соединениями, а не расширением диапазона.
Что почитать дальше
Теперь, когда понятно, как пакет находит машину и сервис, логично разобраться, как между ними устанавливается сам разговор.
- TCP и UDP - чем надёжное соединение отличается от «выстрелил и забыл» и когда какой протокол выбирают.
- Жизненный цикл соединения - как соединение открывается, живёт и закрывается и почему их держат в пуле.
- DNS - как имена вроде
example.comпревращаются в адреса, чтобы не запоминать их руками. - Сетевая инфраструктура в облаке - те же приватные сети и подмена адресов, собранные в подсети и шлюзы.