Розробка мережевого системного програмного забезпечення на базі ядра ОС Linux

Структура мережевої підсистеми Linux. Створення мережевого інтерфейсу. Передача пакетів та аналіз поведінки інтерфейсу. Протокол транспортного рівня. Використання модулів ядра. Вплив маршрутизації на процес розробки і налагодження мережевих модулів.

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык украинский
Дата добавления 23.05.2013
Размер файла 56,2 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

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

Размещено на http://www.allbest.ru/

Міністерство освіти і науки, молоді та спорту України

Тернопільський національний технічний університет імені Івана Пулюя

Комп'ютерні системи та мережі

КУРСОВИЙ ПРОЕКТ

З Системного програмного забезпечення

на тему: Розробка мережевого системного програмного забезпечення на базі ядра ОС Linux

Керівник: Луцків А.М

м. Тернопіль - 2013

Міністерство освіти і науки, молоді та спорту України

Тернопільський національний технічний університет імені Івана Пулюя

Кафедра Комп'ютерні системи та мережі

Дисципліна Системне програмне забезпечення

Напрям підготовки 6.050102 “Комп'ютерна інженерія”

Курс 4 Група CI-42 Семестр 8

ЗАВДАННЯ

на курсову роботу

Студентові Радченку Назару Володимировичу

(прізвище, ім'я, по батькові)

1. Тема роботи Розробка мережевого системного програмного забезпечення на

базі ядра ОС Linux.

2. Термін здачі студентом закінченої роботи 16.05.2013

3. Вихідні дані до роботи Згідно стандартам 4.4BSD і POSIX.

4. Зміст розрахунково-пояснювальної записки (перелік питань, які підлягають розробці)

1.1 Структура мережевої підсистеми Linux;

1.2 Використовувані інструменти;

1.3 Структура sk_buff;

1.4 Створення мережевого інтерфейсу;

1.5 Структура net_device;

2.1 Традиційний підхід для реалізації мережевих інтерфейсів;

2.2 Високошвидкісні інтерфейси;

2.3 Передача пакетів;

2.4 Віртуальний мережевий інтерфейс;

2.5 Аналіз поведінки інтерфейсу;

2.6 Протокол мережевого рівня;

2.7 Протокол транспортного рівня;

3.1 Протокольні фільтри;

3.2 Вплив маршрутизації на процес розробки і налагодження мережевих модулів;

3.3 Допоміжні API ядра;

3.4 Операції з файлами;

3.5 Запуск нових процесів.

5. Перелік графічного (ілюстративного) матеріалу, якщо передбачено

6. Дата видачі завдання 5.03.2013 р.

КАЛЕНДАРНИЙ ПЛАН

п/п

Назва етапів курсової роботи

Термін виконання етапів роботи

Примітка

1

Отримання завдання на КР

1.03.2013 - 5.03.213

2

Ознайомлення з вихідними даними

6.03.2013 - 8.03.2013

3

Огляд літературних джерел

9.03.2013 - 15.03.213

4

Опис теоретичної частини

16.03.2013 - 30.03.2013

5

Реалізація практичної частини

1.04.2013 - 18.04.2013

6

Оформлення коду програм

19.04.2013 - 23.04.2013

7

Оформлення ПЗ КР

24.04.2013 - 5.05.2013

8

Попередній захист КР

6.05.2013 - 10.05.2013

9

Захист КР

11.05.2013 - 16.02.2013

Студент

Керівник роботи Лукців А.М

ЗМІСТ

ВСТУП

1. Ознайомлення з Мережевою підсистемою

1.1 Структура мережевої підсистеми Linux

1.2 Використовувані інструменти

1.3 Структура sk_buff

1.4 Створення мережевого інтерфейсу

1.5 Структура net_device

2. ПРИНЦИПИ РОБОТИ З МЕРЖЕВОЮ ПІДСИСТЕМОЮ

2.1 Традиційний підхід для реалізації мережевих інтерфейсів

2.2 Високошвидкісні інтерфейси

2.3 Передача пакетів

2.4 Віртуальний мережевий інтерфейс

2.5 Аналіз поведінки інтерфейсу

2.6 Протокол мережевого рівня

2.7 Протокол транспортного рівня

3. Додаткові аспекти використання модулів ядра

3.1 Протокольні фільтри

3.2 Вплив маршрутизації на процес розробки і налагодження мережевих модулів

3.3 Допоміжні API ядра

3.4 Операції з файлами

3.5 Запуск нових процесів

ВИСНОВКИ

ПЕРЕЛІК ВИКОРИСТАНОЇ ЛІТЕРАТУРИ

ВСТУП

Мережева підсистема - це розгалужена і обширна частина Linux, формально не пов'язана з драйверами пристроїв, так як при використані мережевого обладнання пристроїв в /dev не створюється, а замість цього оголошуються мережеві інтерфейси. Однак вивчення питань взаємодії з мережевими пристроями та протоколами з коду модулів ядра було вирішено включити в огляд драйверів пристроїв саме через функціонал близькості предметів.

Цікавість розробника модулів ядра до мережевої підсистеми може бути зв'язана з тим, що саме через постійне розширення спектра протоколів і комунікаційних пристроїв, задачі по розробці драйверів для специфічних комунікаційних пристроїв з'являються більш частіше, чим для любих інших видів пристроїв.

Реалізація мережевої підсистеми Linux організована так, щоб не залежати від особливостей реалізації протоколів. Основною структурою даних, яка описує мережевий інтерфейс (пристрій), є struct net_device, а основною структурою, за допомогою якої відбувається обмін даними між мережевими рівнями і на основі якої побудована робота всієї мережевої підсистеми є буфер сокета - struct sk_buff (файл <linux / skbuff. h>).

Хоча це тільки початковий етап у процесі створення модуля мережевого пристрою, отриманої інформації повинно бути досить, щоб скласти загальну картину того, в якій області вести пошук рішень при проектуванні власних модулів мережевих пристроїв. Тому представлена ??інформація про мережеві можливості модулів ядра зможе послужити відправною точкою для початку самостійних експериментів.

Дана робота виконана в рамках освітніх ініціатив компанії IBM у Тернопільському національному технічному університеті ім. Івана Пулюя. В ході її виконання використано навчальні матеріали компанії IBM.

1. ОЗНАЙОМЛЕННЯ З МЕРЕЖЕВОЮ ПІДСИСТЕМОЮ LINUX

1.1 Структура мережевої підсистеми Linux

Мережева підсистема Linux набагато більш «заплутана», ніж інтерфейси для роботи з пристроями. Але, незважаючи на велику кількість можливостей ( наприклад, якщо судити за кількістю обслуговуючих мережевих утиліт: ifconfig, ip, netstat і тд), мережева підсистема Linux, з позиції розробника ядра, виглядає логічнішою і прозорішою, ніж, наприклад, ті ж інтерфейси для роботи з пристроями. Ця підсистема в основному орієнтована на обслуговування Ethernet-протоколів на канальному рівні і TPC/IP на транспортному рівні, але також розширюється і на інші типи протоколів, таким чином, покриваючи весь спектр можливостей. На даний момент мережева підсистема Linux забезпечує підтримку найширшого спектра протоколів і пристроїв:

· дротове з'єднання Ethernet (LAN);

· бездротові з'єднання Wifi (LAN) і Bluetooth (PAN);

· різноманітні провідні пристрої «останньої милі» (WAN): E1/T1/J1, різні модифікації DSL;

· бездротові модеми (WAN), що відносяться до різноманітних протоколів, мережам і модифікаціям: GSM, GPRS, EDGE, CDMA, EV-DO (EVDO), WIMAX, LTE.

Весь цей спектр і деякі класи менш поширених технологій підтримуються єдиною мережевою підсистемою.

Мережева модель TCP/IP, як відомо, дуже умовно узгоджується ( або зовсім не узгоджується ) з 7-ми рівневою OSI-моделлю взаємодії відкритих систем. Це пов'язано з тим, що модель взаємодії TCP/IP була розроблена раніше моделі OSI. На платформі Linux склалася наступна термінологія поділу на підрівні, якою користуються розробники ядра:

· все, що відноситься до підтримки обладнання та канальному рівню, описується як мережеві інтерфейси і позначається як L2 (переважно це Ethernet);

· протоколи мережевого рівня OSI (IP/IPv4/IPv6, IPX, RIP і т.д.) - як мережевий рівень стека протоколів або рівень L3;

· все те, що відноситься до вище лежачим рівнями (сеансовий, представницький, прикладний) моделі OSI (наприклад: SSH, SIP, RTP і т.д.), не проявляється в ядрі і відноситься вже до області клієнтських і серверних утиліт простору користувача.

Така числова нумерація мережевих шарів (L2, L3, L4) відповідає аналогічним рівням моделі OSI. Зверніть увагу, що в термінології, прийнятої в Linux, немає шару L1, так як це фізичний рівень передачі даних, не потрапляє в коло інтересів розробників ядра Linux. Точно також в коло інтересів розробників ядра Linux не потрапляють всі мережеві шари, що лежать вище L4, так як це вже протоколи і додатки користувацького рівня, хоча саме там створюються і споживаються мережеві пакети

1.2 Використовувані інструменти

На відміну від решти системних пристроїв, яким відповідають імена в каталозі /dev, мережеві пристрої створюють мережеві інтерфейси, які не відображаються як іменовані пристрої, але мають свій набір конфігураційних параметрів (MAC адресу, IP адреса, маска мережі і т.д). Інтерфейси можуть бути фізичними (відображаючи реальні апаратні мережеві пристрої, наприклад, eth0 - адаптер Ethernet) або логічними (що відображають деякі модельовані поняття, наприклад tap0 - тунельний інтерфейс). Одному реальному апаратному мережевому пристрою може відповідати одночасно кілька різних мережевих інтерфейсів.

При розробці модулів ядра, що підтримують мережеві пристрої, постійно потрібно спостерігати за станом і керувати різними мережевими інтерфейсами. Найвідомішим інструментом, використовуваним для цієї мети, є утиліта ifconfig.

$ ifconfig

...

cipsec0 Link encap:Ethernet HWaddr 00:0B:FC:F8:01:8F

inet addr:192.168.27.101 Mask:255.255.255.0

inet6 addr: fe80::20b:fcff:fef8:18f/64 Scope:Link

UP RUNNING NOARP MTU:1356 Metric:1

...

wlan0 Link encap:Ethernet HWaddr 00:13:02:69:70:9B

inet addr:192.168.1.21 Bcast:192.168.1.255 Mask:255.255.255.0

inet6 addr: fe80::213:2ff:fe69:709b/64 Scope:Link

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

Тут показані два мережевих інтерфейси: фізична бездротова мережа Wi-Fi (wlan0) і віртуальний інтерфейс (віртуальна приватна мережа, VPN), створений програмними засобами (CiscoSystemVPNClient) від Cisco System (cipsec0), - працюють через один і той же фізичний канал (що підтверджує сказане вище про можливість декількох мережевих інтерфейсів над одним каналом). Для управління створюваним мережевим інтерфейсом (наприклад, операції up або down), на відміну від діагностики, утиліта ifconfig зажадає прав root.

Менш відомим, але більш функціональним інструментом є утиліта ip (в деяких дистрибутивах може знадобитися окреме встановлення пакету, відомого під ім'ям iproute2). Ось результати запуску утиліти ip на цій же конфігурації:

$ ip link

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN qlen 1000

link/ether 00:15:60:c4:ee:02 brd ff:ff:ff:ff:ff:ff

...

6: cipsec0: <NOARP,UP,LOWER_UP> mtu 1356 qdisc pfifo_fast state UNKNOWN qlen 1000

link/ether 00:0b:fc:f8:01:8f brd ff:ff:ff:ff:ff:ff

$ ip addr show dev cipsec0

6: cipsec0: <NOARP,UP,LOWER_UP> mtu 1356 qdisc pfifo_fast state UNKNOWN qlen 1000

link/ether 00:0b:fc:f8:01:8f brd ff:ff:ff:ff:ff:ff

inet 192.168.27.101/24 brd 192.168.27.255 scope global cipsec0

inet6 fe80::20b:fcff:fef8:18f/64 scope link

valid_lft forever preferred_lft forever

Хоча утиліта ip має дуже розгалужений синтаксис, вона включає і повноцінну систему підказок.

$ ip help

Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }

ip [ -force ] -batch filename

where OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |

tunnel | maddr | mroute | monitor | xfrm }

OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |

-f[amily] { inet | inet6 | ipx | dnet | link } |

-o[neline] | -t[imestamp] | -b[atch] [filename] }

$ ip addr help

Usage: ip addr {add|change|replace} IFADDR dev STRING [ LIFETIME ]

[ CONFFLAG-LIST]

ip addr del IFADDR dev STRING

ip addr {show|flush} [ dev STRING ] [ scope SCOPE-ID ]

Цими утилітами для роботи з мережевими інтерфейсами ми будем користуватись при розробці модулів ядра, що створюють такі інтерфейси.

Ще один клас утиліт, який нам неодмінно знадобиться при створенні мережевих модулів - це утиліти для аналізу мережевого трафіку. Існує безліч подібних інструментів, тому ми назвемо лише більш відомі: tcpdump (консольна утиліта) або її функціональний еквівалент з GUI інтерфейсом Wireshark. Ось як може виглядати протокол обміну на одному вибраному мережевому інтерфейсі (показано тільки початок виконання операції ping на цей інтерфейс з зовнішнього хоста LAN):

$ ip addr show dev p7p1

3: p7p1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000

link/ether 08:00:27:9e:02:02 brd ff:ff:ff:ff:ff:ff

inet 192.168.56.101/24 brd 192.168.56.255 scope global p7p1

inet6 fe80::a00:27ff:fe9e:202/64 scope link

valid_lft forever preferred_lft forever

$ sudo tcpdump -i p7p1

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on p7p1, link-type EN10MB (Ethernet), capture size 65535 bytes

... ARP, Request who-has 192.168.56.101 tell 192.168.56.1, length 46

... ARP, Reply 192.168.56.101 is-at 08:00:27:9e:02:02 (oui Unknown), length 28

... IP 192.168.56.1 > 192.168.56.101: ICMP echo request, id 2478, seq 1, length 64

Тут видно, як працює ARP-механізм дозволу IP-адресів в локальній мережі ( початок протоколу) та прийом і передача IP-пакетів (тип протоколу ICMP).

Примітка: У прикладі спеціально показано ім'я (p7p1) дротового (Ethernet) мережевого інтерфейсу в тому вигляді, який він може мати в деяких останніх (Fedora 16, 17) дистрибутивах Linux (замість eth0, eth1, …). Таке позначення пов'язує мережевий інтерфейс з адресом, який реалізує його мережевий адаптер на шині PCI.

1.3 Структура sk_buff

Буфер сокета складається з двох частин:

· керуючі дані, що знаходяться в структурі struct sk_buff;

· дані пакета (вказуються в struct sk_buff покажчиками head і data).

