Анатомія мережевого стека в Linux, Для системного адміністратора

Анатомія мережевого стека в Linux

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

Вступ до протоколів

У той час як формальне введення в роботу в мережі відсилає нас до моделі взаємодії відкритих систем (OSI - Open Systems Interconnection), це введення в основний мережевий стек в Linux використовує чотирирівневу модель, відому як модель Інтернет (Internet model) (див. рисунок 1) . Малюнок 1. Інтернет-модель мережевого стека

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

Архітектура базової мережі

Тепер перейдемо до архітектури мережевого стека 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.

Під час ініціалізації драйвер пристрою виділяє місце для структури 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