headermask image



Анатомия сетевого стека в Linux

Одна из величайших возможностей операционной системы Linux® — ее сетевой стек. Он является производной стека BSD и хорошо оснащен добротным набором интерфейсов, которые варьируются от протоколо-независимых (protocol agnostic), таких как интерфейс уровня общих сокетов или уровня устройств, до специальных интерфейсов конкретных сетевых протоколов. В этой статье исследуется структура сетевого стека Linux с точки зрения его уровней, а также рассматриваются некоторые из основных структур.

Введение в протоколы

В то время как формальное введение в работу в сети отсылает нас к модели взаимодействия открытых систем (OSI — Open Systems Interconnection), это введение в основной сетевой стек в Linux использует четырехуровневую модель, известную как модель Интернет (Internet model) (смотрите рисунок 1).
 Рисунок 1. Интернет-модель сетевого стека
Рисунок 1. Интернет-модель сетевого стека

Внизу стека располагается канальный уровень. Канальный уровень относится к драйверам устройств, обеспечивающим доступ к физическому уровню, который может состоять из многочисленных сред, таких как последовательные каналы или устройства Ethernet. Над канальным находится сетевой уровень, который отвечает за направление пакетов по назначению. Следующий уровень под названием транспортный отвечает за одноранговые (peer-to-peer) коммуникации (например, в пределах хоста). Сетевой уровень управляет связью между хостами, а транспортный — взаимодействием между конечными точками внутри этих хостов. Наконец, существует прикладной уровень, который обычно является семантическим и понимает перемещенные данные. К примеру, протокол передачи гипертекста (HTTP — Hypertext Transfer Protocol) перемещает запросы и ответы для содержимого Web между сервером и клиентом.

В сущности, уровни сетевого стека проходят под более узнаваемыми названиями. На канальном уровне вы найдете Ethernet, наиболее распространенную высокоскоростную среду. К более старым протоколам канального уровня относятся такие последовательные протоколы, как Internet-протокол для последовательной линии (SLIP — Serial Line Internet Protocol), Compressed SLIP (CSLIP) и Point-to-Point Protocol (PPP). Наиболее распространенным протоколом сетевого уровня является Internet Protocol (IP), но существуют и другие, которые удовлетворяют другим нуждам, такие как Протокол управляющих сообщений Internet (ICMP — Internet Control Message Protocol) и Протокол разрешения адресов (ARP — Address Resolution Protocol). На транспортном уровне это Протокол управления передачей (TCP — Transmission Control Protocol) и Протокол пользовательских датаграмм (UDP — User Datagram Protocol). Наконец, прикладной уровень включает в себя множество привычных нам протоколов, в том числе HTTP, стандартный Web-протокол, и SMTP (Simple Mail Transfer Protocol), протокол передачи электронной почты.

Архитектура базовой сети

Теперь перейдем к архитектуре сетевого стека Linux и посмотрим, как он реализует модель Internet. На рисунке 2 представлен высокоуровневый вид сетевого стека Linux. Наверху располагается уровень пользовательского пространства или прикладной уровень, который определяет пользователей сетевого стека. Внизу находятся физические устройства, которые обеспечивают возможность соединения с сетями (последовательные или высокоскоростные сети, как Ethernet). В центре, или в пространстве ядра, — сетевая подсистема, которая находится в центре внимания данной статьи. Через внутреннюю часть сетевого стека проходят буферы сокетов (sk_buffs), которые перемещают данные пакета между источниками и получателями. Кратко будет показана структура sk_buff.

Высокоуровневая архитектура сетевого стека Linux
Рисунок 2. Высокоуровневая архитектура сетевого стека Linux

Во-первых, вам предлагается краткий обзор основных элементов сетевой подсистемы Linux с подробностями в следующих разделах. Наверху (смотрите рисунок 2) находится система под названием интерфейс системного вызова. Она просто дает способ приложениям из пользовательского пространства получать доступ к сетевой подсистеме ядра. Следующим идет протоколо-независимый (protocol agnostic) уровень, который предоставляет общий способ работы с нижестоящими протоколами транспортного уровня. Дальше следуют фактические протоколы, к которым в системе Linux относятся встроенные протоколы TCP, UDP и, конечно же, IP. Следующий — еще один независимый уровень, который обеспечивает общий интерфейс к отдельным доступным драйверам устройств и от них, сопровождаемый в конце самими этими драйверами.

Интерфейс системного вызова

Интерфейс системного вызова может быть описан в двух ракурсах. Когда сетевой вызов производится пользователем, он мультиплексируется через системный вызов в ядро. Это заканчивается как вызов sys_socketcall в ./net/socket.c, который потом демультиплексирует вызов намеченной цели. Другой ракурс интерфейса системного вызова — использование нормальных файловых операций для сетевого ввода/вывода (I/O). Например, обычные операции чтения и записи могут быть выполнены на сетевом сокете (который представляется файловым дескриптором как нормальный файл). Поэтому пока существуют операции, специфичные для работы в сети (создание сокета вызовом socket, связывание его с дескриптором вызовом connect и так далее), есть также и некоторое количество стандартных файловых операций, которые применяются к сетевым объектам, как к обычным файлам. Наконец, интерфейс системного вызова предоставляет средства для передачи управления между приложением в пользовательском пространстве и ядром.

Протоколо-независимый интерфейс (Protocol agnostic interface)

Уровень сокетов является протоколо-независимым (protocol agnostic) интерфейсом, который предоставляет набор стандартных функций для поддержки ряда различных протоколов. Этот уровень не только поддерживает обычные TCP- и UDP-протоколы, но также и IP, raw Ethernet и другие транспортные протоколы, такие как Протокол управления передачей потоков данных (SCTP — Stream Control Transmission Protocol).

Взаимодействие через сетевой стек происходит посредством сокета. Структура сокета в Linux — struct sock, определенная в linux/include/net/sock.h. Эта большая структура содержит все необходимые состояния отдельного сокета, включая определенный протокол, используемый сокетом, и операции, которые можно над ним совершать.

Сетевая подсистема знает о доступных протоколах из специальной структуры, которая определяет ее возможности. Каждый протокол содержит структуру под названием proto (она находится в linux/include/net/sock.h). Эта структура определяет отдельные операции сокета, которые могут выполняться из уровня сокетов на транспортный уровень (например, как создать сокет, как установить соединение с сокетом, как закрыть сокет и т.д.).

Сетевые протоколы

Раздел сетевых протоколов определяет отдельные доступные сетевые протоколы (такие как TCP, UDP и так далее). Они инициализируются в начале дня в функции inet_init в linux/net/ipv4/af_inet.c (так как TCP и UDP относятся к семейству протоколов inet). Функция inet_init регистрирует каждый из встроенных протоколов, использующих функцию proto_register. Эта функция определена в linux/net/core/sock.c, и кроме добавления протокола в список действующих, если требуется, может выделять один или более slab-кэшей.

Можно увидеть, как отдельные протоколы идентифицируют сами себя посредством структуры proto в файлах tcp_ipv4.c, udp.c и raw.c, в linux/net/ipv4/. Каждая из этих структур протоколов отображается в виде типа и протокола в inetsw_array, который приписывает встроенные протоколы их операциям. Структура inetsw_array и его связи показаны на рисунке 3. Каждый из протоколов в этом массиве инициализируется в начале дня в inetsw вызовом inet_register_protosw из inet_init. Функция inet_init также инициализирует различные модули inet, такие как ARP, ICMP, IP-модули и TCP и UDP-модули.
Структура массива Internet-протокола
Рисунок 3. Структура массива Internet-протокола

Обратите внимание на рисунке 3, что структура proto определяет транспортные методы сокета, в то время как структура proto_ops — общие. Дополнительные протоколы можно добавить в переключатель протоколов inetsw с помощью вызова inet_register_protosw. Например, SCTP добавляет себя вызовом sctp_init в linux/net/sctp/protocol.c.

Перемещение данных для сокетов происходит при помощи основной структуры под названием буфер сокета (sk_buff). В sk_buff содержатся данные пакета и данные о состоянии, которые охватывают несколько уровней стека протокола. Каждый отправленный или полученный пакет представлен в sk_buff. Структура sk_buff определяется в linux/include/linux/skbuff.h и показана на рисунке 4.
Буфер сокета и его связи с другими структурами
Рисунок 4. Буфер сокета и его связи с другими структурами