Буфери сокетів упорядковуються у вигляді черги (struct sk_queue_head) за допомогою своїх двох перших полів next та prev.

Лістинг 1. Фрагмент структури sk_buff

typedef unsigned char *sk_buff_data_t;

struct sk_buff {

struct sk_buff *next; /* ці два елементи повинні бути оголошені першими. */

struct sk_buff *prev;

...

sk_buff_data_t transport_header;

sk_buff_data_t network_header;

sk_buff_data_t mac_header;

...

unsigned char *head,

*data;

...

};

Структура вкладеності заголовків мережевих рівнів в точності відповідає структурі інкапсуляції мережевих протоколів всередині один одного.

Примірники даних типу struct sk_buff:

· створюються при надходженні чергового мережевого пакету з зовнішнього фізичного середовища (потрібно враховувати можливість сегментації пакетів). Про цю подію сповіщає переривання (IRQ), що генерується мережевим адаптером. При цьому створюється (або витягується з пулу використаних) примірник буфера сокета, який заповнюється даними від вхідного пакета і далі передається на вгору від мережевого шару до додатку прикладного рівня, що є одержувачем пакета. Після цього екземпляр буфера сокета знищується (утилізується);

· створюються в середовищі додатка прикладного рівня, є відправником пакету даних. Пакет даних, що відправляються поміщається в створений буфер сокета, який передається вниз від мережевого шару до канального рівня L2. На цьому рівні здійснюється фізична передача пакета через мережевий адаптер. У разі успішного завершення передачі (що підтверджується перериванням, що генерується мережив адаптером) буфер сокета знищується (утилізується). При відсутності підтвердження відправки зазвичай робиться кілька повторних спроб перш, ніж прийняти рішення про помилку каналу.

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

1.4 Створення мережевого інтерфейсу

linux інтерфейс мережевий маршрутизація

Мережевий інтерфейс - це назва тієї кінцевої точки, де починається або завершується обробка буфера сокета (пакету даних). Ім'я мережевого інтерфейсу завершується числовим суфіксом - порядковим номером даного інтерфейсу і є унікальним в системі, відповідаючи імені в каталозі /dev для потокових пристроїв. Мережевий інтерфейс пов'язує воєдино безліч параметрів канального рівня (наприклад, апаратний MAC-адресу) з параметрами більш високих рівнів (наприклад, IP адрес).

$ ifconfig wlan0

wlan0 Link encap:Ethernet HWaddr 00:13:02:69:70:9B

inet addr:192.168.1.22 Bcast:192.168.1.255 Mask:255.255.255.0

inet6 addr: fe80::213:2ff:fe69:709b/64 Scope:Link

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

Створимо і зареєструємо у системі новий мережевий інтерфейс, вихідний код якого можна знайти в архіві network.tgz. Наведені приклади засновані на концепціях, наведених у книзі «Writing Linux Device Drives».

Лістинг 2. Створення мережевого інтерфейсу

#include <linux/module.h>

#include <linux/netdevice.h>

static struct net_device *dev;

static int my_open( struct net_device *dev ) {

printk( KERN_INFO "Hit: my_open(%s)\n", dev->name );

/* запуск черги для передачі даних */

netif_start_queue( dev );

return 0;

}

static int my_close( struct net_device *dev ) {

printk( KERN_INFO "Hit: my_close(%s)\n", dev->name );

/* зупинка черги для передачі даних */

netif_stop_queue( dev );

return 0;

}

static int stub_start_xmit( struct sk_buff *skb, struct net_device *dev ) {

dev_kfree_skb( skb );

return 0;

}

static struct net_device_ops ndo = {

.ndo_open = my_open,

.ndo_stop = my_close,

.ndo_start_xmit = stub_start_xmit,

};

static void my_setup( struct net_device *dev ) {

int j;

printk( KERN_INFO "my_setup(%s)\n", dev->name );

/* вказати значення MAC-адреси */

for( j = 0; j < ETH_ALEN; ++j )

dev->dev_addr[ j ] = (char)j;

ether_setup( dev );

dev->netdev_ops = &ndo;

}

static int __init my_init( void ) {

printk( KERN_INFO "Loading stub network module:...." );

dev = alloc_netdev( 0, "fict%d", my_setup );

if( register_netdev( dev ) ) {

printk( KERN_INFO " Failed to register\n" );

free_netdev( dev );

return -1;

}

printk( KERN_INFO "Succeeded in loading %s!\n", dev_name( &dev->dev ) );

return 0;

}

static void __exit my_exit( void ) {

printk( KERN_INFO "Unloading stub network module\n" );

unregister_netdev( dev );

free_netdev( dev );

}

module_init( my_init );

module_exit( my_exit );

Варто звернути увагу на виклик alloc_netdev(), який в якості параметра отримує форматний шаблон (% d) імені нового інтерфейсу: ми задаємо префікс імені (fict), а система сама привласнює перший вільний номер інтерфейсу з таким префіксом. Зверніть також увагу, як в циклі MAC-адресу інтерфейсу заповнюється фіктивним значенням 00:01:02:03:04:05. Створений мережевий пристрій можна встановити в системі, як показано нижче.

$ sudo insmod ./network.ko

$ dmesg | tail -n4

[ 7355.005588] Loading stub network module:....

[ 7355.005597] my_setup()

[ 7355.006703] Succeeded in loading fict0!

$ ip link show dev fict0

5: fict0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000

link/ether 00:01:02:03:04:05 brd ff:ff:ff:ff:ff:ff

$ sudo ifconfig fict0 192.168.56.50

$ dmesg | tail -n6

[ 7355.005588] Loading stub network module:....

[ 7355.005597] my_setup()

[ 7355.006703] Succeeded in loading fict0!

[ 7562.604588] Hit: my_open(fict0)

[ 7573.442094] fict0: no IPv6 routers present

$ ping 192.168.56.50

PING 192.168.56.50 (192.168.56.50) 56(84) bytes of data.

64 bytes from 192.168.56.50: icmp_req=1 ttl=64 time=0.253 ms

...

^C

--- 192.168.56.50 ping statistics ---

4 packets transmitted, 4 received, 0% packet loss, time 3000ms

rtt min/avg/max/mdev = 0.056/0.105/0.253/0.085 ms

$ ifconfig fict0

fict0 Link encap:Ethernet HWaddr 00:01:02:03:04:05

inet addr:192.168.56.50 Bcast:192.168.56.255 Mask:255.255.255.0

inet6 addr: fe80::201:2ff:fe03:405/64 Scope:Link

1.5 Структура net_device

Як зазначалось вище, основу опису мережевого інтерфейсу становить структура struct net_device, описана у файлі <linux/netdevice.h>. Ця структура, містить не тільки опис апаратних засобів, але і конфігураційні параметри мережевого інтерфейсу по відношенню до вище лежачим протоколам.

Лістинг 3. Фрагмент структури net_device

struct net_device {

char name[ IFNAMSIZ ] ;

unsigned long base_addr; /* I/O-адрес пристрою */

unsigned int irq; /* IRQ-номер пристрою */

unsigned short type; /* тип інтерфейсу */

}

Поле type, наприклад, визначає тип апаратного адаптера з точки зору ARP-механізму вирішення MAC-адресів (файл <linux/if_arp.h>), як показано нижче:

#define ARPHRD_ETHER 1 /* Ethernet 10Mbps */

#define ARPHRD_IEEE80211 801 /* IEEE 802.11 */

Докладний розбір полів структури struct net_device або їх можливих значень не має сенсу, так як ця структура може радикально змінюватися між під версіями ядра; та її аналіз повинен проводитися «за місцем» на основі вивчення названих вище заголовних файлів.

