Сетевой стек linux что это

Обновлено: 08.07.2024

Сетевой стек протоколов, формирующий конвейеризацию и передачу данных между хостами, разработан для наилучшего взаимодействия между различными сетевыми уровнями. В этой статье мы попытаемся описать перемещение данных через расположенные в стеке уровни и попробуем внедрить модуль ядра Linux, помогающий нам захватывать и отображать данные, проходящие через уровень TCP. Ниже представлена программа, показывающая как установить новый элемент в файловой системе proc. И что наиболее важно, эта программа взламывает TCP протокол, отслеживает все данные, проходящие через tcp уровень, и отображает их в /proc/TCPdata.

Сетевой стек протоколов, формирующий конвейеризацию и передачу данных между хостами, разработан для наилучшего взаимодействия между различными сетевыми уровнями. В этой статье мы попытаемся описать перемещение данных через расположенные в стеке уровни и попробуем внедрить модуль ядра Linux, помогающий нам захватывать и отображать данные, проходящие через уровень TCP. Так как обсуждение всех видов сетевых соединений выходит за рамки данной статьи, то мы остановимся на TCP/IP соединениях.

Сетевой стек протоколов

Сетевые устройства формируют базовый уровень стека протоколов. Для связи с другими устройствами и приема-передачи трафика они используют протокол канала передачи данных (обычно Ethernet). Интерфейс, организуемый драйверами сетевых устройств, копирует пакеты с физической среды, выполняя некоторые проверки ошибок, после чего помещает пакеты в сетевой уровень. Интерфейсы вывода принимают пакеты из сетевого уровня, выполняют некоторые проверки ошибок и пересылают их в физическую среду. Мы будем обсуждать IP (протокол Интернет) являющийся стандартным протоколом сетевого уровня. Главными функциями IP являются маршрутизация, проверка входящих пакетов, определяющая направлены ли эти пакеты на данный хост или они нуждаются в дальнейшей пересылке. При этом, в случае необходимости, пакеты дефрагментируются и доставляются на транспортные протоколы. Такие протоколы имеют динамическую базу данных маршрутов для исходящих пакетов, адресуют и фрагментируют их перед посылкой на уровень связи.

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

Продвигаясь по транспортному уровню, мы можем найти INET уровень, формирующий промежуточный уровень между транспортным уровнем и сокетами приложений. С помощью INET уровня поддерживаются принадлежащие приложениям сокеты. В этом уровне осуществлены все специфические операции сокетов.

BSD - абстрактная структура данных, содержащая INET сокеты. Запрос приложения на подключение, чтение или запись через сокет, преобразовывается в INET операции с помощью BSD.

Общая структура пакетов. Данные заключаются в общую структуру данных, называемую sk_buff. Все уровни используют эту структуру данных. Сразу после копирования данных из пространства пользователя в пространство ядра, они помещаются в sk_buff и перемещаются на различные уровни. В свою очередь уровень добавляют к этой структуре свои заголовки. Sk_buff содержит ссылки на всю информацию о пакете, его сокете, устройстве, маршруте, расположении данных и т.д.

Сетевые функции Linux

Для нормального сетевого программиста, интерфейсная часть сетевых служб доступна с помощью следующих подпрограмм библиотеки С.

socket(), bind(), listen(), connect(), accept(), send(), sendto(), recv(), recvfrom(), getsockopt(), and setsockopt().

Функция socket() используется для создания нового сокета. Все операции с различными протоколами происходят с помощью сокетов. Поскольку функция socket() возвращает значение дескриптора файла, то к нему могут обращаться стандартные операции работы с файлами типа read(), write().

Фунция bind() используется для связи созданного сокета с портом. Порт, наряду с IP адресом сетевого интерфейса, используется для уникальной идентификации сокета.

Функция listen() используется для программирования сервера. После создания сокета и связи его с портом функция listen() устанавливает сокет в состояние прослушивания. Это означает, что сокет ожидает подключения со стороны других хостов.

При вызове на сервере функции accept() происходит непрерывный опрос сокета происходящий до получения запроса на подключение от другого хоста. После установления соединения серверная программа пробуждается и дает процессу возможность обрабатывать запрос от чужого хоста. Со стороны клиента используется функция connect(), показывающая серверу, что клиент желает открыть подключение на сокете и послать запрос.

Необходимые структуры данных

Эта структура является основой для выполнения интерфейса BSD сокетов. Установка и инициализация этой структуры происходит при помощи системного вызова socket().

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

Эта структура управляет различными частями сокетов, зависящими от конкретной сети. Она необходима для TCP, UDP и RAW сокетов.