Как можно заметить, несколько структур sk_buff для данного соединения могут быть связаны вместе. Каждая из них идентифицирует структуру устройства (net_device), которому пакет посылается или от которого получен. Так как каждый пакет представлен в sk_buff, заголовки пакетов удобно определены набором указателей (th, iph и mac для Управления доступом к среде (заголовок Media Access Control или MAC). Поскольку структуры sk_buff являются центральными в организации данных сокета, для управления ими был создан ряд функций поддержки. Существуют функции для создания, разрушения, клонирования и управления очередностью sk_buff.

Буферы сокетов разработаны таким образом, чтобы связываться друг с другом для данного сокета и включать большой объем информации, в том числе ссылки на заголовки протоколов, временные метки (когда пакет был отправлен или получен) и соответствующее устройство.


Устройство-независимый интерфейс (Device agnostic interface)

Под уровнем протоколов располагается другой независимый уровень интерфейса, который связывает протоколы с различными драйверами физических устройств с разными возможностями. Этот уровень предоставляет стандартный набор функций, которые используются низко-уровневыми сетевыми устройствами, чтобы иметь возможность взаимодействовать с высоко-уровневым стеком протокола.

Прежде всего, драйверы устройств могут регистрировать и разрегистрировать себя в ядре вызовом register_netdevice или unregister_netdevice. Вызывающая команда сначала заполняет структуру net_device, а затем передает ее для регистрации. Ядро вызывает свою функцию init (если она определена), выполняет несколько проверок исправности, создает запись sysfs и потом добавляет новое устройство в список устройств (связанный список устройств, активных в ядре). Структуру net_device можно найти в linux/include/linux/netdevice.h. Некоторые функции находятся в linux/net/core/dev.c.

Для отправления sk_buff из уровня протокола устройству используется функция dev_queue_xmit. Она ставит в очередь sk_buff для возможной пересылки соответствующим драйвером устройства (устройством, определенным при помощи net_device или указателя sk_buff->dev в sk_buff). Структура dev содержит метод под названием hard_start_xmit, который хранит функцию драйвера для инициализации передачи sk_buff.

Получение пакета выполняется традиционно при помощи netif_rx. Когда драйвер устройства более низкого уровня получает пакет (содержащийся внутри выделенного sk_buff), sk_buff идет выше, на сетевой уровень, с помощью вызова netif_rx. Эта функция затем ставит sk_buff в очередь на более высокий уровень протоколов для дальнейшей обработки при помощи netif_rx_schedule. Функции dev_queue_xmit и netif_rx находятся в linux/net/core/dev.c.

Наконец, для взаимодействия с устройство-независимым уровнем (dev) в ядро был введен новый интерфейс прикладных программ (NAPI). Его используют некоторые драйверы, но подавляющее большинство все еще пользуется более старым интерфейсом получения кадров (по грубой оценке шесть из семи). NAPI может давать лучшую производительность при больших нагрузках, избегая при этом прерываний при каждом входящем кадре.

Драйверы устройств

Внизу сетевого стека находятся драйверы устройств, которые управляют физическими сетевыми устройствами. Примерами устройств этого уровня могут служить драйвер SLIP над последовательным интерфейсом или драйвер Ethernet над устройством Ethernet.

Во время инициализации драйвер устройства выделяет место для структуры net_device, а затем инициализирует ее необходимыми подпрограммами. Одна из них, с названием dev->hard_start_xmit, определяет, как верхний уровень должен поставить в очередь sk_buff для передачи. Ей передается sk_buff. Работа этой функции зависит от оборудования, но обычно пакет, описываемый в sk_buff, перемещается в так называемое “аппаратное кольцо” (hardware ring) или “очередь” (queue). Поступление кадра, как описано на устройство-независимом уровне, использует интерфейс netif_rx или netif_receive_skb для NAPI-совместимого сетевого драйвера. Драйвер NAPI накладывает ограничения на возможности базового оборудования.

После того как драйвер устройства настроил свои интерфейсы в структуре dev, вызов register_netdevice делает ее доступной для использования. В linux/drivers/net можно найти драйверы, характерные для сетевых устройств.

Идем дальше

Исходный код Linux — прекрасный способ узнать о конструкции драйверов для множества типов устройств, включая драйверы сетевых устройств. Вы обнаружите различия в конструкции и использовании доступных API ядра, но каждый будет полезен либо инструкциями, либо как отправная точка для нового драйвера. Остальной код в сетевом стеке стандартен и используется, пока не потребуется новый протокол. Но даже тогда реализации TCP (для потокового протокола) или UDP (для протокола на основе передачи сообщений) служат полезными моделями для начала новой разработки.

Автор: М. Тим Джонс, инженер-консультант, Emulex
Взято с ibm developerworks

Комментарии

Your email is never published nor shared. Required fields are marked *

*
*