Разом із структурою мережевого інтерфейсу зазвичай створюється і зв'язується в коді модуля приватна структура даних, у якій користувач може розміщувати довільні дані, асоційовані з інтерфейсом. Це стандартна практика для ядра Linux, яка застосовується не тільки в мережевій підсистемі. Покажчик такої приватної структури поміщається в структуру мережевого інтерфейсу, і доступ до нього (а значить і до приватної структури) має визначатись виключно у спеціально визначеній для цього функції netdev_priv(). Нижче наведено фрагмент даної функції, визначеної в ядрі 3.09, але немає жодних гарантій, що в іншій версії ядра не з'явиться жодних змін.

static inline void *netdev_priv(const struct net_device*dev)

{

return (char *)dev + ALIGN( sizeof( struct net_device ), NETDEV_ALIGN );

}

При створені інтерфейсу розмір цієї користувальницької структури перелається в першому параметрі функції alloc_netdev():

child = alloc_netdev( sizeof( struct priv ),"fict%d",&setup);

При успішному створенні мережевого інтерфейсу дана структура буде розміщена в «хвостовій» частині структури struct net_device і стане доступна за викликом netdev_priv().

Всі структури, що описують мережеві інтерфейси, що перебувають у системі, об'єднанні в єдиний список.

Лістинг 4. Отримання списку мережевих інтерфейсів

#include <linux/module.h>

#include <linux/init.h>

#include <linux/netdevice.h>

static int __init my_init( void ) {

struct net_device *dev;

printk( KERN_INFO "Hello: module loaded at 0x%p\n", my_init );

dev = first_net_device( &init_net );

printk( KERN_INFO "Hello: dev_base address=0x%p\n", dev );

while ( dev ) {

printk( KERN_INFO

"name = %6s irq=%4d trans_start=%12lu last_rx=%12lu\n",

dev->name, dev->irq, dev->trans_start, dev->last_rx );

dev = next_net_device( dev );

}

return -1;

}

module_init( my_init );

Завантажимо раніше створений модуль network.ko і переглянемо список існуючих мережевих інтерфейсів.

$ sudo insmod network.ko

$ sudo insmod devices.ko

insmod: error inserting 'devices.ko': -1 Operation not permitted

$ dmesg | tail -n8

Hello: module loaded at 0xf8853000

Hello: dev_base address=0xf719c400

name = lo irq= 0 trans_start= 0 last_rx= 0

name = eth0 irq= 16 trans_start= 4294693516 last_rx= 0

...

name = mynet0 irq= 0 trans_start= 0 last_rx= 0

2. ПРИНЦИПИ РОБОТИ З МЕРЕЖЕВОЮ ПІДСИСТЕМОЮ

2.1 Традиційний підхід до реалізації мережевих інтерфейсів

У рамках цього підходу кожний отриманий мережевий пакет продовжує апаратне переривання по IRQ-лінії адаптера, що служить сигналом на прийом чергового мережевого пакету і створення буфера сокета для його збереження і обробки прийнятих даних. Порядок дій модуля мережевого інтерфейсу при цьому наступний:

1. Зчитавши конфігураційну область мережевого PCI-адаптера при ініціалізації модуля, можна визначити лінію переривання IRQ, яка обслуговуватиме мережевий обмін.

char irq;

pci_read_config_byte( pdev, PCI_INTERRUPT_LINE, &byte );

2. При ініціалізації мережевого інтерфейсу для цієї IRQ-лінії встановлюється обробник переривання my_interrupt ():

request_irq( (int)irq, my_interrupt, IRQF_SHARED, "my_interrupt", &my_dev_id );

3. В обробнику переривання при прийомі нового пакету (це ж переривання може відбуватися і при завершенні відправки пакета, тому необхідно точно визначити його причину) створюється (або запитується з пулу використовуваних) новий екземпляр буфера сокетів:

static irqreturn_t my_interrupt( int irq, void *dev_id ) {

...

struct sk_buff *skb = kmalloc( sizeof( struct sk_buff ), ... );

// заповнення *skb даними, зчитаними з портів мережевого адаптера

netif_rx( skb );

return IRQ_HANDLED;

}

Всі ці дії виконуються не в самому обробнику верхньої половини переривань від мережевого адаптера, а в обробнику відкладеного переривання NET_RX_SOFTIRQ для цієї IRQ-лінії. Останньою дією є передача заповненого сокетного буфера викликом netif_rx() (або netif_receive_skb()), який і запустить процес переміщення буфера вгору по структурі мережевого стека, тобто відзначить відкладене програмне переривання NET_RX_SOFTIRQ для виконання.

2.2 Високошвидкісні інтерфейси

Особливість мережевих інтерфейсів полягає в тому, що їх функціонування має «вибуховий» характер: після тривалих періодів мовчання виникають інтервали пікової активності, коли мережеві пакети (сегментовані на IP-пакети обсяги переданих даних) слідують щільним потоком. Після такого сплеску активності може знову настати «зшиття» або активність знизиться до мінімуму (наприклад, обмін ARP-пакетами для оновлення інформації дозволу локальних адрес). Хоча сучасні Ethernet-адаптери використовують швидкості обміну до 10Gbit / s, але вже навіть при значно меншому навантаженні традиційний підхід до побудови мережевих інтерфейсів стає недоцільним. Так, у періоди високої щільності надходження пакетів:

· нові пакети, що приходять створюють вкладені багаторівневі IRQ-запити при ще обслуговуваному прийомі поточного IRQ;

· асинхронне обслуговування кожного IRQ в щільному потоці створює занадто великі накладні витрати.

Тому в Linux був доданий набір API для обробки подібних потоків пакетів, що надходять з високошвидкісних інтерфейсів, який отримав назву NAPI (New API). Його ідея полягає в тому, щоб здійснювати прийом пакетів не методом апаратного переривання, а методом програмного опитування (polling), точніше, комбінацією цих двох можливостей:

· при вступі першого пакету з «комплекту» зініціюється IRQ-переривання адаптера, як і при традиційному підході;

· в обробнику переривання забороняється надходження подальших запитів переривання з цієї лінії IRQ-запитів для прийому пакетів, але IRQ-запити з цієї ж лінії для відправки пакетів можуть продовжувати находити. Це обмеження реалізується не за рахунок програмної заборони IRQ-лінії з боку процесора, а завдяки запису керуючої інформації в апаратні регістри мережевого адаптера. Тому адаптер повинен передбачати роздільне керування надходження переривань по прийому і передачі, але більшість сучасних високошвидкісних адаптерів володіє такою можливістю;

· після припинення переривань з прийому обробник переходить в режим циклічного зчитування й обробки прийнятих з мережі пакетів, мережевий адаптер при цьому накопичує отримані пакети у внутрішньому кільцевому буфері. Зчитування виробляється до повного вичерпання кільцевого буфера або до певного порогового числа лічених пакетів (10, 20, … ), званого бюджетом (budget) функції полінга;

· це зчитування та обробка пакетів відбувається не в самому обробнику переривання (верхній половині), а в його відстроченої частини. Для кожного прийнятого пакета генерується сокетний буфер для подальшої відправки по стеку мережевих протоколів;

· після завершення циклу програмного опитування і за його результатами встановлюється стан завершення NAPI_STATE_DISABLE (якщо в кільцевому буфері адаптера не залишилося нелічених пакетів) або NAPI_STATE_SCHED (це говорить, що опитування адаптера має бути продовжене, коли ядро наступного разу перейде до циклу опитувань у відкладеному обробнику переривань);