Эта структура содержит ряд операций, одинаковых для всех протоколов.

Sockaddr (sockaddr_in):

Такая структура необходима для поддержки различных форматов адресов.

Модули ядра Linux

Ядра Linux ядра состоят из модулей. Некоторые части ядра находятся в памяти постоянно (типа планировщика), а некоторые загружаются при необходимости. Например, файловая система VFAT для чтения дисков, загружается только при необходимости. Такая особенность linux ядра позволяет пространству ядра занимать немного места.

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

Проект хакерского модуля для TCP протокола.

Используемые структуры данных

tcp_prot -> Содержит указатели на все осуществленные TCP операции

struct msghdr -> Содержит данные, посылаемые приложением, а также другими полями для идентификации адреса сокета

Struct msg_iov -> находится в msghdr, в нем содержатся указатели на данные

При создании файла в /proc, он сразу регистрируется с набором функций, сообщающих ядру, что необходимо делать при открытии этого файла или записи в него. Большинство файлов поддерживают только чтение, и лишь некоторые поддерживают запись.

Теперь мы начнем кодирование

Данная программа была протестирована на ядре 2.4, так что вы можете откомпилировать её используя:

Теперь для того, чтобы понять этот код, я дам некоторые пояснения. Вся программа - это только хитрый путь для использования функциональных указателей. Но необходимо быть внимательным при использовании ваших собственных обработчиков, потому что неправильное размещение функционального указателя может привести к зависанию системы. Как было сказано, это является минимальным путем для взлома TCP. Вы можете сформировать новый уровень над уровнем TCP подобно этому, который будет вмешиваться во все типы заданий, сделанных TCP. Тоже может быть проделано и с UDP.

Заключение

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

Понимание сетевого стека Linux (1): краткое описание стека сетевых протоколов Linux

В этой серии статей описывается сетевой стек Linux, в том числе:

(4) Технология разгрузки сегментации в среде QEMU / KVM + VxLAN (принимающая сторона)

1. Сетевой путь Linux


1.1 отправитель

1.1.1 Уровень приложения

(1) Socket

Различные сетевые приложения на прикладном уровне в основном взаимодействуют со стеком сетевых протоколов пространства ядра через программный интерфейс Linux Socket. Linux Socket разработан на основе BSD Socket, является одной из важных частей операционной системы Linux и основой сетевых приложений. На иерархическом уровне он расположен на уровне приложений и представляет собой API, предоставляемый операционной системой для программистов приложений, через который приложения могут получить доступ к протоколу транспортного уровня.

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




Процесс обработки сокета UDP (источник) Процесс обработки сокета TCP (источник)

(2) Поток обработки на уровне приложений


1.1.2 Транспортный уровень

Конечная цель транспортного уровня - предоставить своим пользователям эффективные, надежные и экономичные услуги передачи данных.Основные функции включают (1) построение сегмента TCP (2) вычисление контрольной суммы (3) отправку пакета ответа (ACK) (4) Операция, гарантирующая надежность, например, скользящий отвод. Общий процесс обработки стека протоколов TCP показан на следующем рисунке:


Краткий процесс работы стека TCP:

  1. Функция tcp_sendmsg сначала проверяет статус установленного TCP-соединения, затем получает MSS соединения и запускает процесс отправки сегмента.
  2. Создайте загрузку сегмента TCP: он создает экземпляр skb структуры данных sk_buffer пакета в пространстве ядра и копирует данные пакета из буфера пространства пользователя в буфер skb.
  3. Создайте заголовок TCP.
  4. Вычислить контрольную сумму TCP (контрольную сумму) и порядковый номер (порядковый номер).
    1. Контрольная сумма TCP - это сквозная контрольная сумма, вычисляемая отправителем и затем проверяемая получателем. Его цель - обнаруживать любые изменения в заголовке TCP и данных от отправителя к получателю. Если получатель обнаруживает ошибку в контрольной сумме, сегмент TCP будет напрямую отброшен. Контрольная сумма TCP покрывает заголовок TCP и данные TCP.
    2. Требуется контрольная сумма TCP

    Краткий процесс работы стека UDP:

    1.1.3 IP-сетевой уровень - добавление заголовка и контрольной суммы, обработка маршрутизации, IP-фрагментация

    Задача сетевого уровня - выбрать подходящие узлы маршрутизации и коммутации между сетями для обеспечения своевременной передачи данных. Сетевой уровень составляет пакеты данных из кадров, предоставленных канальным уровнем.Заголовок сетевого уровня инкапсулируется в пакет, который содержит информацию о логическом адресе - сетевые адреса исходного сайта и адреса конечного сайта. В его основные задачи входит (1) обработка маршрутизации, то есть выбор следующего перехода (2) добавление IP-заголовка (3) вычисление контрольной суммы IP-заголовка, которая используется для определения того, содержит ли заголовок IP-пакета ошибку во время процесса распространения (4), если возможно, продолжить После обработки IP-фрагментации (5) получите MAC-адрес следующего перехода, установите заголовок канального уровня и затем передайте на канальный уровень для обработки.


    Базовый процесс обработки IP-стека показан на следующем рисунке:


    1.1.4 Уровень канала передачи данных

    Функционально, на основе услуги потока битов, предоставляемой физическим уровнем, устанавливается канал данных между соседними узлами, обеспечивается безошибочная передача кадров данных (Frame) по каналу посредством контроля ошибок, и выполняются действия в каждой цепи. серии. Уровень канала передачи данных обеспечивает надежную передачу на ненадежных физических носителях. Роль этого уровня включает: адресацию физических адресов, формирование кадров данных, управление потоком, обнаружение ошибок данных, повторную передачу и т. Д. На этом уровне единица данных называется кадром. Представители протоколов уровня звена данных включают: SDLC, HDLC, PPP, STP, Frame Relay и т. Д.

    С точки зрения реализации, Linux предоставляет уровень абстракции сетевого устройства, фактически это теперь linux / net / core / dev.c. Конкретные физические сетевые устройства должны реализовывать виртуальные функции в драйвере устройства (driver.c). Уровень абстракции сетевого устройства вызывает функции определенных сетевых устройств.


    1.1.5 Инкапсуляция и передача физического уровня на физическом уровне


    1.1.6 Краткое резюме


    (источник)

    1.2 Ресивер

    1.2.1 Физический уровень и уровень канала данных



    1.2.2 Сетевой уровень





    1.2.3 Транспортный уровень (TCP / UDP)

    1. Запись обработки TCP транспортного уровня находится в функции tcp_v4_rcv (находится в файле linux / net / ipv4 / tcp ipv4.c), которая выполняет проверку заголовка TCP и другую обработку.
    2. Вызовите _tcp_v4_lookup, чтобы найти открытый сокет пакета. Если его не найти, пакет будет удален. Затем проверьте состояние сокета и подключения.
    3. Если для сокета и соединения все в порядке, вызовите tcp_prequeue, чтобы пакет вошел в пользовательское пространство из ядра и поместил его в очередь приема сокета. Затем сокет будет разбужен, вызовет системный вызов и, наконец, вызовет функцию tcp_recvmsg, чтобы получить сегмент из очереди приема сокета.

    1.2.4 Уровень приемника-приложения

    1. Каждый раз, когда пользовательское приложение вызывает read или recvfrom, этот вызов будет сопоставлен с системным вызовом sys_recv в /net/socket.c и преобразован в вызов sys_recvfrom, а затем будет вызвана функция sock_recgmsg.
    2. Для сокетов INET будет вызван метод inet_recvmsg в / net / ipv4 / af inet.c, который вызовет метод получения данных соответствующего протокола.
    3. Для TCP вызовите tcp_recvmsg. Эта функция копирует данные из буфера сокета в пользовательский буфер.
    4. Для UDP любой из трех системных вызовов recv () / recvfrom () / recvmsg () может быть вызван из пользовательского пространства для получения пакета UDP, и эти системные вызовы в конечном итоге вызовут метод udp_recvmsg в ядре.


    2. Linux структура данных sk_buff struct и очередь (очередь)

    2.1 sk_buff

    2.1.1 Что такое sk_buff

    Когда сетевой пакет обрабатывается ядром, данные нижележащего протокола передаются на более высокий уровень, и при передаче данных процесс меняется на противоположный. Данные (включая заголовок и нагрузку), генерируемые различными протоколами, непрерывно передаются на нижний уровень, пока они не будут окончательно отправлены. Поскольку скорость этих операций критична для производительности сетевого уровня, ядро ​​использует специальную структуру, называемую sk_buff, файл определения которой находится вskbuffer.h. Буферы сокетов используются для обмена данными на уровне реализации сети без копирования или депакетирования пакетов данных - это значительный выигрыш в скорости. Чтобы

    • sk_buff - это основная структура данных сети Linux, и ее файл определения находится вskbuffer.h。
    • Буфер ядра сокета (skb) - это буфер, используемый сетевым стеком ядра Linux (от L2 до L4) для обработки сетевых пакетов (пакетов), его тип - sk_buffer. Проще говоря, один skb представляет пакет в сетевом стеке Linux; несколько skb, созданных сегментацией TCP, и IP-пакет хранятся в виде списка skb.
    • struct sock имеет три очереди skb (очередь sk_buffer): rx, tx и err.


    Его основные конструктивные элементы:

    2.1.2 Основная операция skb

    (1) Размещение skb = alloc_skb (len, GFP_KERNEL)


    (2) Добавить полезную нагрузку (skb_put (skb, user_data_len))


    (3) Используйте skb-> push, чтобы добавить заголовок протокола, или skb-> pull, чтобы удалить заголовок.


    2.2 Очередь драйверов, используемая сетевым стеком Linux (очередь драйверов)

    (Выдержки из этой главыQueueing in the Linux Network Stack by Dan Siemon)

    2.2.1 Очередь


    Между стеком IP и драйвером сетевого адаптера существует очередь драйверов. Обычно он реализуется как кольцевой буфер FIFO, который можно рассматривать просто как фиксированный размер. Эта очередь не содержит пакетных данных. Напротив, она сохраняет только указатель буфера ядра сокета (skb), а использование skb описано в предыдущем разделе на протяжении всей обработки сетевого стека ядра.

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

    Если TSO / GSO не используется, длина пакетов, отправленных из IP-стека в эту очередь, должна быть меньше MTU.

    2.2.2 размер skb - максимальный размер по умолчанию - NIC MTU

    Большинство сетевых карт имеют атрибут фиксированной максимальной единицы передачи (MTU), который представляет собой размер самого большого кадра, который может передать сетевое устройство. Для Ethernet значение по умолчанию составляет 1500 байт, но некоторые сети Ethernet могут поддерживать кадры jumbo размером до 9000 байт. В стеке IP-сети MTU указывает размер самого большого пакета, который может быть отправлен на NIC. Например, если приложение записывает 2000 байтов данных в сокет TCP, стеку IP необходимо создать два IP-пакета, чтобы размер каждого пакета был равен или меньше 1500 байтов. Можно видеть, что для передачи больших объемов данных относительно небольшой MTU приведет к передаче большого количества небольших пакетов в очередь драйвера. Это называется фрагментацией IP.

    На следующем рисунке показана фрагментация IP-пакета с полезной нагрузкой 1500 байтов при MTU 1000 и 600:

    > структура сетевого стека Linux с точки зрения его уровней, а также рассматриваются некоторые из основных структур.

    Это даже на введение не тянет.


    Ну написано все доступно и понятно, но слишком уж поверхностная информация.

    С тем же успехом можно было написать "Анатомия сетевого стека в оффтопике", и не менять ни одной буквы статьи

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


    Тема вообще ниразу не раскрыта. Это же самая сложная и запутанная подсистема в линуксовом ядре. А тут. 2 картинки и 3 замечания.


    Чтото кислая статья. Новичку бесполезна, опытному еще более бесполезна.

    Говорят, что в свое время Линус так и не осилил написать оригинальную реализацию TCP/IP стека, поэтому весь код линупсоды стянули у Sun или BSD, я уже не помню.

    >Говорят, что в свое время Линус так и не осилил написать оригинальную реализацию TCP/IP стека, поэтому весь код линупсоды стянули у Sun или BSD, я уже не помню.

    Саныч, это твои ночные фантазии - в TCP/IP стеке ядра Linux нет ни строчки из исходников Sun или BSD. Ни единой. Он несколько раз переписывался и даже в 1.0 ядре был собственный стек.


    > Говорят, что в свое время Линус так и не осилил написать оригинальную реализацию TCP/IP стека, поэтому весь код линупсоды стянули у Sun или BSD, я уже не помню.

    Говорят, что Sun-ch почти разумная матка африканских муравьев, подключенныя через диоды к новому кластеру Sun.


    > подключенныя через диоды к новому кластеру Sun

    Девочка, не хами дяде.

    А по какому поводу в свое время было столько воплей? Ведь какой то код они все равно стянули?

    "дяде" стоило бы перестать разговаривать ерунду, тогда и хамить не будут

    >Говорят, что в свое время Линус так и не осилил написать оригинальную реализацию TCP/IP стека, поэтому весь код линупсоды стянули у Sun или BSD, я уже не помню.

    А мне помнится, что мелософты взяли стек из BSD

    Пилять, сходите уже по ссылке, а?

    А господину Джонсу можно доверять, поскольку в отличие от ЛОРОвских говнаналитиков, он работает инженером-консультантом и пишет статьи по заказу IBM.

    >А по какому поводу в свое время было столько воплей? Ведь какой то код они все равно стянули?

    Какой? Может NFS у Sun? Или SLAB (написали хоть и сами, но изначально алгоритм появился в sunos).

    Но это далеко не TCP/IP. Последняя реализация написана Алексеем Кузнецовым с нуля (хотя сокетные интерфейсы и опции он подсматривал в BSD для совместимости) кода оттуда нет, т.к. очень различные низкоуровневые структуры и драйверные интерфейсы.

    >А господину Джонсу можно доверять, поскольку в отличие от ЛОРОвских говнаналитиков, он работает инженером-консультантом и пишет статьи по заказу IBM.

    Ога, производной. Трижды, я повторяю для манагеров и блондиног: ТРИЖДЫ стек был переписан с нуля!

    Какая производная? Даже в 1.0 стек был свой и его через огромные грабли прикручивали.

    TCP/IP стек берет корни в крайнем случае из KA9Q.

    Саныч, а ты не думал, что г-н Джонс, хоть и значимая фигура в ИБМ, может попросту не владеть тонкостями.

    Лично я соглашусь с г-ном rtc. В линухе всегда был свой стек. и г-н Кузнецов внес туды неоценимый вклад, практически переписав его.

    >Саныч, а ты не думал, что г-н Джонс, хоть и значимая фигура в ИБМ, может попросту не владеть тонкостями. >Лично я соглашусь с г-ном rtc. В линухе всегда был свой стек. и г-н Кузнецов внес туды неоценимый вклад, практически переписав его.

    Возможно Джонс имел в виду не то, что был взят код, а то, что была взята логическая структура.


    Статья ниачом. Лучше бы про NAPI порассказывали, про sk_buff по-подробнее.

    > Пилять, сходите уже по ссылке, а?

    Sun-ch, может что-то в линаксовой сети и произвели откуда-то, но такую какашку как sk_buff взять было точно неоткуда, сами придумали.

    опять новость на недостатью от межделмаша? б-у-э-э-э

    >Sun-ch, может что-то в линаксовой сети и произвели откуда-то, но такую какашку как sk_buff взять было точно неоткуда, сами придумали.

    Это вы с mbuf сравниваете? :)

    >>Sun-ch, может что-то в линаксовой сети и произвели откуда-то, но такую какашку как sk_buff взять было точно неоткуда, сами придумали.

    >Это вы с mbuf сравниваете? :)

    угу, такого грязного кода как работа с sk_buff ни в одном стеке не найдешь.


    смех смехом а у меня уже 18 713 накапало!

    > .. линаксовой .

    к логопеду, . <ну ты понял>

    >угу, такого грязного кода как работа с sk_buff ни в одном стеке не найдешь.

    Я с иронией спросил о mbuf - это очень низкопроизводительное решение, да и куча макросов для беганья по цепочкам mbuf не добавляет читабельности.

    Чем mbuf лучше sk_buff? В последнем очень грамотная структура head/tail смещений и указателей на заголовки и список фрагментов. Что в нем "грязного"?


    > это очень низкопроизводительное решение

    да враки это все, ну немного sk_buff побыстрее, но для того, чтобы этого добиться весь код надо превратить в сплошной хак. Ничего грамотного не вижу, смещения - зло, и то, сколько вылизывали линаксовый стек это подтверждает.

    Смещения для уменьшения использования памяти - добро. mbuf делает несколько аллокаций для одного пакета - когда _одну_ убрали в openbsd (нужна была для PF, вместо этого увеличили первый chunk), производительность возросла в 2 (!) раза.

    так что mbuf - это даже не прошлый век.

    зачем это надо, если есть замечательная книга Клакса Вейрле, Франка Пэльке, Хартмута Риттера, Даниеля Мюллера, Марка Бехлера "Linux, сетевая архитектура. Структура и реализация сетевых протоколов вядре"?

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

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

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

    Рисунок 1. Интернет-модель сетевого стека

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

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


    Рисунок 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-модули.

    Рисунок 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.

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

    Идем дальше

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

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

    Как работает сетевой стек

    Коротко

    1. Материнские платы могут поддерживать одновременную работу нескольких процессоров, у которых может быть несколько ядер, у которых может быть несколько потоков.
    2. Оперативная память, NUMA. При использовании нескольких процессоров, как правило, у каждого процессора есть “своя” и “чужая” память. Обе они доступны, но доступ в чужую - медленнее. Бывают архитектуры, в которых память не делится между процессорами на свою и чужую. Сочетание ядер процессора и памяти называется NUMA-нодой. Иногда сетевые карты тоже принадлежат к NUMA-ноде.
    3. Сетевые карты можно поделить по поддержке RSS (аппаратное масштабирование захвата пакетов). Серверные поддерживают, бюджетные и десктопные нет. Зачастую, несмотря на диапазон, указанный в smp_affinity_list у обработчика прерываний, прерывания обрабатываются только одним ядром (как правило CPU0). Все сетевые карты работают следующим образом:
      1. IRQ (top-half): сетевая карта пишет пакеты в свою внутреннюю память. В оперативной памяти той же NUMA-ноды, к которой привязана сетевая карта, под неё выделен кольцевой буфер. По прерыванию процессора сетевая карта копирует свою память в кольцевой буфер и делает пометку, что у неё есть пакеты, которые надо обработать. Кольцевых буферов может быть несколько и они могут обрабатываться параллельно.
      2. Softirq (bottom half): сетевой стек периодически проверяет пометки от сетевых карт о необходимости обработать пакеты. Пакеты из кольцевых буферов обрабатываются, проходят, файрволы, наты, сессии, доходят до приложения при необходимости. На этом уровне есть программный аналог аппаратных очередей, который уместен в случае с сетевыми картами с одной очередью.
      3. Cache locality. Если пакет обрабатывался на определённом CPU и попал в приложение, которое работает там же - это лучший случай, кэши работают максимально эффективно.

      Подробнее - путь пакета из кабеля в приложение:

      Сразу оговорю неточности: не прописано прохождение L2, который ethernet. С L1 как-то сразу в L3 прыгнул.

      Выводы

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

      Есть два способа распределить нагрузку по обработке пакетов между ядрами процессора:

      1. RSS - назначить smp_affinity для каждой очереди сетевой карты.
      2. RPS (можно считать его программным аналогом RSS) - назначить rps_cpus для каждой очереди сетевой карты.
      3. Комбинирование RSS и RPS. Дополнительный буфер с одной стороны - снижает вероятность потери пакета при пиковой нагрузке, с другой стороны - увеличивает общее времени обработки и может за счёт этого увеличивать вероятность потерь. Для сетевых карт с несколькими очередями и равномерным распределением пакетов перенос пакета с ядра на ядро будет использовать драгоценный budget и снизит эффективность использования кэша процессора.

      Как подбирать аппаратное обеспечение

      • Процессоры
        • Число процессоров:
          • Однопроцессорный сервер эффективен, если трафик приходит только на одну сетевую карту, в том числе в её порты, если их несколько.
          • Двухпроцессорный сервер эффективен, если есть больше двух источников трафика, с потоком более 2 Гбит/сек и они обрабатываются отдельными сетевыми картами (не портами).
          • Не нужно больше ядер, чем максимальное суммарное количество очередей всех сетевых карт.
          • Hyper-Threading: не помогает, если обработка пакетов - основной вид нагрузки на процессор. Оценивайте процессор по числу ядер, а не потоков.
          • Размер RX-буферов: чем он больше, тем лучше.
          • Максимальное число очередей: чем их больше, тем лучше. Некоторые (mellanox) сетевые карты поддерживают только число очередей равное степени двойки. Если у Вас 6-ядерный процессор - имеет смысл подобрать другую сетевую карту.
          • Бракованные сетевые карты - вероятность мала, но иногда случается. Заменяем одну сетевую карту на точно такую же и всё замечательно.
          • Драйвер: не рекомендую использовать десктопные карты (обычно D-Link, Realtek).

          Мониторинг и тюнинг сетевого стека

          Мониторинг можно условно поделить на

          • краткосрочный - посмотреть как чувствует себя система прямо сейчас;
          • долгосрочный - с алертами, вот это всё.

          Заниматься тюнингом без краткосрочного мониторинга равноценно случайным действиям. Я разработал инструменты для такого мониторинга - netutils-linux, они протестированы и работают на версиях python 2.6, 2.7, 3.4, 3.6, 3.7 и, возможно на более новых. Изначально делал для технической поддержки, объяснять каждому такой объёмный материал - долго, сложно. Есть фраза “код - лучшая документация”, а моей целью было “инструменты вместо документации”.

          При возникновении проблем - сообщайте о них на github, а лучше присылайте pull-request’ы.

          Мониторинг

          network-top

          Эта утилита отображает полную картину процесса обработки пакетов. Вы увидите равномерность распределения нагрузки (прерывания, softirqs, число пакетов в секунду на ядро процессора и на сетевой интерфейс) на ресурсы сервера, ошибки обработки пакетов. Аномальные значения счётчиков подсвечиваются красным.

          Вверху отображаются источники прерываний, чтобы всё влезало на экран редкие прерывания скрыты. Имена ядер подсвечиваются в зависимости от принадлежности к NUMA-ноде или к процессору.

          Посередине находится самое важное - распределение обработки пакетов по CPU:

          1. Interrupts. Суммарное число прерываний на ядро. Лучше держаться не более 10000 прерываний на 1GHz частоты ядра. В случае с hyperthreading - 5000. Настраивается утилитой rss-ladder .
          2. NET_RX. Число softirq на приём пакетов. Настраивается утилитой autorps .
          3. NET_TX. Число softirq на отправку пакетов. Настраивается утилитой autoxps .
          4. Total. Число обработанных данным ядром пакетов.
          5. Dropped. Число отброшенных в процессе обработки пакетов. Отбрасывание приводит медленной работе сети, хосты повторно отправляют пакеты, у них задержки, потери, люди жалуются в техподдержку.
          6. Time squuezed. Число пакетов, которым не хватило времени для обработки и их обработку отложили на следующий виток цикла. Повод задуматься о дополнительном тюнинге.
          7. CPU Collision. times that two cpus collided trying to get the device queue lock. Ни разу не видел на своей практике.

          Внизу находится статистика по сетевым девайсам.

          • rx-errors - общее число ошибок, обычно суммирует остальные. В какой именно счётчик попадает пакет зависит от драйвера сетевой карты.
          • dropped , fifo , overrun - пакеты, не успевшие обработаться сетевым стеком
          • missed - пакеты, не успевшие попасть в сетевой стек
          • crc - прилетают битые пакеты. Часто бывает следствием высокой нагрузки на коммутатор.
          • length - слишком большие пакеты, которые не влезают в MTU на сетевой карте. Лечится его увеличением: ip link set eth1 mtu 1540 . Постоянное решение для RHEL-based систем - прописать строчку MTU=1540 в файле конфигурации сетевой карты, например /etc/sysconfig/network-scripts/ifcfg-eth1 .
          Флаги утилиты
          • Задать список интересующих девайсов: --devices=eth1,eth2,eth3
          • Отсеять девайсы регуляркой: --device-regex='^eth'
          • Сделать вывод менее подробным, спрятав все специфичные ошибки: --simple
          • Убрать данные об отправке пакетов: --rx-only .
          • Представление данных об объёме трафика можно менять ключами: --bits , --bytes , --kbits , --mbits .
          • Показывать абсолютные значения: --no-delta-mode

          Альтернативные способы получения этой информации:

          Потери могут быть не только на Linux-сервере, но и на порту связанного с ним сетевого оборудования. О том, как это посмотреть можно узнать из документации производителя сетевого оборудования.

          Стандартный top

          server-info

          Если приходится иметь дело с разношёрстными серверами, которые закупались разными людьми, полезно знать какое оборудование у них внутри и насколько оно подходит под текущие нагрузки. Утилита server-info именно для этого и предназначена. У неё два режима:

          • --show - показать оборудование;
          • --rate - оценить оборудование.

          Вывод в YAML. Примеры:

          и оценивать это железо по шкале от 1 до 10:

          Вместо --server можно указать --subsystem , --device или вообще ничего, тогда оценка будет вестись по каждому параметру устройства в отдельности.

          Тюнинг

          maximize-cpu-freq

          Плавающая частота процессора плохо сказывается для нагруженных сетевых серверов. Если процессор может работать на 3.5GHz - не надо экономить немного ватт ценой потерь пакетов. Утилита включает для cpu_freq_governour режим performance и устанавливает минимальную частоту всех ядер в значение максимально-доступной базовой. Узнать текущие значения можно командой:

          Помимо плавающей частоты есть ещё одно но, которое может приводить к потерям: режим энергосбережения в UEFI/BIOS. Лучше его выключить, выбрав режим “производительность” (для этого потребуется перезагрузить сервер).

          rss-ladder

          Утилита автоматически распределяет прерывания “лесенкой” на ядрах локального процессора для сетевых карт с поддержкой нескольких очередей.

          Если сетевых карт несколько, лучше выделить для каждой очереди каждой сетевой карты одно физическое ядро, ответственное только за неё. Если ядер не хватает - число очередей можно уменьшить с помощью ethtool, например: ethtool -L eth0 combined 2 или ethtool -L eth0 rx 2 в зависимости от типа очередей.

          Для RSS по возможности используйте разные реальные ядра, допустим, дано:

          • 1 процессор с гипертредингом
          • 4 реальных ядра
          • 8 виртуальных ядер
          • 4 очереди сетевой карты, которые составляют 95% работы сервера

          В зависимости от того как расположены ядра и потоки (узнать можно по выводу lscpu -e ), использовать 0, 2, 4 и 6 ядра будет эффективнее, чем 0, 1, 2 и 3.

          rx-buffers-increase

          Увеличивает RX-буфер сетевой карты. Чем больше буфер - тем больше пакетов за один тик сетевая карта сможет скопировать с помощью DMA в кольцевой буфер в RAM который уже будет обрабатываться процессором.

          Для работы после перезагрузки в RHEL-based дистрибутивах (платформа Carbon, CentOS, Fedora итд) укажите в настройках интерфейса, например /etc/sysconfig/network-scripts/ifcfg-eth1 , строчку вида:

          autorps

          Утилита для распределения нагрузки на сетевых картах с одной очередью. Вычисляет и применяет маску процессоров для RPS, например:

          Настройка драйверов сетевых карт для работы в FORWARD/Bridge-режимах

          Опции General Receive Offload и Large Receive Offload в таких режимах могут приводить к паникам ядра Linux и их лучше отключать либо при компиляции драйвера, либо на ходу, если это поддерживается драйвером:

          Примеры

          1. Максимально простой

          Параметр Значение
          Число процессоров 1
          Ядер 4
          Число карт 1
          Число очередей 4
          Тип очередей combined
          Режим сетевой карты 1 Гбит/сек
          Объём входящего трафика 600 Мбит/сек
          Объём входящего трафика 350000 пакетов/сек
          Максимум прерываний на ядро в секунду 55000
          Объём исходящего трафика 0 Мбит/сек
          Потери 200 пакетов/сек
          Детали Все очереди висят на CPU0, остальные ядра простаивают

          Решение: распределяем очереди между ядрами и увеличиваем буфер:

          Параметр Значение
          Максимум прерываний на ядро в секунду 15000
          Потери 0
          Детали Нагрузка равномерна

          Пример 2. Чуть сложнее

          Параметр Значение
          Число процессоров 2
          Ядер у процессора 8
          Число карт 2
          Число портов у карт 2
          Число очередей 16
          Тип очередей combined
          Режим сетевых карт 10 Гбит/сек
          Объём входящего трафика 3 Гбит/сек
          Объём исходящего трафика 100 Мбит/сек
          Детали Все 4 порта привязаны к одному процессору

          Одну из 10 Гбит/сек сетевых карт перемещаем в другой PCI-слот, привязанный к NUMA node1.

          Уменьшаем число combined очередей на каждый порт до числа ядер одного физического процессора (временно, нужно делать это при перезагрузке) и распределить прерывания портов. Ядра будут выбраны автоматически, в зависимости от того к какой NUMA-ноде принадлежит сетевая карта. Увеличиваем сетевым картам RX-буферы:

          Необычные примеры

          Не всегда всё идёт идеально:

          Проблема Решение
          Сетевая карта теряет пакеты при использовании RSS. 1 RX-очередь для захвата на CPU0, а обработка на остальных ядрах: autorps --cpus 1,2,3,4,5 eth0
          У сетевой карты несколько очередей, но 99% пакетов обрабатывается одной очередью Причина в том, что у 99% трафика одинаковый хэш, такое бывает при использовании QinQ, Vlan, PPPoE и во время DDoS атак. Решений несколько: от DDoS защититься ранним DROP трафика, перенести агрегацию VLAN на другое оборудование, сменить сетевую карту, которая учитывает Vlan при вычислении хэша для RSS, попробовать использовать RPS
          Сетевые карты intel X710 начала работать без прерываний, вся нагрузка висела на CPU0. Нормальная работа восстановилась после включения и выключения RPS. Почему началось и закончилось - неизвестно.
          Некоторые SFP-модули для Intel 82599ES при обновлении драйвера (сборка ixgbe из исходников с sourceforge) “пропадают” из списка сетевых карт и даже флаг unsupported_sfp=1 не помогает. При этом в lspci этот порт отображается, второй аналогичный порт работает, а в dmesg на оба порта одинаковые warning’и. Не нашлось.
          Некоторые драйверы сетевых карт работают с числом очередей только равным степени двойки Замена сетевой карты или процессора.

          Блог Олега Стрижеченко

          30% личного, 20% linux, 30% наблюдения за разработкой, 5% книги, 10% математика и статистика, 10% шуток

          Читайте также: