Если вы полный ноль в интернет-технологиях, и хотите получить общее понимание Интернета, прочитав всего одну статью, то эта статья — для вас.
Здесь вы узнаете о 4 уровнях модели TCP/IP. О том, что такое MAC‑адрес и IP-адрес, и зачем нам 2 типа цифровых адресов. Как работает DNS. Зачем нужны коммутаторы и роутеры. Как работает NAT. Как устанавливается защищённое соединение. Что такое инфраструктура открытых ключей, и зачем нужны TLS-сертификаты. Чем отличаются три версии протокола HTTP. Как происходит HTTP-аутентификация. И в конце будет несколько слов о VPN.
4 Уровня модели TCP/IP
Подключив несколько компьютеров к одному свитчу, вы получите локальную сеть. Это максимально простая сеть с плоской структурой, в которой не надо думать ни о каких сложных алгоритмах маршрутизации. На уровне локальной сети работает канальный (четвёртый, т. е. самый низкий) уровень модели TCP/IP (англ. — Network Access Layer). Канальный уровень используется для физической доставки данных. Для того чтобы доставить данные на другое устройство в сети Ethernet (или WiFi), вашему компьютеру нужно знать MAC-адрес этого устройства. Ему нужно сформировать сообщение (например, Ethernet-кадр), положить туда нужные данные, указать в нём MAC-адрес получателя и отправить это сообщение по Ethernet-кабелю. MAC-адрес — это уникальный цифровой идентификатор сетевого устройства, состоящий из 6 октетов, например 00-50-B6-5B-CA-6A.
Интернет — это сложная и запутанная структура, состоящая из множества локальных сетей. Подход с передачей данных по MAC-адресу не масштабируется на такую большую и сложную сеть, поэтому данные в ней нужно маршрутизировать. За это отвечает третий уровень модели TCP/IP — межсетевой, или Internet-уровень. На этом уровне данные заворачиваются в IP-пакеты, и в каждом пакете указывается IP-адрес, на который он должен быть доставлен. Чтобы отправить IP-пакет получателю, который не находится в вашей локальной сети, вам нужно передать этот пакет по цепочке устройств. Поскольку физическая доставка данных осуществляется только на канальном уровне, вам нужно спуститься на канальный уровень и отправить данные на следующее устройство в этой цепочке, которое имеет доступ к другой локальной сети. Потом вам надо снова подняться на сетевой уровень и решить, на какое следующее устройство должен быть доставлен этот пакет. Затем вы опять спускаетесь на 4 уровень, передаёте данные на следующее устройство, и повторяете до тех пор, пока ваш пакет не пройдёт через все промежуточные локальные сети и не достигнет того устройства, для которого он предназначен. На каждом промежуточном устройстве принимается решение, с учётом IP-адреса, на какое следующее устройство должен быть доставлен пакет, с тем чтобы выбрать более-менее быстрый маршрут.
Далее идёт второй, транспортный уровень (Transport Layer), на котором работают транспортные протоколы, такие как TCP, UDP, TLS и QUIC. Они позволяют обмениваться данными не между устройствами, а между приложениями, а точнее — между портами. Происходит это так: приложение просит операционную систему выделить для него, скажем, 80-й порт TCP. Если ОС соглашается, то приложение получает возможность слушать этот порт. Если на это устройство приходит TCP-пакет, в котором указан номер порта 80, то ОС даёт приложению сигнал, что на данном порте появились новые данные, и приложение получает возможность прочитать эти данные из соответствующего буфера. Если придёт UDP-пакет, в котором указан порт 80, то приложение ничего о нём не узнает, потому что у операционной системы есть отдельная таблица сокетов для UDP и отдельная — для TCP.
Работая с протоколом TCP, программисту вообще не нужно думать о разбиении данных на TCP-пакеты, IP-пакеты и Ethernet-кадры, и о том что эти пакеты могут отправляться по разным физическим маршрутам и теряться по пути. Он просто устанавливает TCP-соединение между двумя приложениями на разных устройствах, загружает массив байтов в TCP-сокет на одном устройстве и получает точно такой же массив байтов из TCP-сокета на другом устройстве.
IP-пакеты могут теряться из-за помех, переполнения буферов сетевых устройств, препятствий на пути радиоканала, повреждённых кабелей и так далее. И никакие протоколы сетевого и канального уровней никак не борются с этой проблемой, поскольку это не их зона ответственности. Повторная отправка потерянных пакетов, если это необходимо, происходит на транспортном уровне — например, этим занимается протокол TCP, потому что TCP — это протокол для надёжной передачи данных. TCP также восстанавливает порядок данных, потому что IP-пакеты вполне могут быть отправлены в одном порядке, а получены в другом. UDP ничего не делает для решения этих проблем, но зато он более быстрый.
Протокол TLS работает поверх протокола TCP, поэтому он тоже обеспечивает надёжную доставку данных, но в добавок к этому он обеспечивает шифрование и позволяет установить защищённый канал связи. Раньше для этого использовался протокол SSL, но он безнадёжно устарел.
И наконец, первый, прикладной уровень модели TCP/IP (англ. — Application Layer) — это тот уровень, на котором существуют известные всем протоколы HTTP, HTTPS, FTP (для передачи файлов), SMTP (для электронной почты), DNS и пр. Этот уровень вообще не про то, как доставлять данные. Он про то, как их структурировать и парсить. Конечно, это очень удобно, что вы можете создать TCP-соединение, отправить по нему массив байтов и получить этот массив на другом устройстве без всяких потерь, но как вы будете интерпретировать эти байты? Как вы вообще поймёте, где начинается и где заканчивается сообщение, записанное этими байтами? Для этого нужны соглашения о структуре передаваемых сообщений и о порядке обмена сообщениями. И если вы хотите обмениваться сообщениями с клиентами и серверами по всему миру, для которых вы не писали ПО, то вам нужно использовать международные стандарты структуризации этих сообщений.
Один из этих стандартов — это протокол HTTP/1.1. HTTP-запрос — это просто текст, который должен начинаться со стартовой строки (метод путь HTTP/Версия). Далее идут стандартные заголовки, такие как Host (доменное имя ресурса, которому предназначен запрос), User-Agent (описание ПО, которое сформировало запрос), Connection (следует ли оставлять TCP соединение открытым), Accept-Language (предпочитаемые языки ответа) и пр. Затем идёт необязательное тело запроса, отделённое пустой строкой от заголовка. Оно используется для отправки полезных данных. HTTP ответ состоит из статусной строки, заголовков и тела ответа. HTTP протокол 1-й версии устанавливает строгую последовательность запросов и ответов в рамках одного соединения. Клиент не может отправить следующий запрос, пока не получит ответ на предыдущий, а сервер вообще не может отправить клиенту запрос, только ответ. Такого рода регламент тоже устанавливается протоколами прикладного уровня.
Подключение к сети
Ethernet-кабель для вашего компьютера — это просто некий интерфейс, из которого в любое время может вывалиться структурированная последовательность байтов, и в который можно загрузить другую последовательность байтов, снабжённую MAC-адресом получателя, и рассчитывать на то, что скорее всего эта последовательность будет доставлена по назначению. Когда вы подключаете Ethernet-кабель к своему компьютеру, он это чувствует и понимает, что перед ним потенциальная возможность выйти в cеть, но для этого нужно, как минимум, обзавестись IP-адресом, поэтому он отправляет по Ethernet-соединению DHCP-запрос, который представляет собой сообщение следующего содержания: «устройство с MAC-адресом таким-то просит присвоить ему IP-адрес».
В предыдущем разделе я рассказывал, что для физической доставки данных в локальной сети используется MAC-адрес. Так на какой MAC-адрес отправляется DHCP-запрос? Ведь ваш компьютер ничего не знает о других устройствах в локальной сети, к которой его только что подключили. Соответственно, никаких MAC-адресов в его памяти нет. Дело в том, что он отправляет широковещательный запрос. Для этого в качестве MAC-адреса назначения он указывает FF-FF-FF-FF-FF-FF. Сообщение с таким адресом рассылается всем устройствам в пределах широковещательного домена (в данном случае, в пределах локальной сети) — кроме того устройства, которое сформировало и отправило широковещательный запрос. Если на каком-то из этих устройств нет DHCP-сервера, то этот запрос на нём просто отбрасывается. Если в сети есть устройство, на котором работает DHCP-сервер (у вас дома этим устройством является роутер), то оно отвечает на этот запрос, отправляя компьютеру с известным MAC-адресом присваиваемый ему IP-адрес. Задача DHCP-сервера в том, чтобы соблюдать уникальность IP-адресов в своей подсети. Получив DHCP-ответ, устройство отправляет ещё один запрос с подтверждением, что оно согласно принять этот IP-адрес, после чего DHCP-сервер присылает ответ с окончательным подтверждением. Второй обмен запросами нужен на случай, если в сети работает несколько DHCP-серверов, и из предложенных IP-адресов устройству нужно выбрать только один, а от остальных отказаться. Кроме IP-адреса, первый DHCP-ответ содержит IP-адрес шлюза по умолчанию (default gateway), IP-адреса одного или нескольких DNS-серверов и маску подсети.
Дефолтный шлюз — это устройство, через которое из вашей локальной сети можно выйти в Интернет. У вас дома это роутер. Когда вашему компьютеру нужно отправить IP-пакет на устройство, находящееся вне вашей локальной сети, он отправляет его дефолтному шлюзу, а дефолтный шлюз поднимается на третий уровень модели TCP/IP и разбирается, куда дальше нужно маршрутизировать этот пакет, чтобы он в конце концов дошёл по назначению. Но как ваш компьютер узнаёт, что адресат пакета находится вне вашей локальной сети? Для этого существует маска подсети. Не буду вдаваться в подробности, но применив операцию побитового И к маске подсети и двум IP-адресам, можно точно узнать, находятся ли эти два IP-адреса в одной локальной сети. Если нет, то придётся воспользоваться услугами дефолтного шлюза.
DNS-сервера используются для преобразования доменных имён в IP-адреса. Дело в том, что когда вы хотите обратиться к какому-то устройству в интернете, вы обычно не используете IP-адрес (например, 172.217.22.14), потому что он не слишком human-readable. Вы используете доменное имя (например, example.com), которое удобно запоминать человеку, но которое нельзя использовать для маршрутизации, поэтому его надо сначала преобразовать в IP-адрес с помощью системы DNS. Когда вы вводите адрес в браузере и нажимаете Enter, браузер проверяет наличие доменного имени в своём кэше. Если его там нет, то он делает системный вызов, и дальше запросом DNS занимается операционная система. Сначала она пытается найти адрес в своём кэше. Если его там нет, то система делает запрос DNS-серверу.
Вернёмся к компьютеру, в который только что воткнули Ethernet-кабель, и он ещё не успел наполнить свои кэши таблицами доменных имён и IP-адресов. Допустим, ему нужно зайти на сайт example.com. Он только что сделал DHCP-запрос, и у него есть IP-адрес DNS-сервера, так что он может обратиться к этому серверу, получить от него IP-адрес домена example.com и заодно записать этот адрес в свой кэш, чтобы не делать дорогостоящее обращение к DNS-серверу каждый раз, когда нужно узнать IP-адрес этого домена.
Для запроса к серверу, как мы помним, нужен не IP-адрес, а MAC-адрес, потому что физическая доставка нашего запроса происходит на канальном уровне, т. е. на уровне MAC-адресов. Компьютер берёт свой IP-адрес, IP-адрес сервера и маску подсети, и либо узнаёт, что сервер находится в одной с ним локальной сети, и тогда надо просто отправить запрос на MAC-адрес этого сервера, либо что он находится вне сети, и тогда надо отправить запрос на MAC-адрес дефолтного шлюза, который далее станет разбираться, как доставить его DNS-серверу. Проблема в том, что у нас нет MAC-адреса ни того, ни другого. Ответ DHCP-сервера не содержал никаких MAC-адресов, только IP-адреса. Для того, чтобы узнать MAC-адрес устройства в той же локальной сети по его IP-адресу, существует протокол ARP. Ваш компьютер делает ещё один широковещательный запрос, следующего содержания: «Устройство с IP-адресом таким-то и MAC-адресом таким-то просит устройство с вот этим IP-адресом сообщить свой MAC-адрес». Напоминаю, что широковещательный запрос принимается всеми устройствами в сети. Когда он приходит на устройство с IP-адресом, отличным от указанного в запросе, это устройство отбрасывает запрос и, скорее всего, помещает в свой кэш информацию об указанных в запросе IP-адресе и MAC-адресе отправителя, чтобы не выполнять ARP-запрос, если ему когда-нибудь понадобится связаться с этим устройством. Когда запрос приходит на нужное устройство, оно тоже помещает информацию в свой кэш и отправляет на ваш MAC-адрес ответ, в котором сообщает свой MAC-адрес.
DNS
Если DNS-сервер «не знает» нужный адрес, то запрашивается рекурсивный поиск, который проходит по списку вышестоящих DNS-серверов, пока не будет найдена нужная запись. Вышестоящий DNS-сервер, вместо запрашиваемого IP-адреса, может вернуть IP-адреса других DNS-серверов, которые могут знать нужный адрес. Например, корневой DNS-сервер точно не станет сам искать нужный вам IP-адрес, иначе ему не хватит производительности для обработки всех запросов. Поэтому он просто возвращает DNS-сервера доменной зоны (.ru, .com и т. п.).
Если есть необходимость распределять нагрузку на сервера в разных регионах планеты, то владелец сервиса может выбрать DNS-провайдера, который поддерживает GeoDNS. В настройках DNS такого провайдера есть возможность выбрать, какой IP-адрес будет возвращаться в каждом регионе для одного и того же доменного имени. Например, в Северной Америке для домена example.com DNS-серверы будут возвращать 192.0.2.1, в Европе — 198.51.100.2, а в Азии — 203.0.113.3. Это позволяет уменьшить время отклика, доставляя пользователю контент с ближайшего сервера, а не с другого конца планеты.
Ещё один способ балансировки нагрузки — это CDN (content delivery network) — система, позволяющая кэшировать данные на серверах в разных регионах. Начнём с того, что один из типов записей DNS, «canonical name record» (CNAME), служит для определения псевдонимов для доменных имён. Например, запись «www.example.com. IN CNAME example.com» перенаправляет все запросы к www.example.com на example.com. Клиент отправляет DNS-запрос, чтобы узнать IP-адрес домена www.example.com, и получает ответ от DNS-сервера, что на самом деле искать надо example.com, после чего клиент отправляет ещё один DNS-запрос, уже для example.com, и DNS-сервер возвращает нужный IP-адрес. Это позволяет, например, запустить несколько сайтов с разными доменными именами, являющимися псевдонимами одного и того же доменного имени. Эти сайты будут доступны по одному и тому же IP-адресу. В случае «переезда» серверов на другой IP-адрес, достаточно будет изменить A-запись в DNS, вписав туда новый IP-адрес, без того, чтобы редактировать каждую запись для каждого сайта.
CDN подразумевает, что с помощью CNAME записей пользователь получает разный конечный адрес домена в разных регионах, благодаря чему его запросы отправляются на ближайший CDN-сервер, а не на главный сервер компании, который может находиться на другом континенте. Компании не нужно в каждом регионе содержать свой сервер, на котором работает то же самое ПО, что и на основном сервере. CDN-провайдеры сами кэшируют контент и берут на себя работу по обслуживанию CDN-серверов. Настройка CDN позволяет прописать, какой контент должен кэшироваться и на какое время.
URL
Ну что ж, мы разобрались, как получить IP-адрес из доменного имени, но для обращения к конкретному ресурсу в Интернете используется не доменное имя и не IP-адрес, а URL. Доменное имя — это только часть URL. Структура URL выглядит так:
scheme://username:password@host:port/path?query#fragment
scheme
— это протокол (http
, https
, ftp
, mailto
и пр.), используемый для запроса. Структура URL отличается для разных протоколов, но для HTTP она выглядит в точности так, как указано выше. Host — это, собственно, доменное имя, вместо которого в URL можно сразу написать IP-адрес. После доменного имени обычно идёт путь, но иногда перед путём указывается порт (например, :8080
). После пути может идти якорь (Fragment), который указывает конкретное место на веб-странице, которую должен отобразить браузер, а также параметры запроса (query) — пары «ключ=значение», разделённые знаком &.
При обращении к ресурсу через браузер, в большинстве случаев порт не указывается. При этом браузер использует порт по умолчанию (например, 80 для HTTP или 443 для HTTPS).
При использовании такого URL в HTTP-запросе, хост и номер порта используются для установления TCP-соединения, имя хоста также указывается в заголовках, а путь и параметры запроса указываются в стартовой строке в том же виде, в котором они присутствуют в URL (что делать с этими данными, полностью зависит от внутренней логики сервера). Имя хоста нужно включать в запрос, потому что на одном IP-адресе могут работать несколько сайтов, и сервер должен знать, какое конкретно доменное имя использовал клиент, чтобы показать клиенту соответствующий сайт. Якорь — это информация для клиента, а не для сервера, поэтому он не включается в запрос. Данные аутентификации (username:password
) не рекомендуется использовать в URL по многим соображениям, и некоторые современные браузеры отказываются работать с такими ссылками, но вообще эти данные кодируются в Base64 и помещаются в HTTP-заголовок Authorization, как и при обычной базовой HTTP-аутентификации.
Свитч и роутер
Для построения локальной сети без выхода в Интернет, достаточно использовать коммутатор, он же свитч. Коммутатор — это маленький компьютер, в котором есть несколько портов для подключения устройств. Когда одно из устройств отправляет по Ethernet-кабелю какой-то запрос, первым делом этот запрос попадает на коммутатор, который смотрит MAC-адрес назначения, и пересылает запрос в тот порт, к которому подключён кабель, ведущий к получателю. У коммутатора в памяти содержится таблица, в которой указано, к какому MAC-адресу ведёт каждый порт. Если устройство только что подключили к сети, и его MAC-адреса ещё нет в таблице коммутатора, то он дублирует запрос на все подключённые к нему устройства, кроме того, от которого пришёл запрос. Устройства, которым этот запрос не предназначен, никак на него не отвечают. В запросах и ответах, проходящих через свитч, содержится информация о MAC-адресах отправителей и получателей, что позволяет свитчу обновлять информацию в своей таблице. Записи в таблице MAC-адресов имеют время жизни, чтобы таблица всегда была актуальной.
Дорогие модели коммутаторов в добавок к этому могут быть настроены для создания виртуальных локальных сетей (VLAN — это когда один свитч обеспечивает работу нескольких широковещательных доменов), для приоритизации и ограничения трафика определённого вида либо между определёнными портами.
Если бы в сетях не использовался интернет-уровень модели TCP/IP, а вся работа сетей строилась на доставке данных по MAC-адресам, то каждый коммутатор должен был бы хранить в памяти актуальную таблицу с MAC-адресами всех устройств на Земле. Это абсолютно нереалистичный подход, поэтому для доставки данных через Интернет используются сложные сети и маршрутизация по IP-адресам. Для маршрутизации используются маршрутизаторы, они же роутеры.
Роутер — это тоже маленький компьютер, как и свитч, но у него больше возможностей и больше требований к железу, поэтому он в разы дороже коммутатора, даже если это роутер без WiFi. Роутер может выполнять функции коммутатора для подключённых к нему устройств, объединяя их в локальную сеть.
Маршрутизация осуществляется с помощью протоколов маршрутизации (routing protocols). Суть этих протоколов в том, чтобы распределённо хранить на маршрутизаторах информацию, как добраться до других подсетей, не связанных с маршрутизатором напрямую. Эту информацию надо периодически обновлять, хранить данные о резервных маршрутах и ранжировать маршруты по какой-то метрике (например, на основе bandwidth и delay). Всё это делается ради того, чтобы отправлять каждый пакет по оптимальному маршруту.
Транспортные протоколы
Протоколы транспортного уровня (TCP, UDP и другие) используются для адресации пакетов с порта приложения отправителя на порт приложения получателя. Передаваемое сообщение (например, HTTP запрос) формируется на уровне приложения, затем передаётся на транспортный уровень, где к нему добавляется TCP или UDP заголовок, который, помимо прочего, содержит информацию о портах. В результате получается то, что обычно называется датаграммой или сегментом. Затем сегмент передаётся на межсетевой уровень. Там к нему добавляется IP-заголовок, содержащий адреса устройств, между которыми передаются данные. Результат объединения сегмента и IP-заголовка обычно называется пакетом. Дальше пакет передаётся на канальный уровень, где (в случае Ethernet-соединения) к нему добавляется заголовок Ethernet-кадра. Затем этот кадр передаётся по сети, и в каждом узле сети из него извлекают IP-пакет, определяют, на какое следующее устройство его нужно передать, формируют из него ещё один кадр и отправляют дальше. В начале и в конце передачи участвуют протоколы всех уровней TCP/IP-стека, а промежуточные ноды работают только с протоколами канального и межсетевого уровней.
Заголовок UDP-пакета состоит из 8 байт, которые включают в себя порт отправителя, порт получателя, длину датаграммы и контрольную сумму. Контрольная сумма позволяет определить, был ли пакет повреждён при доставке. В случае повреждения пакет отбрасывается. Размер сообщения, передаваемого по UDP, ограничен максимальным размером UDP-пакета — чуть меньше 64 килобайт (на практике — ещё меньше, ГОРАЗДО меньше, но это зависит от ограничений конкретной сети). Как видите, UDP протокол предельно прост и обеспечивает необходимый минимум функций, который можно ожидать от транспортного протокола.
С TCP-пакетом всё сложнее. Его заголовок включает порты отправителя и получателя, порядковый номер, номер подтверждения, длину заголовка, 9 флагов (каждый по 1 биту), размер окна, контрольную сумму, указатель важности и дополнительные опции. Всё это нужно, чтобы обеспечить надёжный канал связи с повторной отправкой потерянных пакетов и восстановлением порядка пакетов. По протоколу UDP вы просто отправляете и принимаете сообщения. При использовании TCP вы сначала устанавливаете соединение, какое-то время обмениваетесь данными, а потом закрываете соединение. Для открытия соединения используется процедура рукопожатия (handshake). Первое устройство посылает второму пакет с флагом SYN, второе устройство отвечает пакетом с флагами SYN-ACK, первое устройство отвечает на это пакетом с флагом ACK. С этого момента соединение установлено. Все передаваемые по нему пакеты содержат порядковый номер, позволяющий отследить целостность данных и порядок отправки пакетов. Процесс завершения соединения похож на рукопожатие, но вместо флага SYN используется флаг FIN.
Бóльшая часть интернет-трафика идёт по TCP, из-за надёжности этого протокола. Но, например, для DNS-запросов обычно используется протокол UDP, который, в отличие от TCP, не требует установления соединения и сессии между клиентом и сервером и каких-то дополнительных процедур, а следовательно, обеспечивает минимальную задержку. Весь DNS-запрос обычно не превышает 512 байт, и может быть помещён в один UDP-пакет. Если этот пакет теряется, то клиент просто по таймауту посылает ещё один запрос. После одной или нескольких неудачных попыток клиент может отправить запрос на другие DNS-серверы, и если ничего не помогает, то он возвращает пользователю ошибку. Когда DNS-запрос слишком длинный и не помещается в 512 байт, может использоваться протокол TCP.
NAT
Вы, должно быть, много раз видели типовые IP-адреса вроде 192.168.0.1, которые часто встречаются в локальных сетях. Как такое возможно, если IP — это уникальный адрес устройства в Интернете? Дело в том, что существуют диапазоны адресов для частных (приватных) сетей, которые определены стандартом RFC 1918:
10.0.0.0 – 10.255.255.255
172.16.0.0 – 172.31.255.255
192.168.0.0 – 192.168.255.255
Адреса из этих диапазонов не используются в глобальной сети Интернет и не могут быть маршрутизированы в ней. Это позволяет разным организациям (и частным лицам) использовать одни и те же адреса для своих локальных сетей без риска конфликтов с адресами в Интернете. Всё это придумано для того, чтобы не исчерпались адреса IPv4, поскольку потребность в последних оказалась гораздо больше, чем предполагали создатели интернет-протокола в начале 80-х. Для выхода в Интернет любому устройству нужен глобальный IP-адрес, но большинство устройств имеют локальный IP-адрес. Для интернет-коммуникации, на каком-то этапе прохождения пакетов через сеть, локальный IP-адрес должен заменяться на глобальный, а при возвращении ответного пакета глобальный адрес должен заменяться локальным, чтобы дойти до того устройства, которое отправило запрос. Для этого используется Network Address Translation (NAT). Фактически, NAT расширяет IP-адрес номером порта, используемого на транспортном уровне. По-хорошему, проблему нехватки IP-адресов следовало бы решать переходом на IPv6, который состоит из 128 бит вместо 32, что делает адресное пространство IPv6 практически неисчерпаемым. Но благодаря появлению NAT у бизнеса появилась возможность продолжать использовать IPv4, поэтому избавление от этого устаревшего стандарта затянулось на неопределённый срок. NAT использует 4 механизма трансляции адресов: Static NAT, Static PAT, Dynamic NAT и Dynamic PAT.
Два из этих подтипов NAT тоже называются NAT, так что не запутайтесь. Static означает, что трансляция конкретного локального адреса в глобальный однозначно задана администратором сети. Static NAT транслирует заранее настроенный приватный адрес A (скажем, 10.2.2.33) в заранее настроенный публичный адрес B (скажем, 54.4.5.9), а на обратном пути B транслирует в A. Это самый простой из 4 перечисленных механизмов NAT, который никак не помогает сберечь адресное пространство IPv4, поэтому он по большей части бесполезен.
Dynamic означает, что администратор устанавливает адреса, в которые может идти трансляция, но конкретный адрес в каждый момент времени выбирает устройство, которое осуществляет NAT. NAT означает Network Address Translation и изменяет только IP адрес. PAT означает Port Address Translation, и изменяет как IP адрес, так и номер порта.
Static PAT может транслировать разные приватные адреса в один и тот же публичный адрес, но с разным номером порта. Например, 10.4.4.41:8080 → 73.8.2.44:80 и 10.4.4.43:443 → 73.8.2.44:443. Dynamic PAT означает, что администратор устанавливает соответствие глобальному адресу не отдельных локальных адресов, а целой локальной подсети, а роутер сам формирует таблицу маппинга локальных IP-адресов (точнее, связок «IP-адрес + порт») к портам глобального адреса. Например, если админ настроил Dynamic PAT на маппинг 10.6.6.0/24 в 32.8.2.66, то когда на роутер придёт пакет от 10.6.6.61:2222, он транслирует этот адрес во что-то вроде 32.8.2.66:7777, т. е. он сам выберет случайный номер порта и запишет его себе в какую-нибудь таблицу.
Наконец, Dynamic NAT — это какая-то бессмысленная непонятная технология, которая сегодня уже мало где используется. Администратор назначает N публичных IP-адресов для всех устройств во внутренней сети. Если не более N внутренних хостов выходят в интернет, то всё работает нормально, и пакеты не теряются. Если N+1-е устройство начинает слать в Интернет свои пакеты, то они все дропаются. Чтобы это устройство могло выходить в Интернет, один из предыдущих N хостов должен разорвать соединение. Устройство, осуществляющее NAT, узнаёт о разрыве TCP-соединения по пакетам FIN или RST, либо по таймауту считает, что соединение разорвано.
TLS
Поверх протокола TCP может работать протокол SSL или TLS, который тоже является протоколом транспортного уровня, но к возможностям TCP добавляет защищённую связь. SSL — это старый протокол, который никто старается не использовать из-за проблем с безопасностью. Очень часто название SSL по историческим причинам используется в названии какого-нибудь тула или библиотеки, но при этом подразумевается TLS. Например, библиотека OpenSSL поддерживает как SSL, так и TLS, и чаще всего используется для работы с TLS.
При установке TLS-соединения происходит рукопожатие, в ходе которого клиент запрашивает TLS-сертификат сервера. Чтобы идти от простого к сложному, я сначала объясню принцип установки защищённого соединения без сертификата. Так никто не делает, но в образовательных целях это надо рассказать.
Существуют симметричные и асимметричные алгоритмы шифрования. Первые используют всего один криптографический ключ. Ключ — это цифровая последовательность, с помощью которой можно зашифровать сообщение так, что расшифровать его обратно можно будет только с помощью того же ключа. Получив зашифрованное сообщение, злоумышленник теоретически сможет подобрать к нему ключ и расшифровать его, но на подбор ключа уходит тем больше времени, чем длиннее ключ, поэтому длину ключа надо подобрать такую, чтобы ни за какое разумное время взломать его было бы невозможно. Симметричный ключ в TLS-соединении имеет длину 128, 192 или 256 бит. Для его взлома, даже самым мощным современным компьютерам потребуются миллиарды лет.
Итак, у нас есть простой рецепт, как установить защищённое соединение клиента с сервером. Каждое сообщение перед отправкой зашифровывается одним и тем же ключом, и им же расшифровывается. Даже если в каком-то из узлов Интернета будет сидеть злоумышленник, который перехватывает и читает все сообщения, он не сможет получить доступ к защищённой информации, поскольку не сможет её расшифровать. Но для этого один и тот же ключ должен быть и у клиента, и у сервера. Как им передать друг другу этот ключ? Не будете же вы встречаться в офлайн с владельцем сервера каждый раз, когда вам надо будет связаться с каким-нибудь сайтом по HTTPS? А если передавать ключ по сети, то злоумышленник сможет его перехватить.
Здесь нам поможет асимметричное шифрование, в котором используется пара ключей. Сообщение, зашифрованное первым ключом, может быть расшифровано только с помощью второго ключа. Один из этих ключей — закрытый ключ — находится в приватном хранилище и неизвестен никому, кроме его владельца. Второй, открытый ключ, публикуется в открытом доступе и спокойно пересылается по открытым каналам. Если вам известен открытый ключ сервера, то вы можете зашифровать им своё сообщение, и никто, кроме сервера, не сможет его прочитать. Если к вам пришло сообщение, и вы смогли расшифровать его открытым ключом сервера, значит, прислал его именно тот самый сервер, потому что никто другой не смог бы его так зашифровать. Вы связываетесь с сервером по открытому каналу и просите его установить с вами защищённое соединение, сервер отправляет вам свой открытый ключ, вы генерируете ключ симметричного шифрования, для использования только в этом соединении (сессионный ключ), шифруете этот ключ открытым ключом сервера и отправляете его на сервер. Вы можете быть уверены, что никто, кроме сервера, не сможет прочитать этот сессионный ключ. Сервер расшифровывает это сообщение своим закрытым ключом, и получает сессионный ключ, сгенерированный вами. С этого момента вы можете обмениваться зашифрованными сообщениями с сервером, используя симметричное шифрование с этим сессионным ключом.
Симметричное шифрование проще и быстрее, чем асимметричное, поэтому для постоянного обмена сообщениями лучше использовать его, чтобы сверх меры не нагружать процессор, а асимметричное использовать только на этапе установки защищённого соединения, чтобы отправить на сервер сессионный ключ.
Устанавливая такое соединение, вы полагаетесь на то, что злоумышленникам неизвестен приватный ключ сервера. На владельцах сайтов лежит ответственность хранить в секрете свои ключи, и всегда есть риск того, что закрытый ключ будет украден.
Описанная система защищает вас от злоумышленника, который может просматривать все ваши сообщения, но как насчёт злоумышленника, который может активно вмешиваться в вашу переписку и отправлять вам сообщения, притворяясь сервером, и отправлять сообщения серверу, притворяясь вами? Сервер отправит вам свой открытый ключ, злоумышленник его перехватит, и вместо открытого ключа сервера отправит вам свой открытый ключ. Это называется атакой «Man In The Middle» (MITM, или «человек посередине»). Для того чтобы защититься от такой атаки, используется инфраструктура открытых ключей и TLS-сертификатов.
Эта система основана на механизме, который можно назвать цепочкой доверия. У каждого пользователя Интернета есть список организаций, которым он безусловно доверяет. Открытые ключи шифрования этих организаций хранятся на его устройстве, и он считает, что эти ключи подлинные, а не подделанные с помощью атаки «Man In The Middle». Такие доверенные организации называются сертификационными центрами (Certificate Authority, или CA). Их открытые ключи предустановлены в операционную систему пользователя, что и даёт ему основания верить в их подлинность. Задача таких организаций — выдавать TLS-сертификаты владельцам сайтов. Сертификат — это электронный документ, который удостоверяет, что данный открытый ключ действительно принадлежит данному сайту (по крайней мере, за это ручается данный сертификационный центр). Благодаря этому, «человек посередине» сможет подделать открытый ключ сервера не раньше, чем он сумеет подделать сертификат. Для рядового интернет-мошенника подделать такой сертификат не представляется возможным, поскольку приватные ключи CA слишком хорошо охраняются.
Файл сертификата содержит название CA, выдавшего сертификат, открытый ключ сервера, название сервера (например, доменное имя), названия алгоритмов шифрования и хэширования, срок действия, серийный номер и цифровую подпись. Технология цифровой подписи основана на технологии асимметричного шифрования. Чтобы сформировать цифровую подпись, открытый ключ сервера и название сервера хэшируются с помощью хэш-функции, указанной в сертификате, полученный хэш шифруется закрытым ключом сертификационного центра, и получается цифровая подпись. После этого любой клиент, получивший сертификат, может расшифровать эту цифровую подпись открытым ключом CA и проверить, что полученный хэш действительно является хэшем этого сертификата.
Пока что для простоты предположим, что речь идёт о корневом CA, сертификат которого предустановлен в операционной системе клиента и обновляется во время обновления ОС. Обновления ОС защищены цифровой подписью производителя ОС, поэтому они защищены от атак MITM.
Итак, объясняю ещё раз. Клиент владеет данными сертификационного центра, которому он доверяет. У него есть открытый ключ этого сертификационного центра. Сервер присылает клиенту сертификат, подписанный электронной подписью этого CA. С помощью открытого ключа CA клиент проверяет, что цифровая подпись подлинная, и что данный сертификат действительно подписан этим сертификационным центром. Это означает, что сертификат пришёл от сервера, а не от «человека посередине». Далее открытый ключ сервера используется для согласования сессионного ключа. Это может происходить по описанному выше алгоритму, но разумнее будет немного усложнить этот процесс, применив алгоритм Диффи-Хеллмана.
Этот алгоритм позволяет сохранить конфиденциальность прошлых сессий, даже если приватный ключ сервера в будущем будет скомпрометирован. Для этого обе стороны генерируют эфемерные закрытые ключи и на их основе вычисляют общий сессионный ключ. Природа этого алгоритма такова, что и клиент, и сервер, вычисляют одинаковый ключ. При этом они обмениваются по сети данными, на основе которых вычислить этот сессионный ключ невозможно. Чтобы его вычислить, нужно, помимо этих данных, обладать одним из закрытых эфемерных ключей, которые по сети не передаются. Эфемерными они зовутся потому, что после рукопожатия они стираются из памяти. После разрыва соединения из памяти стирается и сессионный ключ. Чтобы в будущем расшифровать сообщения, переданные по этому соединению, нужно будет обладать хотя бы одним из этих трёх ключей. Поскольку все три ключа к тому моменту уже будут стёрты и забыты, расшифровать эту переписку не сможет никто.
Корневой сертификационный центр, информация о котором предустановлена в операционной системе — это серьёзная организация, проходящая через строгий аудит безопасности. Она редко выдаёт сертификаты непосредственно для доменов, но она может выдавать сертификаты другим сертификационным центрам, о которых отсутствует предустановленная информация в операционных системах. Эти CA могут выдавать сертификаты центрам ещё более низкого уровня. Чем ниже уровень организации, тем меньше следует доверять её сертификатам. В случае рядового CA, сервер отправляет клиенту не один сертификат, а цепочку сертификатов, в конце которой должен находиться сертификат корневого CA. Вся эта иерархия сертификационных центров называется инфраструктурой открытых ключей (PKI — public key infrastructure).
Полагаться на защищённость вашего соединения вы можете только в той мере, в которой вы доверяете каждой организации в цепочке сертификатов. В 2011 голландский сертификационный центр DigiNotar подвергся хакерской атаке. Через какое-то время в сети были найдены сотни фальшивых сертификатов, использованных в атаках «Man in the Middle». Самые распространённые браузеры немедленно внесли в чёрный список все сертификаты DigiNotar, и в конце концов, компания обанкротилась. Примечательно, что все известные жертвы этой атаки — это 300 000 иранских пользователей Gmail.
В общем случае, группировка, завладевшая приватным ключом CA, не заинтересована в том, чтобы этот факт вышел на поверхность. Если сейчас кто-то незаконно владеет таким ключом, то нет оснований полагать, что широкая общественность вообще когда-нибудь об этом узнает.
Квантовые компьютеры теоретически позволяют взламывать криптографическую защиту гораздо быстрее обычных компьютеров. В настоящее время ничего не известно о существовании полноценных квантовых компьютеров, которые можно было бы использовать для этих целей, но сама возможность их существования заставляет определённые организации переходить на пост-квантовые алгоритмы шифрования. Дело в том, что если кому-то удастся построить квантовый компьютер, то он не будет заинтересован в том, чтобы заявлять об этом публично. Скорее всего, он постарается использовать возможности такого компьютера для того, чтобы тайно взламывать защищённые коммуникации и получать колоссальную выгоду от знания корпоративных и правительственных секретов. Если у вас есть основания проявлять особую бдительность при защите своих секретов, то вы должны исходить из того, что где-то уже существует секретная лаборатория с квантовым компьютером, либо что она появится в обозримом будущем, поэтому вам следует использовать пост-квантовые алгоритмы шифрования, устойчивые к взлому с помощью таких машин.
Шифрование с симметричным ключом устойчиво к взлому с помощью квантовых компьютеров, но использование квантовых компьютеров позволяет снизить сложность атаки вдвое, поэтому для защиты вместо 128-битного ключа нужно использовать 256-битный. Распространённые алгоритмы шифрования с асимметричным ключом легко взламываются квантовым компьютером независимо от длины ключа, поэтому для установки защищённого соединения нужно использовать квантово-устойчивые алгоритмы, которые уже добавляются в новые версии TLS.
HTTP
Как вы помните, HTTP-запрос (как и HTTP-ответ) — это всего лишь текст, который начинается со стартовой строки и заголовков, содержащих стандартную служебную информацию, и тела сообщения, содержащего данные, которые нужно передать.
Вместо того чтобы отправлять целиком весь контент в одном сообщении, в HTTP/1.1 сервер может передавать данные постепенно, но в пределах одного ответа. Для этого он указывает заголовок Transfer-Encoding: chunked и присылает чанки, в начале каждого из которых указывается его размер в байтах. В конце сообщения передаётся нулевой чанк. Это помогает распределить по времени нагрузку на оперативную память сервера. Если речь идёт о пользователе, который зашёл на сайт через браузер, то в теле HTTP-ответа содержится HTML-документ. HTML-документ — это специальным образом размеченный текстовый файл, с описанием того, как должна выглядеть веб-страница, и как в интерфейсе пользователя должны располагаться все её элементы — текст, таблицы, картинки, видео и т. д. HTML-документ сам по себе не содержит ресурсы, которые отображаются на странице (картинки, js-файлы, шрифты, таблицы и пр.), но он содержит адреса URL, указывающие на эти ресурсы. Приняв от сервера HTML-документ, браузер находит в нём ссылки на необходимые ресурсы и запрашивает каждый из них в отдельном HTTP-запросе. Например, в случае картинок, сервер отправляет бинарное представление картинки в теле HTTP-ответа. При этом полезным оказывается заголовок Connection со значением keep-alive, позволяющий не открывать новое соединение (с рукопожатием и проверкой сертификата) для загрузки каждого ресурса.
Защищённой версией протокола HTTP является протокол HTTPS, который отличается от HTTP только тем, что работает поверх TLS, а не поверх TCP.
HTTP версий 2 и 3 были созданы для повышения скорости обмена данными. Начиная с версии 2, уже не нужно дожидаться ответа на предыдущий запрос, чтобы отправить следующий. HTTP-заголовки стали передавать в бинарном виде, при этом сжимая их специальным алгоритмом, чтобы при последовательных запросах не передавать повторяющуюся информацию, а заменять её указателями на ранее полученные блоки данных в заголовках ранее отправленных запросов. В отличие от версии 1.1, происходит параллельная загрузка всех ресурсов страницы в одном соединении, с помощью потоков фреймов, причём загрузка разных ресурсов может приоритизироваться, а браузер может останавливать отдельные потоки, прекращая загрузку каких-то ресурсов. Функция Server push в HTTP/2 и HTTP/3 позволяет серверу начать передачу ресурсов сразу после изначального запроса клиента на загрузку веб-страницы, не дожидаясь запросов на загрузку этих ресурсов.
Во всех распространённых системах с поддержкой HTTP/2, этот протокол работает только по TLS, то есть это не HTTP, а HTTPS.
В отличие от своих предшественников (которые работают поверх TCP/TLS), версия HTTP/3 работает поверх UDP/QUIC, ещё больше ускоряя передачу данных. Протокол QUIC был разработан специально для неё. Он обеспечивает всё то же самое, что и TCP/TLS — надёжность соединения, проверку сертификатов и криптографическую защиту, только делает всё это быстрее. Он устанавливает соединение и настраивает шифрование в 1 заход, благодаря чему количество рукопожатий снизилось до 1. К тому же, при разрыве соединения уже не нужно устанавливать новое и производить новые рукопожатия, так как используется уникальный идентификатор соединения. Новый протокол реализует Zero-RTT — возможность отправить запрос сразу, не дожидаясь завершения рукопожатия — и обеспечивает более лёгкую и быструю миграцию IP (смена IP-адреса, например, при переключении с мобильного интернета на Wi-Fi).
HTTP-аутентификация
Если HTTP-серверу перед выполнением запроса необходима авторизация клиента, то он сообщает об этом заголовком «WWW-Authenticate» и ошибкой «401 Unauthorized» в стартовой строке ответа. В заголовке «WWW-Authenticate» он указывает, какой протокол аутентификации должен использовать клиент, и приводит дополнительную информацию, необходимую для конкретного протокола.
Самый простой протокол — это Basic Authentication. В качестве дополнительных данных для этого протокола сервер сообщает realm — название подраздела сайта, в котором пользователю предлагается авторизироваться. Чтобы всё-таки получить доступ к запрошенному ресурсу, пользователь должен повторить свой запрос, на этот раз добавив заголовок «Authorization» и указав в нём свой логин и пароль, закодированные в Base64. Base64 не обеспечивает никакой криптографической защиты и нужен только для кодирования данных в формате, разрешённом для HTTP-заголовков (поскольку исходные логин и пароль могут содержать международные символы). Так как пароль фактически передаётся на сайт в открытом виде, такую аутентификацию имеет смысл делать только по HTTPS.
Второй протокол — Digest access authentication — в 2011 признан устаревшим, но несмотря на это, всё ещё используется. В заголовке «WWW-Authenticate» сервер передаёт nonce — последовательность, случайно сгенерированную только для этого соединения, которую пользователь сможет захэшировать с добавлением своего пароля и отправить серверу полученный хэш. При этом сам пароль не передаётся по сети. Сервер тоже вычисляет этот хэш и проверяет правильность хэша, предоставленного пользователем. Для генерации финального хэша используется не сам пароль, а хэш MD5 из username-а, realm-а и пароля, разделённых двоеточиями. Это позволяет серверу вместо самих паролей хранить только хэши, которых достаточно для того, чтобы проверить наличие пароля у пользователя. Это гарантирует, что даже в случае утечки базы данных пользователей, злоумышленники не смогут получить их пароли.
Этот протокол не давал значительного усиления безопасности по сравнению с базовой аутентификацией по HTTPS, и при этом имел слишком сложную спецификацию, поэтому инженеры IETF решили, что его поддержка не стоит затрачиваемых на неё усилий, и обозначили этот протокол как Obsolete.
Базовая аутентификация с использованием стандартных кодов состояния и заголовков — это удобная опция для скриптов и программ, которые всё делают автоматически, в том числе и авторизируются на разных сервисах, чтобы получить доступ к веб-API. Но живые люди привыкли вводить свои креденшиалы в интерфейсе, гармонично встроенном в дизайн веб-сайта. Такой подход называется Form-based Authentication. Метод отправки креденшиалов в этом случае полностью остаётся на усмотрение разработчиков сайта. К примеру, это может быть POST-запрос по HTTPS с логином и паролем в теле запроса.
Отправив серверу данные аутентификации, клиент обычно получает не только доступ к ресурсу, но и токен, позволяющий ему какое-то время получать доступ к этому и другим ресурсам, без необходимости каждый раз вводить логин и пароль. Примером является bearer токен, который клиент в HTTP запросе помещает в заголовке «Authorization» после слова «bearer». Токен содержит информацию о пользователе, его правах, и сроке действия токена. Все эти данные подписаны секретным ключом сервера, поэтому не могут быть подделаны. Но «подписаны» — не значит «зашифрованы», так что хранить в токенах какую-нибудь чувствительную информацию крайне не рекомендуется. Сами токены могут храниться в разных местах, но если нужно надёжное долговременное хранилище, то можно использовать httpOnly куку.
Куки (Cookie) — это данные, которые сервер передаёт клиенту, для того чтобы клиент потом передал их обратно серверу. Дело в том, что HTTP — это протокол без состояния, и когда сервер получает 2 HTTP-запроса, у него нет возможности понять, поступили они от одного клиента или от разных — если только он не использует куки. Куки позволяют серверу сохранять состояние сессии с конкретным клиентом и могут содержать любые данные — токены аутентификации, пользовательские настройки, корзину покупок и т. п. Они передаются сервером в HTTP-заголовках Set-Cookie в формате пар «ключ=значение» и сопровождаются служебной информацией, такой как время жизни, область видимости (т. е. те URL, при запросах к которым браузер будет передавать эту куку), параметр Secure, который говорит браузеру, что он может пересылать эту куку только по HTTPS, и параметр httpOnly, благодаря которому кука становится невидимой для кода JavaScript в браузере, что позволяет избежать атак, направленных на похищение токена аутентификации из кук.
Токены используются в механизме Single Sing-On, когда аутентификация на одном сайте даёт пользователю доступ к нескольким другим сайтам. Благодаря этому, вы можете не регистрироваться на самом сайте, а войти в него через учётную запись Google, Microsoft, X и других Identity провайдеров (IdP). Доказательством аутентификации для сервиса является токен. подписанный электронной подписью IdP. Токен может на какое-то время давать сервису доступ к информации пользователя в системе IdP, такой как никнейм, емейл, пол, возраст, фотографии и т. п.
VPN
VPN создаёт виртуальную сеть с произвольной структурой, которая работает как обычная сеть, только устройства в ней соединены не проводами, а сетевыми туннелями. Ваше устройство может установить сетевой туннель с устройством на другом конце города, и этот туннель будет проходить через множество сетей и маршрутизаторов, но в структуре VPN два конечных устройства будут фактически соединены напрямую, образуя виртуальную сеть из двух устройств. Как правило, сетевой туннель обеспечивает не простую, а зашифрованную связь. Разберём работу VPN на примере VPN-протокола Wireguard.
На линуксе сетевые устройства представлены сетевыми интерфейсами, обозначаемыми eth0
, eth1
и т. д. (для Ethernet) или wlan0
, wlan1
и т. д. (для WiFi). WireGuard в добавок к ним создаёт виртуальные сетевые интерфейсы, обозначаемые wg0
, wg1
и т. д., которые точно так же отвечают за связь на сетевом уровне. Их точно так же можно конфигурировать стандартными средствами Linux — включать, отключать, назначать IP-адреса, маски подсети и пр. Кроме того, на каждом устройстве, подключённом к виртуальной сети WireGuard, должен лежать конфигурационный файл, в котором содержится его собственный закрытый ключ и открытые ключи пиров, с которыми он находится в одной VPN-сети. Когда IP-пакет отправляется в сетевой интерфейс WireGuard (например, wg0), он шифруется с использованием открытого ключа того пира, для которого он предназначен, и отправляется по UDP на соответствующий этому пиру эндпоинт (публичный IP-адрес + порт UDP).
Когда я говорю «шифруется с использованием открытого ключа пира», это не следует понимать как «шифруется непосредственно открытым ключом пира». На самом деле, между пирами периодически происходит рукопожатие, когда с помощью закрытых ключей методом Диффи-Хеллмана (см. выше) генерируется сессионный ключ, с помощью которого шифруется трафик между этими пирами, но только до следующего рукопожатия.
В передаваемых сообщениях имеется поле счётчика, которое позволяет сохранять порядок UDP-пакетов, поскольку они имеют тенденцию приходить не в том порядке, в котором их отправляли.
Автор: taydvax