· якщо результатом є NAPI_STATE_DISABLE, то після завершення циклу програмного опитування відновлюється дозвіл генерації переривань по IRQ-лінії прийому пакетів із записом у порти мережевого адаптера.

У коді модуля це має виглядати, як показано нижче за умови, що IRQ-лінія пов'язана з апаратним адаптером, відповідно до вимог традиційного підходу.

1. Програміст модуля зобов'язаний попередньо створити і зареєструвати специфічну для модуля функцію опитування (poll-функцію), використовуючи виклик netif_napi_add() з вказаними параметрами (див. файл <netdevice.h>);

static inline void netif_napi_add( struct net_device *dev,

struct napi_struct *napi,

int (*poll)( struct napi_struct *, int ),

int weight );

o dev - розглянута раніше структура зареєстрованого мережевого інтерфейсу;

o poll - зареєстрована функція програмного опитування, яка буде описана далі;

o weight - пріоритет, встановлювальний розробником для цього інтерфейсу; для 10Mb і 100Mb адаптерів часто вказується значення 16, а для 10Gb і 100Gb - значення 64;

o napi - додатковий параметр, покажчик на спеціальну структуру, яка буде передаватися при кожному виклику poll-функції; поле state цієї структури після виконання функції буде заповнюватися значеннями NAPI_STATE_DISABLE або NAPI_STATE_SCHED; інформацію про цю структуру можна знайти у файлі <netdevice.h>.

2. Приклад функції програмного опитування (яка є специфічною для кожного конкретного завдання і реалізується в коді модуля) приведений в лістингу 1.

Лістинг 1. Функція для програмного опитування

static int my_card_poll( struct napi_struct *napi, int budget ) {

int work_done; // число реально оброблених в циклі опитування мережевих пакетів

// прийом пакетів у відповідності зі специфікацією завдання

work_done = my_card_input( budget, ... );

if( work_done < budget ) {

netif_rx_complete( netdev, napi );

my_card_enable_irq( ... ); // дозволити IRQ прийому

}

return work_done;

}

У представленому фрагменті користувача функція my_card_input() у циклі намагається апаратно вважати budget мережевих пакетів, і для кожного порахованого мережевого пакету створює сокетний буфер і викликає метод netif_receive_skb(), після чого буфер починає рух вгору по стеку протоколів. Якщо кільцевий буфер мережевого адаптера вичерпався до зчитування budget пакетів, то адаптеру дозволяється порушувати переривання по прийому, а ядро викликом netif_rx_complete() повідомляє, що відкладене програмне переривання NET_RX_SOFTIRQ для подальшого виклику функції опитування скасовується. Якщо ж вдалося вважати budget пакетів (і в буфері адаптера, мабуть, залишилися ще не оброблені пакети), то опитування продовжиться при наступному циклі обробки відкладеного програмного переривання NET_RX_SOFTIRQ.

3. У лістингу 2 наведено приклад обробника апаратного переривання по IRQ-лінії мережевого адаптера ( активізується при прибутті першого мережевого пакета ).

Лістинг 2. Функція для обробки переривання

static irqreturn_t my_interrupt( int irq, void *dev_id ) {

struct net_device *netdev = dev_id;

if( likely( netif_rx_schedule_prep( netdev, ... ) ) ) {

my_card_disable_irq( ... ); // Заборонити IRQ прийому

__netif_rx_schedule( netdev, ... );

}

return IRQ_HANDLED;

}

В даному випадку ядро повинне бути повідомлено, що для обробки готова нова порція мережевих пакетів. Для цього:

o виклик netif_rx_schedule_prep() готує пристрій до додавання в список для програмного опитування, встановлюючи стан в NAPI_STATE_SCHED;

o якщо виклик функції netif_rx_schedule_prep () був успішний ( а противне можливо, тільки якщо NAPI вже активний), то виклик __netif_rx_schedule () встановлює пристрій у список для програмного опитування в цикл обробки відкладеного програмного переривання NET_RX_SOFTIRQ.

На цьому опис нової моделі прийому мережевих пакетів можна вважати завершеним, але потрібно враховувати, що кількість пакетів( параметр budget ), яке встановлюється в функції опитування, не повинно бути надмірно великим:

1. Опитування не винно споживати більше одного системного тику ( глобальна змінна jiffies ), інакше це буде створювати диспетчеризацію потоків ядра;

2. Кількість пакетів не повинно бути більше глобального встановленого обмеження.

$ cat /proc/sys/net/core/netdev_budget

300

Після кожного циклу опитування число оброблених пакетів ( повернених функцією опитування) віднімається з цього глобального параметра, і якщо залишок менше нуля, то обробник програмного переривання NET_RX_SOFTIRQ зупиняється.

2.3 Передача пакетів

Вище було описано, як відбувається створення і переміщення сокетного буфера вгору по стеку. Відправка буфера в мережу (тобто рух вниз по стеку) організовано за іншою схемою. При ініціалізації мережевого інтерфейсу створюється таблиця його операцій, одне з полів якої ndo_start_xmit визначає функцію передачі пакета в мережу:

struct net_device_ops ndo = {

.ndo_open = my_open,

.ndo_stop = my_close,

.ndo_start_xmit = stub_start_xmit,

};

Ця функція при виклику повинна забезпечити апаратну передачу отриманого сокета в мережу, після чого знищити або перевернути буфер сокета в пул.

static int stub_start_xmit( struct sk_buff *skb, struct net_device *dev ) {

// ... апаратне обслуговування передачі

dev_kfree_skb( skb );

return 0;

}

Частіше знищення відправного буфера буде відбуватися не при ініціалізації операції, а при її (успішному) завершені, що відстежується по тій же лінії IRQ, згадуваної вище.

2.4 Віртуальний мережевий інтерфейс

Лістинг 1. Віртуальний мережевий інтерфейс.

#include <linux/module.h>

#include <linux/netdevice.h>

#include <linux/etherdevice.h>

#include <linux/moduleparam.h>

#include <net/arp.h>

#define ERR(...) printk( KERN_ERR "! "__VA_ARGS__ )

#define LOG(...) printk( KERN_INFO "! "__VA_ARGS__ )

static char* link = "eth0";

module_param( link, charp, 0 );

static char* ifname = "virt";

module_param( ifname, charp, 0 );

static struct net_device *child = NULL;

struct priv {

struct net_device_stats stats;

struct net_device *parent;

};

static rx_handler_result_t handle_frame( struct sk_buff **pskb ) {

struct sk_buff *skb = *pskb;

if( child ) {

struct priv *priv = netdev_priv( child );

priv->stats.rx_packets++;

priv->stats.rx_bytes += skb->len;

LOG( "rx: injecting frame from %s to %s", skb->dev->name, child->name );

skb->dev = child;

return RX_HANDLER_ANOTHER;

}

return RX_HANDLER_PASS;

}

/*

* методи для запуску і зупинки черги для передачі даних

*/

static netdev_tx_t start_xmit( struct sk_buff *skb, struct net_device *dev ) {

struct priv *priv = netdev_priv( dev );

priv->stats.tx_packets++;

priv->stats.tx_bytes += skb->len;

if( priv->parent ) {

skb->dev = priv->parent;

skb->priority = 1;

dev_queue_xmit( skb );

LOG( "tx: injecting frame from %s to %s", dev->name, skb->dev->name );

return 0;

}

return NETDEV_TX_OK;

}

static struct net_device_stats *get_stats( struct net_device *dev ) {

return &( (struct priv*)netdev_priv( dev ) )->stats;

}

static struct net_device_ops crypto_net_device_ops = {

.ndo_open = open,

.ndo_stop = stop,

.ndo_get_stats = get_stats,

.ndo_start_xmit = start_xmit,

};

static void setup( struct net_device *dev ) {

int j;

ether_setup( dev );

memset( netdev_priv(dev), 0, sizeof( struct priv ) );

dev->netdev_ops = &crypto_net_device_ops;

//встановити значення MAC-адреса

for( j = 0; j < ETH_ALEN; ++j )

dev->dev_addr[ j ] = (char)j;

}

int __init init( void ) {

int err = 0;

struct priv *priv;

char ifstr[ 40 ];

sprintf( ifstr, "%s%s", ifname, "%d" );

child = alloc_netdev( sizeof( struct priv ), ifstr, setup );

if( child == NULL ) {

ERR( "%s: allocate error", THIS_MODULE->name ); return -ENOMEM;

}

priv = netdev_priv( child );

// отримання інформації про батьківський «апаратний» інтерфейс

priv->parent = __dev_get_by_name( &init_net, link );

if( !priv->parent ) {

ERR( "%s: no such net: %s", THIS_MODULE->name, link );

err = -ENODEV; goto err;

}

if( priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK ) {

ERR( "%s: illegal net type", THIS_MODULE->name );

err = -EINVAL; goto err;

}

// скопіюємо параметри батьківського інтерфейсу в його віртуального нащадка

memcpy( child->dev_addr, priv->parent->dev_addr, ETH_ALEN );

memcpy( child->broadcast, priv->parent->broadcast, ETH_ALEN );

if( ( err = dev_alloc_name( child, child->name ) ) ) {

ERR( "%s: allocate name, error %i", THIS_MODULE->name, err );

err = -EIO; goto err;

}

register_netdev( child );

rtnl_lock();

netdev_rx_handler_register( priv->parent, &handle_frame, NULL );

rtnl_unlock();

LOG( "module %s loaded", THIS_MODULE->name );

LOG( "%s: create link %s", THIS_MODULE->name, child->name );

LOG( "%s: registered rx handler for %s", THIS_MODULE->name, priv->parent->name );

return 0;

err:

free_netdev( child );

return err;

}

void __exit exit( void ) {

struct priv *priv = netdev_priv( child );

if( priv->parent ) {

rtnl_lock();

netdev_rx_handler_unregister( priv->parent );

rtnl_unlock();

LOG( "unregister rx handler for %s\n", priv->parent->name );

}

unregister_netdev( child );

free_netdev( child );

LOG( "module %s unloaded", THIS_MODULE->name );

}

module_init( init );

module_exit( exit );

MODULE_AUTHOR( "Oleg Tsiliuric" );

MODULE_AUTHOR( "Nikita Dorokhin" );

MODULE_LICENSE( "GPL v2" );

MODULE_VERSION( "2.1" );

Перехоплення вхідного трафіку батьківського інтерфейсу здійснюється встановленням обробника вхідних пакетів у виклику netdev_rx_handler_unregister(), який став доступний в API ядра, починаючи з версії 2.6.36.

Розглянемо приклад роботи з створеним інтерфейсом, для початку перевіривши, що обраний нами мережевий інтерфейс дійсно існує і функціонує:

$ ip addr show dev p7p1?3: p7p1:

<BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000?

link/ether 08:00:27:9e:02:02 brd ff:ff:ff:ff:ff:ff?

inet 192.168.56.101/24 brd 192.168.56.255 scope global p7p1

Після цього можна встановити створений віртуальний інтерфейс і налаштувати його підмережу, відмінну від p7p1:

$ sudo insmod virt2.ko link=p7p1

$ sudo ifconfig virt0 192.168.50.2

$ ifconfig virt0

virt0 Link encap:Ethernet HWaddr 08:00:27:9E:02:02

inet addr:192.168.50.2 Bcast:192.168.50.255 Mask:255.255.255.0

inet6 addr: fe80::a00:27ff:fe9e:202/64 Scope:Link

Найпростіше створити "протилежний" кінець такої підмережі на іншому LAN-хості, вказавши новий IP як alias для мережевого інтерфейсу цього хоста, як показано нижче.

$ sudo ifconfig vboxnet0:1 192.168.50.1

$ ifconfig

...

vboxnet0 Link encap:Ethernet HWaddr 0A:00:27:00:00:00

inet addr:192.168.56.1 Bcast:192.168.56.255 Mask:255.255.255.0

inet6 addr: fe80::800:27ff:fe00:0/64 Scope:Link

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

...

vboxnet0:1 Link encap:Ethernet HWaddr 0A:00:27:00:00:00

inet addr:192.168.50.1 Bcast:192.168.50.255 Mask:255.255.255.0

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

У представленому прикладі використовується мережевий інтерфейс гіпервізора віртуальних машин VirtualBox, але конфігурація інтерфейсу будь-якого фізичного пристрою виконується аналогічним чином. Тепер з новоствореного віртуального інтерфейсу ми можемо перевірити прозорість мережі відпрвкою ICMP-пакетів.

$ ping 192.168.50.1

PING 192.168.50.1 (192.168.50.1) 56(84) bytes of data.

64 bytes from 192.168.50.1: icmp_req=1 ttl=64 time=0.371 ms

...

^C

--- 192.168.50.1 ping statistics ---

4 packets transmitted, 4 received, 0% packet loss, time 3001ms

rtt min/avg/max/mdev = 0.184/0.251/0.371/0.074 ms

У лістингу 1 був представлений спрощений варіант модуля, який повністю перехоплює трафік батьківського інтерфейсу, заміщаючи його.

2.5 Аналіз поведінки інтерфейсу

Віртуальний мережевий інтерфейс - це потужний інструмент для розробки та налагодження мережевий модулів, що заслуговує окремого розгляду. Процеси, що відбуваються в мережевому інтерфейсі, складно побачити «у відкриту» ( в порівнянні інтерфейсами /dev або /proc ). Тому важливою характеристикою інтерфейсу стає накопичена статистика про процеси, що відбуваються у ньому. Для накопичення статистики роботи мережевого інтерфейсу існує спеціальна структура (визначена у файлі <linux/netdevice.h>). Через великий обсяг нижче показано тільки початок цієї структури:

struct net_device_stats {

unsigned long rx_packets; /* загальне число отриманих пакетів */

unsigned long tx_packets; /* загальне число переданих пакетів */

...

}

Поля цієї структури повинні заповнюватися в коді модуля статистичними даними «пакетів, які проходять» (наприклад, при передачі пакета повинно інкрементуватися значення tx_packets).

У просторі користувача цю структуру можна отримати за допомогою функції в таблиці операцій ndo_get_stats структури net_device_ops (ці поля були показані в лістингу 1). Модуль повинен реалізувати аналогічну функцію і помістити її в net_device_ops структуру. Це необхідно, щоб отримати статистику мережевого інтерфейсу при виклику ifconfig або через інтерфейс файлової системи /proc, як це реалізовано для всіх інших мережевих інтерфейсів:

$ ifconfig wlan0

wlan0 Link encap:Ethernet HWaddr 00:13:02:69:70:9B

inet addr:192.168.1.22 Bcast:192.168.1.255 Mask:255.255.255.0

inet6 addr: fe80::213:2ff:fe69:709b/64 Scope:Link

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

TX packets:9070 errors:0 dropped:0 overruns:0 carrier:0

Існує кілька варіантів розміщення структури net_device_stats, яка повинна повертатися користувачеві:

1. якщо модуль обслуговує єдиний мережевий інтерфейс, то структура може розміщуватися на глобальному рівні коду модуля.

static struct net_device_stats *stats;

...

static struct net_device_stats *my_get_stats( struct net_device *dev ) {

return &stats;

}

...

static struct net_device_ops ndo = {

...

.ndo_get_stats = my_get_stats,

};

2. часто ця структура розміщується як складова частина структури приватних даних (яка розглядалася в попередньому розділі) яку розробник пов'язує з мережевим інтерфейсом.

static struct net_device *my_dev = NULL;

struct my_private {

struct net_device_stats stats;

...

};

...

static struct net_device_stats *my_get_stats( struct net_device *dev ) {

struct my_private *priv = (my_private*)netdev_priv( dev );

return &priv->stats;

}

...

static struct net_device_ops ndo = {

...

.ndo_get_stats = my_get_stats,

}

...

void my_setup( struct net_device *dev ) {

memset( netdev_priv( dev ), 0, sizeof( struct my_private ) );

dev->netdev_ops = &ndo;

}

int __init my_init( void ) {

my_dev = alloc_netdev( sizeof( struct my_private ), "my_if%d", my_setup );

}

3. можна використовувати структуру, реалізовану безпосередньо у визначенні інтерфейсу - структура всередині структури net_device.

static struct net_device_stats *my_get_stats( struct net_device *dev ) {

return &dev->stats;

}

2.6 Протокол мережевого рівня

На цьому рівні забезпечується обробка таких протоколів, як: IP/IPv4/IPv6, IPX, ICMP, RIP, OSPF, ARP, або додавання оригінальних користувальницьких протоколів. Для встановлення обробників мережевого рівня надається API мережевого рівня, оголошений у файлі <linux/netdevice.h>.

struct packet_type {

__be16 type; /* This is really htons(ether_type). */

struct net_device *dev; /* NULL is wildcarded here */

int (*func) ( struct sk_buff *, struct net_device *,

struct packet_type *, struct net_device *);

...

struct list_head list;

};

extern void dev_add_pack( struct packet_type *pt );

extern void dev_remove_pack( struct packet_type *pt );

Фактично, в модулі, що працюють з мережевими або транспортними протоколами, необхідно додавати фільтр, через який будуть проходити буфера сокетів з вхідного потоку інтерфейсу (приклад реалізації вихідного потоку був представлений в попередніх статтях). У нашому випадку в подібну функцію будуть направлятися буфера сокетів, що задовольняють критеріям, закладеним у структурі struct packet_type.

Лістинг 1. Приклад реалізації протоколу мережевого рівня (файл net_proto.c)

#include <linux/module.h>

#include <linux/init.h>

#include <linux/netdevice.h>

int test_pack_rcv( struct sk_buff *skb, struct net_device *dev,

struct packet_type *pt, struct net_device *odev ) {

printk( KERN_INFO "packet received with length: %u\n", skb->len );

return skb->len;

};

#define TEST_PROTO_ID 0x1234

static struct packet_type test_proto = {

__constant_htons( ETH_P_ALL ), // може використовуватися інше значення TEST_PROTO_ID

NULL,

test_pack_rcv,

(void*)1,

NULL

};

static int __init my_init( void ) {

dev_add_pack( &test_proto );

printk( KERN_INFO "module loaded\n" );

return 0;

}

static void __exit my_exit( void ) {

dev_remove_pack( &test_proto );

printk( KERN_INFO "module unloaded\n" );

}

module_init( my_init );

module_exit( my_exit );

MODULE_AUTHOR( "Oleg Tsiliuric" );

MODULE_LICENSE( "GPL v2" );

Основну складність при роботі з подібними модулями представляє процес їх тестування, так як операційна система може не знати такого мережевого протоколу і, отже, не мати утиліт для обміну даними в форматі тестового протоколу. Встановимо створений нами модуль і перевіримо його роботу, як показано нижче.

$ sudo insmod net_proto.ko

$ dmesg | tail -n6

module loaded

packet received with length: 74

packet received with length: 60

packet received with length: 66

packet received with length: 241

packet received with length: 52

$ sudo rmmod net_proto

У цьому прикладі обробник протоколу перехоплює (фільтрує) всі пакети (див. константу ETH_P_ALL в лістингу 1) на всіх мережевих інтерфейсах. При додаванні власного протоколу замість значення ETH_P_ALL мало б використовуватися значення TEST_PROTO_ID, але в такому випадку у нас би не знайшлося інструментів для тестування модуля. Велика кількість ідентифікаторів протоколів (Ethernet Protocol ID's) можна знайти у файлі <linux/if_ether.h>, а найцікавіші з них наведені нижче:

#define ETH_P_LOOP 0x0060 /* Ethernet Loopback packet */

#define ETH_P_IP 0x0800 /* Internet Protocol packet */

#define ETH_P_ARP 0x0806 /* Address Resolution packet */

#define ETH_P_PAE 0x888E /*Port Access Entity(IEEE 802.1X) */

#define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */

Тут же можна знайти опис заголовка Ethernet-пакета, який використовується при заповненні структури struct packet_type:

struct ethhdr {

unsigned char h_dest[ETH_ALEN]; /* ether-адрес отримувача */

unsigned char h_source[ETH_ALEN]; /* ether-адрес відправника */

__be16 h_proto; /* ідентифікатор типу пакета */

} __attribute__((packed));

2.7 Протокол транспортного рівня

На цьому рівні забезпечується обробка таких IP-протоколів, як: UDP, TCP, SCTP і т.д., які описані у файлі <linux/in.h>.

/* Standard well-defined IP protocols. */

enum {

IPPROTO_IP=0, /* Dummy protocol for TCP */

IPPROTO_ICMP=1, /* Internet Control Message Protocol */

IPPROTO_IGMP=2, /* Internet Group Management Protocol */

IPPROTO_TCP=6, /* Transmission Control Protocol */

IPPROTO_UDP=17, /* User Datagram Protocol */

IPPROTO_SCTP=132, /* Stream Control Transport Protocol */

IPPROTO_RAW=255, /* Raw IP packets */

}

Для встановлення обробника протоколів транспортного рівня існує спеціальний API, оголошений у <net/protocol.h>.

// дана структура використовується для реєстрації протоколів

struct net_protocol {

int (*handler)( struct sk_buff *skb );

void (*err_handler)( struct sk_buff *skb, u32 info );

int (*gso_send_check)( struct sk_buff *skb );

struct sk_buff *(*gso_segment)( struct sk_buff *skb, int features );

struct sk_buff **(*gro_receive)( struct sk_buff **head, struct sk_buff *skb );

int (*gro_complete)( struct sk_buff *skb );

unsigned int no_policy:1,

netns_ok:1;

};

// другий параметр функцій inet_ ... _protocol - це константа зі списку IPPROTO_ *

int inet_add_protocol(const struct net_protocol *prot, unsigned char num );

int inet_del_protocol(const struct net_protocol *prot, unsigned char num );

У лістингу 2 представлений приклад модуля, що встановлює обробник для протоколу транспортного рівня.

Лістинг 2. Приклад реалізації протоколу транспортного рівня (файл trn_proto.c)

#include <linux/module.h>

#include <linux/init.h>

#include <net/protocol.h>

int test_proto_rcv( struct sk_buff *skb ) {

printk( KERN_INFO "Packet received with length: %u\n", skb->len );

return skb->len;

};

static struct net_protocol test_proto = {

.handler = test_proto_rcv,

.err_handler = 0,

.no_policy = 0,

};

#define PROTO IPPROTO_RAW

static int __init my_init( void ) {

int ret;

if( ( ret = inet_add_protocol( &test_proto, PROTO ) ) < 0 ) {

printk( KERN_INFO "proto init: can't add protocol\n");

return ret;

};

printk( KERN_INFO "proto module loaded\n" );

return 0;

}

static void __exit my_exit( void ) {

inet_del_protocol( &test_proto, PROTO );

printk( KERN_INFO "proto module unloaded\n" );

}

module_init( my_init );

module_exit( my_exit );

MODULE_AUTHOR( "Oleg Tsiliuric" );

MODULE_LICENSE( "GPL v2" );

Встановимо і перевіримо функціонування нашого модуля для протоколу IPPROTO_RAW:

$ sudo insmod trn_proto.ko

$ lsmod | head -n2

Module Size Used by

trn_proto 780 0

$ cat /proc/modules | grep proto

trn_proto 780 0 - Live 0xf9a26000

$ ls -R /sys/module/trn_proto

/sys/module/trn_proto:

holders initstate notes refcnt sections srcversion

$ sudo rmmod trn_proto

$ dmesg | tail -n60 | grep -v ^audit

proto module loaded

proto module unloaded

Але якщо спробувати встановити обробник для вже оброблюваного (тобто встановленого) протоколу (наприклад, IPPROTO_TPC), то виникне наступна помилка.

$ sudo insmod trn_proto.ko

insmod: error inserting 'trn_proto.ko': -1 Operation not permitted

$ dmesg | tail -n60 | grep -v ^audit

proto init: can't add protocol

$ lsmod | grep proto

$

Як видно, тут виникла вже згадувана раніше складність.

Тому якщо ви плануєте реалізувати підтримку нового (або не використовується в системі) протоколу, то в системі може не виявитися інструментів для його тестування, і попередньо необхідно буде створити тестові додатки прикладного рівня.

· якщо ж спробувати змоделювати роботу нового протоколу під виглядом вже існуючого (наприклад, IPPROTO_UDP), то вам спочатку знадобиться видалити існуючий обробник даного протоколу, що може порушити працездатність системи (наприклад, у випадку з IPPROTO_UDP така дія призведе до руйнування системи розв'язання доменних імен DNS) .

3. ДОДАТКОВІ АСПЕКТИ ВИКОРИСТАННЯ МОДУЛІВ ЯДРА

3.1 Протокольні фільтри

У попередніх розділах розглядалося створення віртуального мережевого інтерфейсу, який перехоплював трафік з реально існуючого фізичного інтерфейсу за допомогою виклику netdev_rx_handler_register (). Але такий спосіб має певними недоліками, так як він став доступний, починаючи з версії 2.6.36 API ядра, і їм можна користуватися тільки на відповідних системах. Також він в деякій мірі складний для вивчення і подальшого використання.

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

Лістинг 1. Віртуальний мережевий інтерфейс.

#include <linux/module.h>

#include <linux/netdevice.h>

#include <linux/etherdevice.h>

#include <linux/inetdevice.h>

#include <linux/moduleparam.h>

#include <net/arp.h>

#include <linux/ip.h>

#define ERR(...) printk( KERN_ERR "! "__VA_ARGS__ )

#define LOG(...) printk( KERN_INFO "! "__VA_ARGS__ )

#define DBG(...) if( debug != 0 ) printk( KERN_INFO "! "__VA_ARGS__ )

static char* link = "eth0";

module_param( link, charp, 0 );

static char* ifname = "virt";

module_param( ifname, charp, 0 );

static int debug = 0;

module_param( debug, int, 0 );

static struct net_device *child = NULL;

static struct net_device_stats stats; // таблиця статистики інтерфейсу

static u32 child_ip;

struct priv {

struct net_device *parent;

};

static char* strIP( u32 addr ) { // висновок IP-адреси в точковій нотації

static char saddr[ MAX_ADDR_LEN ];

sprintf( saddr, "%d.%d.%d.%d",

( addr ) & 0xFF, ( addr >> 8 ) & 0xFF,

( addr >> 16 ) & 0xFF, ( addr >> 24 ) & 0xFF

);

return saddr;

}

static int open( struct net_device *dev ) {

struct in_device *in_dev = dev->ip_ptr;

struct in_ifaddr *ifa = in_dev->ifa_list;

LOG( "%s: device opened", dev->name );

child_ip = ifa->ifa_address;

netif_start_queue( dev );

if( debug != 0 ) {

char sdebg[ 40 ] = "";

sprintf( sdebg, "%s:", strIP( ifa->ifa_address ) );

strcat( sdebg, strIP( ifa->ifa_mask ) );


Подобные документы

  • Неекспортовані символи ядра. Оптимальний підхід до реалізації пошуку символів у ядрі. Виконання, підміна, додавання та приховання системних викликів. Завантаження модуля ядра із програмного коду та з коду іншого модуля. Робота з UNIX-сигналами.

    курсовая работа [84,0 K], добавлен 23.05.2013

  • МАС-адреса як унікальний ідентифікатор мережевого інтерфейсу (зазвичай мережевої карти) для реалізації комунікації пристроїв в мережі на фізичному рівні. IP-адреса: поняття та призначення, принципи та етапи настройки. Основи роботи з утилітами TCP/IP.

    лабораторная работа [161,6 K], добавлен 15.10.2013

  • История создания, архитектура операционной системы и перечень возможностей, реализуемых в Linux. Инструментальные средства и цикл разработки новой версии ядра. Жизненный цикл патча. Структура принятия решений при добавлении новых функций (патчей) в ядро.

    лекция [303,8 K], добавлен 29.07.2012

  • Аналіз системи збору первинної інформації та розробка структури керуючої ЕОМ АСУ ТП. Розробка апаратного забезпечення інформаційних каналів, структури програмного забезпечення. Алгоритми системного програмного забезпечення. Опис програмних модулів.

    дипломная работа [1,9 M], добавлен 19.08.2012

  • Аналіз технічного забезпечення, вибір інструментального програмного забезпечення та середовища розробки програм. Створення класів для реалізації необхідних функцій для роботи програмного засобу. Розробка інтерфейсу для користувача та лістинг програми.

    курсовая работа [343,9 K], добавлен 24.08.2012

  • Створення навчальної програми для вирішення системи лінійних рівнянь різними методами. Детальне покрокове рішення та довідкова теоретична інформація. Структура і функціональне призначення модулів програмного продукту, основні елементи його інтерфейсу.

    курсовая работа [1,9 M], добавлен 20.05.2015

  • Етапи розробки проекту. Вимоги до апаратного і програмного забезпечення, до користувача. Специфікація та структура даних, які мають бути розміщеними в системі. Вигляд інтерфейсу системи програмного забезпечення. Розробка бази даних косметичного салону.

    дипломная работа [1,8 M], добавлен 21.02.2015

  • Структура та побудова модулів для системи віддаленого адміністрування серверів Ajenti. Огляд веб-орієнтованих систем віддаленого адміністрування для linux. Процес розробки та реалізації програмного модуля "Менеджер процесів", системні вимоги до нього.

    дипломная работа [3,0 M], добавлен 09.06.2012

  • Требования к операционной системе Linux, встраиваемые приложения. Предсказуемость поведения, свойства ОС реального времени. Структура ядра; системные вызовы; работа с файлами. Стандартные устройства; обзор программирования; компилирование и линковка.

    лекция [112,2 K], добавлен 29.07.2012

  • UNIX - одна з найпопулярніших в світі операційних систем. Ключеві риси Linux. Порівняльні характеристики 32-розрядних операційних систем. Поверхневий огляд характеристик ядра Linux. Програмні характеристики: базові команди і утиліти, мови програмування.

    курсовая работа [33,3 K], добавлен 07.12.2010

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.