52
МЕЖДУНАРОДНЫЙ ГУМАНИТАРНЫЙ УНИВЕРСИТЕТ http://www.mgu.com.ua КАФЕДРА КОМПЬЮТЕРНОЙ ИНЖЕНЕРИИ КУРСОВАЯ РАБОТА РЕАЛИЗАЦИЯ ДЕЙТАГРАММНОГО РЕЖИМА СТЕКА ПРОТОКОЛОВ Е6 ПОСРЕДСТВОМ СОКЕТОВ ОС LINUX по курсу «Проектирование компьютерных систем и сетей» 4 курс, 9 семестр подготовки бакалавров по специальности Компьютерные системы и сети Выполнил: М.А. ХАРСУН [email protected] Руководитель: д.т.н., профессор Д.А. ЗАЙЦЕВ http://daze.ho.ua Одесса, 2012

Реализация дейтаграммного режима стека протоколов Е6 посредством сокетов ОС Linux

Embed Size (px)

DESCRIPTION

Международный Гуманитарный Университет, Украина, Одесса, 2012 г. 52 стр. ВведениеОбзор сетевой технологии Е6Введение в инструментарий для реализации дейтаграммного режима протокола Е6Сокеты как средства коммуникаций в ОС LinuxВведение в загружаемые модули ядра ОС LinuxРеализация стека E6 через механизм сокетов ОС LinuxИнсталляция и деинсталляция загружаемого модуляОбщая структура модуляКоманды пользователя для работы с функциями модуляВзаимодействие загружаемого модуля и статической части ядраАлгоритмы функций загружаемого модуляАнализ функционирования загружаемого модуля на примерах трассировки основных вызововЗаключениеСписок литературыВ 2007 году, в Украине разработан национальный стек сетевых протоколов Е6 и выполнена его опытная программная реализация через дополнительный системный вызов ядра ОС Linux. Несмотря на некоторые преимущества полученной автономности кода за счет слабой интеграции в среду ядра, указанный подход имеет ряд существенных недостатков, препятствующих дальнейшему развитию национального стека Е6 и его промышленной реализации. В данной работе представлена новая реализация дейтаграмного режима стека сетевых протоколов Е6 на основе стандартного механизма сокетов операционной системы Linux – также как и большинство других известных стеков сетевых протоколов. Разработка является уникальной, поскольку требует досконального изучения среды ядра ОС Linux и существенной интеграции с его структурами данных и функциями. В основе технологии Е6 лежит способ передачи данных в сети с замещением сетевого и транспортного уровней универсальной технологией канального уровня, которая отличаются использованием на всех уровнях эталонной модели взаимодействия открытых систем единых сетевых E6-адресов длиной в шесть байтов и иерархической структурой.В данной работе рассматривается независимая реализация дейтаграмного режима стека Е6 на основе стандартного механизма сокетов операционной системы Linux – также как и большинство других известных стеков сетевых протоколов. Разработка является уникальной, поскольку требует досконального изучения среды ядра ОС Linux и существенной интеграции с его структурами данных и функциями.

Citation preview

МЕЖДУНАРОДНЫЙ ГУМАНИТАРНЫЙ УНИВЕРСИТЕТ

http://www.mgu.com.ua

КАФЕДРА КОМПЬЮТЕРНОЙ ИНЖЕНЕРИИ

КУРСОВАЯ РАБОТА

РЕАЛИЗАЦИЯ ДЕЙТАГРАММНОГО РЕЖИМА

СТЕКА ПРОТОКОЛОВ Е6 ПОСРЕДСТВОМ СОКЕТОВ ОС LINUX

по курсу

«Проектирование компьютерных систем и сетей»

4 курс, 9 семестр

подготовки бакалавров по специальности

Компьютерные системы и сети

Выполнил:

М.А. ХАРСУН

[email protected]

Руководитель:

д.т.н., профессор

Д.А. ЗАЙЦЕВ

http://daze.ho.ua

Одесса, 2012

2

РЕФЕРАТ

В 2007 году, в Украине разработан национальный стек сетевых протоколов

Е6 и выполнена его опытная программная реализация через дополнительный

системный вызов ядра ОС Linux. Несмотря на некоторые преимущества

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

указанный подход имеет ряд существенных недостатков, препятствующих

дальнейшему развитию национального стека Е6 и его промышленной

реализации. В данной работе представлена новая реализация дейтаграмного

режима стека сетевых протоколов Е6 на основе стандартного механизма

сокетов операционной системы Linux – также как и большинство других

известных стеков сетевых протоколов. Разработка является уникальной,

поскольку требует досконального изучения среды ядра ОС Linux и

существенной интеграции с его структурами данных и функциями. В основе

технологии Е6 лежит способ передачи данных в сети с замещением сетевого

и транспортного уровней универсальной технологией канального уровня,

которая отличаются использованием на всех уровнях эталонной модели

взаимодействия открытых систем единых сетевых E6-адресов длиной в

шесть байтов и иерархической структурой.

Ключевые слова: стек сетевых протоколов, Е6, ядро, Linux, загружаемый

модуль, Ethernet

3

СОДЕРЖАНИЕ

Стр. №

ВВЕДЕНИЕ ............................................................................................................ 4

1 ОБЗОР СЕТЕВОЙ ТЕХНОЛОГИИ E6 ......................................................... 5

2 ВВЕДЕНИЕ В ИНСТРУМЕНТАРИЙ ДЛЯ РЕАЛИЗАЦИИ

ДЕЙТАГРАММНОГО РЕЖИМА ПРОТОКОЛА Е6 .................................... 6

2.1 Сокеты как средства коммуникаций в ОС Linux ................................. 6

2.2 Введение в загружаемые модули ядра ОС Linux ............................... 11

3 РЕАЛИЗАЦИЯ СТЕКА E6 ЧЕРЕЗ МЕХАНИЗМ СОКЕТОВ ОС

LINUX .................................................................................................................... 15

3.1 Инсталляция и деинсталляция загружаемого модуля ......................... 15

3.2 Общая структура модуля ........................................................................ 17

3.3 Команды пользователя для работы с функциями модуля .................. 20

3.4 Взаимодействие загружаемого модуля и статической части ядра .... 22

3.5 Алгоритмы функций загружаемого модуля ......................................... 25

3.6 Анализ функционирования загружаемого модуля на примерах

трассировки основных вызовов ........................................................................... 32

ЗАКЛЮЧЕНИЕ ................................................................................................... 36

СПИСОК ЛИТЕРАТУРЫ ................................................................................. 37

ПРИЛОЖЕНИЕ ................................................................................................... 39

4

ВВЕДЕНИЕ

В 2007 году, в Украине разработан национальный стек сетевых

протоколов Е6 [6] и выполнена его опытная программная реализация через

дополнительный системный вызов ядра ОС Linux [7]. Несмотря на некоторые

преимущества полученной автономности кода за счет слабой интеграции в

среду ядра, указанный подход имеет ряд существенных недостатков,

препятствующих дальнейшему развитию национального стека Е6 и его

промышленной реализации. Основным недостатком является требование

перекомпиляции ядра ОС для добавления нового системного вызова и

существенная модификация прикладных интерфейсов; кроме того,

отсутствуют возможности повторного использования средств ядра,

задействованных в реализации других стеков, например TCP/IP.

В основе национального Украинского стека лежит технология Ethernet.

Она не только доминирует на рынке локальных сетей, но и вытесняет другие

в секторах корпоративных магистралей и магистралей операторов связи.

Стандарты 1Гбит/с и 10Гбит/с Ethernet позволяют использовать ее там, где

традиционно использовалась технология PDH/SDH (STM). Скорость

передачи 10Гбит/с является особенно удобной при реализации DWDM-

магистралей. Она соответствует максимальной скорости передачи для

отдельной длины волны и позволяет обеспечить передачу потока 40Гбит/с по

40 доступным длинам волн DWDM. Таким образом, Ethernet становится

универсальной технологией канального уровня, постепенно вытесняя другие

технологии.

В большинстве телекоммуникационных сетей поверх Ethernet

работают протоколы семейства TCP/IP. Своей популярностью это семейство

обязано способности именно протоколов сетевого-сеансового уровней IP,

TCP, UDP интегрировать различные канальные технологии и, таким образом,

объединять различные сети канального уровня. Багаж приложений,

выросших в среде TCP/IP, таких как "всемирная паутина" (HTTP),

5

электронная почта (SMTP), телефония (VoIP), является основной ценностью

указанного семейства протоколов.

В условиях когда и оконечные локальные сети, и магистрали

построены на основе одной и той же технологии (Ethernet) протоколы

сетевого, сеансового уровней (а именно IP, TCP) в семействе TCP/IP

становятся избыточными. Практически все задачи, решаемые этими

протоколами, могут быть решены с помощью протоколов Ethernet.

В данной работе рассматривается независимая реализация

дейтаграмного режима стека Е6 на основе стандартного механизма сокетов

операционной системы Linux – также как и большинство других известных

стеков сетевых протоколов. Разработка является уникальной, поскольку

требует досконального изучения среды ядра ОС Linux и существенной

интеграции с его структурами данных и функциями.

1 ОБЗОР СЕТЕВОЙ ТЕХНОЛОГИИ E6

В основе технологии Е6 [2] лежит способ передачи данных в сети с

замещением сетевого и транспортного уровней универсальной технологией

канального уровня, которая включает использование стандартного формата

заголовка кадра Ethernet с полями адресов длиной в 6 байтов. Они

отличаются использованием на всех уровнях эталонной модели

взаимодействия открытых систем единых сетевых адресов, E6-адресов

длиной в шесть байтов. Они предоставляют возможность использования и

размещения E6-адресов, вместо MAC-адресов в заголовке кадра Ethernet,

который отличается иерархической структурой E6-адресов, которая состоит

из номера сети и номера узла сети. При этом предотвращается

необходимость передачи сигналов касаемо отображения адресов разных

уровней эталонной модели, а также обеспечивается возможность сокращения

адресных таблиц сетевых устройств, благодаря агрегации отдельных адресов

узлов и адресов подсетей в адрес сети следующего уровня иерархии, которая

6

обеспечивает возможность построения глобальных сетей с большим

количеством подключенных узлов.

Вместо протоколов UDP и TCP, используются аппаратные

возможности Ethernet LLC1 и LLC2 соответственно, вместо протокола IP

используется стандартный аппаратный заголовок кадра Ethernet с E6-

адресами, который остается неизменным в процессе доставки кадра к

оконечному узлу.

Сеть построена на специальных коммутирующих маршрутизаторах

KME6, которые подключены друг к другу и к оконечным узлам сети, что

позволяет использовать в KME6 адресные таблицы с агрегацией адресов для

решения задач маршрутизации и индивидуальные адреса для решения задач

коммутации, при этом адрес интерфейса KME6 задается только номером его

порта. Кроме того KME6 использует только информацию со стандартного

заголовка кадра Ethernet, что позволяет сократить объем адресных таблиц и

уменьшить время ретрансляции кадра за счет уменьшения размера шины

адреса и упрощения алгоритмов обработки кадра.

2 ВВЕДЕНИЕ В ИНСТРУМЕНТАРИЙ ДЛЯ РЕАЛИЗАЦИИ

ДЕЙТАГРАММНОГО РЕЖИМА ПРОТОКОЛА Е6

2.1 Сокеты как средства коммуникаций в ОС Linux

Socket API был впервые реализован в операционной системе Berkley

UNIX. Сейчас этот программный интерфейс доступен практически в любой

модификации Unix, в том числе в Linux. Хотя все реализации чем-то

отличаются друг от друга, основной набор функций в них совпадает.

Изначально сокеты использовались в программах на C/C++, но в настоящее

время средства для работы с ними предоставляют многие языки (Perl, Java и

др.).

Сокеты предоставляют весьма мощный и гибкий механизм

межпроцессного взаимодействия (IPC). Они могут использоваться для

организации взаимодействия программ на одном компьютере, по локальной

7

сети или через Internet, что позволяет вам создавать распределѐнные

приложения различной сложности. Кроме того, с их помощью можно

организовать взаимодействие с программами, работающими под

управлением других операционных систем. Например, под Windows

существует интерфейс Window Sockets, спроектированный на основе socket

API.

Сокеты поддерживают многие стандартные сетевые протоколы

(конкретный их список зависит от реализации) и предоставляют

унифицированный интерфейс для работы с ними. Наиболее часто сокеты

используются для работы в IP-сетях. В этом случае их можно использовать

для взаимодействия приложений не только по специально разработанным, но

и по стандартным протоколам - HTTP, FTP, Telnet и т. д. Например,

существует возможность написать собственный Web-броузер или Web-

сервер, способный обслуживать одновременно множество клиентов.

Понятие сокета

Сокет (socket) – это конечная точка сетевых коммуникаций. Он

является чем-то вроде "портала", через которое можно отправлять байты во

внешний мир. Приложение просто пишет данные в сокет; их дальнейшая

буферизация, отправка и транспортировка осуществляется используемым

стеком протоколов и сетевой аппаратурой. Чтение данных из сокета

происходит аналогичным образом.

В программе сокет идентифицируется дескриптором - это просто

переменная типа int. Программа получает дескриптор от операционной

системы при создании сокета, а затем передаѐт его сервисам socket API для

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

Атрибуты сокета

С каждым сокетом связываются три атрибута: домен, тип и протокол.

Эти атрибуты задаются при создании сокета и остаются неизменными на

протяжении всего времени его существования. Для создания сокета

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

8

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Домен определяет пространство адресов, в котором располагается

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

Чаще других используются домены Unix и Internet, задаваемые

константами AF_UNIX и AF_INET соответственно (префикс AF означает

"address family" - "семейство адресов"). При задании AF_UNIX для

передачи данных используется файловая система ввода/вывода Unix. В этом

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

компьютере и не годятся для работы по сети. Константа AF_INET

соответствует Internet-домену. Сокеты, размещѐнные в этом домене, могут

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

(AF_IPX для протоколов Novell, AF_INET6 для новой модификации

протокола IP - IPv6 и т. д.)

Тип сокета определяет способ передачи данных по сети. Чаще других

применяются:

SOCK_STREAM. Передача потока данных с предварительной

установкой соединения. Обеспечивается надѐжный канал передачи данных,

при котором фрагменты отправленного блока не теряются, не

переупорядочиваются и не дублируются. Поскольку этот тип сокетов

является самым распространѐнным.

SOCK_DGRAM. Передача данных в виде отдельных сообщений

(датаграмм). Предварительная установка соединения не требуется. Обмен

данными происходит быстрее, но является ненадѐжным: сообщения могут

теряться в пути, дублироваться и переупорядочиваться. Допускается

передача сообщения нескольким получателям (multicasting) и

широковещательная передача (broadcasting).

9

SOCK_RAW. Этот тип присваивается низкоуровневым (т. н.

"сырым") сокетам. Их отличие от обычных сокетов состоит в том, что с их

помощью программа может взять на себя формирование некоторых

заголовков, добавляемых к сообщению.

Обратите внимание, что не все домены допускают задание

произвольного типа сокета. Например, совместно с доменом Unix

используется только тип SOCK_STREAM. С другой стороны, для Internet-

домена можно задавать любой из перечисленных типов. В этом случае для

реализации SOCK_STREAM используется протокол TCP, для реализации

SOCK_DGRAM - протокол UDP, а тип SOCK_RAW используется для

низкоуровневой работы с протоколами IP, ICMP и т. д.

Наконец, последний атрибут определяет протокол, используемый для

передачи данных. Как мы только что видели, часто протокол однозначно

определяется по домену и типу сокета. В этом случае в качестве третьего

параметра функции socket можно передать 0, что соответствует протоколу

по умолчанию. Тем не менее, иногда (например, при работе с

низкоуровневыми сокетами) требуется задать протокол явно. Числовые

идентификаторы протоколов зависят от выбранного домена; их можно найти

в документации.

Адреса

Прежде чем передавать данные через сокет, его необходимо связать с

адресом в выбранном домене (эту процедуру называют именованием сокета).

Иногда связывание осуществляется неявно (внутри функций connect

и accept), но выполнять его необходимо во всех случаях.

Вид адреса зависит от выбранного вами домена. В Unix-домене это

текстовая строка - имя файла, через который происходит обмен данными. В

Internet-домене адрес задаѐтся комбинацией IP-адреса и 16-битного номера

порта. IP-адрес определяет хост в сети, а порт - конкретный сокет на этом

хосте.

10

Протоколы TCP и UDP используют различные пространства портов.

Для явного связывания сокета с некоторым адресом используется

функция bind. Еѐ прототип имеет вид:

#include <sys/types.h>

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *addr, int

addrlen);

В качестве первого параметра передаѐтся дескриптор сокета, который

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

указатель на структуру с адресом, а третий - длину этой структуры.

Описание этой структуры представлено ниже

struct sockaddr {

unsigned short sa_family;// Семейство адресов,

AF_xxx

char sa_data[14];// 14 байтов для

хранения адреса

};

Поле sa_family содержит идентификатор домена, тот же, что и

первый параметр функции socket. В зависимости от значения этого поля

по-разному интерпретируется содержимое массива sa_data. Разумеется,

работать с этим массивом напрямую не очень удобно, поэтому вы можете

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

вида sockaddr_XX (XX - суффикс, обозначающий домен: "un" - Unix, "in" -

Internet и т. д.). При передаче в функцию bind указатель на эту структуру

приводится к указателю на sockaddr. Рассмотрим для примера

структуру sockaddr_in.

struct sockaddr_in {

short int sin_family; // Семейство

адресов

unsigned short int sin_port; // Номер

порта

struct in_addr sin_addr; // IP-адрес

11

unsigned char sin_zero[8]; //

"Дополнение" до размера структуры sockaddr

};

Здесь поле sin_family соответствует полю sa_family в

sockaddr, в sin_port записывается номер порта, а в sin_addr - IP-

адрес хоста. Поле sin_addr само является структурой, которая имеет вид:

struct in_addr {

unsigned long s_addr;

};

Зачем понадобилось заключать всего одно поле в структуру? Дело в

том, что раньше in_addr представляла собой объединение (union),

содержащее гораздо большее число полей. Сейчас, когда в ней осталось

всего одно поле, она продолжает использоваться для обратной

совместимости.

И ещѐ одно важное замечание. Существует два порядка хранения

байтов в слове и двойном слове. Один из них называется порядком

хоста (host byte order), другой - сетевым порядком (network byte order)

хранения байтов. При указании IP-адреса и номера порта необходимо

преобразовать число из порядка хоста в сетевой. Для этого используются

функции htons (Host TO Network Short) и htonl (Host TO Network Long).

Обратное преобразование выполняют функции ntohs и ntohl.

2.2 Введение в загружаемые модули ядра ОС Linux

Ядро Linux относится к категории так называемых, монолитных – это

означает, что большая часть функциональности операционной системы

называется ядром и запускается в привилегированном режиме. Можно было

бы подумать, что ядро Linux очень статично, но на самом деле все как раз

наоборот.

Ядро Linux динамически изменяемое – это означает, что вы можете

загружать в ядро дополнительную функциональность, выгружать функции из

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

12

Преимущество загружаемых модулей заключается в возможности сократить

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

Linux – не единственное (и не первое) динамически изменяемое

монолитное ядро. Загружаемые модули поддерживаются в BSD-системах,

Sun Solaris, в ядрах более старых операционных систем, таких как OpenVMS,

а также в других популярных ОС, таких как Microsoft® Windows® и Apple

Mac OS X.

Загружаемые модули ядра расширяют функциональные возможности

ядра без необходимости перезагрузки системы. Например, драйверы

устройств, позволяют ядру взаимодействовать с аппаратурой компьютера.

При отсутствии поддержки модулей, разработчикам пришлось бы писать

монолитные ядра и добавлять новые возможности прямо в ядро. При этом,

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

систему.

Загружаемые модули ядра имеют ряд фундаментальных отличий от

элементов, интегрированных непосредственно в ядро, а также от обычных

программ. Обычная программа содержит главную процедуру main в отличие

от загружаемого модуля, содержащего функции входа и выхода. Функция

входа вызывается, когда модуль загружается в ядро, а функция выхода –

соответственно при выгрузке из ядра. Поскольку функции входа и выхода

являются пользовательскими, для указания назначения этих функций

используются макросы module_init и module_exit. Загружаемый

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

Они определяют тип лицензии, автора и описание модуля, а также другие

параметры.

Сборка модулей ядра

Чтобы модуль был работоспособен, при компиляции необходимо

передать gcc ряд опций. Кроме того, необходимо чтобы модули

компилировались с предварительно определенными символами. Ранние

13

версии ядра полностью полагались, в этом вопросе, на программиста и ему

приходилось явно указывать требуемые определения в Makefile-ах.

Несмотря на иерархическую организацию, в Makefile-ах, на вложенных

уровнях, накапливалось такое огромное количество параметров настройки,

что управление и сопровождение этих настроек стало довольно трудоемким.

К счастью появился kbuild, в результате процесс сборки внешних

загружаемых модулей теперь полностью интегрирован в механизм сборки

ядра. Дополнительные сведения по сборке модулей, которые не являются

частью официального ядра, вы найдете в файле

linux/Documentation/kbuild/modules.txt.

Содержимое makefile для загружаемого модуля ядра под названием

hello, в простейшем случае может иметь вид:

obj-m += hello.o

Для того, чтобы запустить процесс сборки модуля, используем команду

make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD

modules.

На экран должно быть выведено нечто подобное:

[root@pcsenonsrv test_module]# make -C /

/usr/src/linux-`uname -r` SUBDIRS=$PWD modules

make: Entering directory `/usr/src/linux-2.6.x

CC [M] /root/test_module/hello.o

Building modules, stage 2.

MODPOST

CC /root/test_module/hello.mod.o

LD [M] /root/test_module/hello.ko

make: Leaving directory `/usr/src/linux-2.6.x

Подключение и отключение загружаемых модулей ядра

Когда ядро обнаруживает необходимость в тех или иных

функциональных возможностях, еще не загруженных в память, то

демон kmod вызывает утилиту modprobe, передавая ей строку в виде:

14

Название модуля, например softdog или ppp.

Универсальный идентификатор, например char-major-10-30.

Если утилите modprobe передается универсальный идентификатор, то

она сначала пытается отыскать имя соответствующего модуля в

файле /etc/modules.conf, где каждому универсальному

идентификатору поставлено в соответствие имя модуля, например:

alias char-major-10-30 softdog

Это соответствует утверждению: "Данному универсальному

идентификатору соответствует файл модуля softdog.ko".

Затем modprobe отыскивает файл

/lib/modules/version/modules.dep,

чтобы проверить, не нужно ли загружать еще какие-либо модули, от которых

может зависеть заданный модуль. Этот файл создается командой depmod и

описывает зависимости модулей. Например, модуль msdos.ko требует,

чтобы предварительно был загружен модуль fat.ko. Если

модуль Б экспортирует ряд имен (имена функций, переменных и т.п.),

которые используются модулем А, то говорят, что "Модуль А зависит от

модуля Б".

И наконец modprobe вызывает insmod, чтобы сначала загрузить

необходимые, для удовлетворения зависимостей, модули, а затем и

запрошенный модуль. Вызывая insmod,утилита modprobe указывает ей

каталог, /lib/modules/version/ – стандартный путь к модулям ядра.

Утилита insmod ничего не "знает" о размещении модулей ядра, зато это

"знает" утилита modprobe. Таким образом, если вам необходимо загрузить

модуль msdos, то вам необходимо дать следующие команды:

insmod /lib/modules/2.6.0/kernel/fs/fat/fat.ko

insmod /lib/modules/2.6.0/kernel/fs/msdos/msdos.ko

или просто:

modprobe -a msdos

15

В большинстве дистрибутивов Linux, утилиты modprobe, insmod,

depmod входят в состав пакета modutils или mod-utils.

Для загрузки свежесобранного модуля вручную, необходимо

использовать команду

insmod ./hello.ko

Любой загруженный модуль ядра заносится в

список /proc/modules. При необходимости, можно его просмотреть и

убедиться, что загруженный модуль стал частью ядра.

В свою очередь выгружать модули можно с помощью команды

rmmod, например: rmmod hello. Посмотреть, сообщение сгенерированное

модулем (если конечно оно было прописано в исходном тексте модуля)

можно в файле /var/log/messages

Настоятельно рекомендуется всю работу, связанную с

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

выполнять в текстовой консоли. Система XWindow не подходит для

выполнения подобных задач.

3 РЕАЛИЗАЦИЯ СТЕКА E6 ЧЕРЕЗ МЕХАНИЗМ СОКЕТОВ ОС

LINUX

3.1 Инсталляция и деинсталляция загружаемого модуля

Набор исходных файлов для реализации дейтаграммного режима

передачи данных с помощью протокола E6 состоит из трех файлов:

– Исходного файла реализации протокола Е6, написанного на языке С

e6.c

– Файла makefile, который содержит правила для сборки проекта

– Файла kbuild, содержащего информацию, необходимую для сборки

загружаемого модуля ядра

16

Для сборки модуля ядра в Debian, необходима утилита module-

assistant, которая позволяет управлять упаковкой сторонних модулей

ядра. Установить ее можно с помощью команды:

$ sudo apt-get install module-assistant

Для сборки модуля в Fedora, пригодится пакет kernel-devel,

который имеет все необходимые заголовочные файлы ядра и инструменты

для сборки внешних модулей ядра. Установите его с помощью:

$ sudo yum install kernel-devel

Когда все необходимые приготовления выполнены, переходим в

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

загружаемого модуля, выполняем команду make. В результате можно будет

наблюдать вывод подобный приведенному в разделе 2.2.

После этого, в каталоге появится несколько файлов, один из которых

будет называться e6.ko – объектный файл ядра.

Существует возможность просмотреть информацию о модуле от

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

как название сетевого устройства и его MAC-адреса. Сделать это можно с

помощью:

$ modinfo e6.ko

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

команду insmod:

$ insmod ./e6.ko

В результате модуль будет установлен и зарегистрирован в системе,

что обеспечит функционирование дейтаграммного режима стека протоколов

Е6.

Для того чтобы «выгрузить» модуль из ядра, достаточно использовать

команду:

$ rmmod e6

После ее выполнения, все дополнительные возможности,

предоставляемые модулем, будут аннулированы, а сам он удален.

17

3.2 Общая структура модуля

При выполнении команды insmod, выполняется функция

инициализации модуля ядра:

static int __init e6_init(void)

Поскольку функции инициализации и деинициализации модуля,

являются пользовательскими, эта функция активируется с помощью макроса

module_init(e6_init);

В функции инициализации описаны процедуры регистрации семейства

протоколов Е6, поиск Е6-устройств, их инициализация, а также объявление

типа кадра.

Рассмотрим процедуру инициализации стека протоколов Е6 более

детально.

Общая структура модуля представлена на рис. 3.1.

Подключаемые

файлы

Константы и

макросы

Структуры данных

Функции и

процедуры

init Процедура

инициализации

exit

bind

...

send

Регистрация: - семейство адресов

- тип кадра

Аннулирование регистрации

Загружаемый

модуль

Процедура

отключения

Рисунок 3.1 – Общая структура работы загружаемого модуля Е6

18

Для регистрации семейства, вызывается функция:

proto_register(&e6_proto, 0);

в которую передается адрес структуры e6_proto, указывающей

название, владельца и длину сокета семейства протоколов Е6. Структура

e6_proto имеет вид:

static struct proto e6_proto = {

.name = "E6",

.owner = THIS_MODULE,

.obj_size = sizeof(struct e6_sock),

};

Далее происходит инициализация Е6-устройств с помощью вызова

функции e6_init_dev модуля Е6:

e6_dev=e6_init_dev(e6_devname,e6_devaddr,

&e6_myaddr);

При выполнении этой функции, происходит попытка инициализации

сетевого устройства, в случае удачи, установка для него Е6-адреса и

сообщение системе о готовности устройства к дальнейшей работе.

Далее выполняется определение виртуального сетевого интерфейса

(loopback) (интерфейс обратной петли):

lo_dev = dev_get_by_name(&init_net, lo_devname);

Следующая команда

sock_register(&e6_family_ops);

выполняет регистрацию Е6 сокета: определяет семейство протоколов,

адрес функции создающей сокет и владельца. Структура e6_family_ops

имеет вид:

static struct net_proto_family e6_family_ops = {

.family = PF_E6,

.create = e6_create,

.owner = THIS_MODULE,

};

Она содержит в себе название семейства протоколов Е6, функцию для

создания сокета данного типа, и его принадлежность. Функция e6_create

19

static int e6_create(struct net *net, struct socket

*sock, int protocol)

принимая параметры, описывающие все необходимые свойства сети,

она определяет процесс создания сокета Е6.

И наконец строка

dev_add_pack(&e6_packet_type);

выполняет регистрацию нового типа кадров Ethernet: обращаясь к

структуре e6_packet_type, типа packet_type, эта функция добавляет

обработчик кадров указанного типа и определяет тип кадра Е6 в списках

ядра.

В результате выполнения процедуры инициализации загружаемого

модуля, к ядру ОС Linux присоединяется набор структур, констант, макросов

и функций для реализации дейтаграммного режима передачи информации на

основе протокола Е6, совместно с другими семействами протоколов, в том

числе вместе с привычным IP. Детальная схема загрузки и выгрузки модуля

Е6, представлена на рис. 3.2.

Структуры данных

e6_hdr

e6_sock

e6_cb

Функции

e6_create

e6_recv

e6_release

e6_bind

e6_connect

e6_recvmsg

e6_sendmsg

e6_init

инициализация

e6_exit

деинициализация

insmod

загрузка модуля

rmmod

выгрузка модуля

Регистрация семейства протоколов

PF_E6

e6_create

ETH_P_E6

e6_recv

Регистрация типа кадров

Аннулирование регистрации

- семейства протоколов

- типа кадров

Инициализация сетевого устройства

Рисунок 3.2 – Детальная схема процедур загрузки и выгрузки модуля

20

Кадры разных типов передаются в Ethernet, чередуясь, и не влияя друг

на друга, за исключением определенного снижения пропускной способности.

Драйвер принимает кадр из сетевого адаптера, выполняет его первичную

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

обработки на следующий (сетевой) уровень эталонной модели

взаимодействия открытых систем. Адрес обработчика определяется по

списку зарегистрированных в системе типов кадров e6_recv для кадров Е6.

В случае использования в консоли Linux команды rmmod e6 (в

режиме суперпользователя), начнется процесс деинициализации

загружаемого модуля. Рассмотрим этот процесс подробнее. Для выгрузки

модуля из ядра в работу вступит процедура

static void __exit e6_exit(void)

которая в свою очередь будет приведена в действие макросом

module_exit(e6_exit);

Функция

dev_remove_pack(&e6_packet_type);

обеспечит удаление обработчика сетевого протокола и записи о типе

кадра Е6 из списков ядра, которые ранее были объявлены с помощью

команды

dev_add_pack(&e6_packet_type);

Далее функция

sock_unregister(PF_E6);

выполнит удаление семейств протоколов и адресов Е6.

proto_unregister(&e6_proto);

завершит удаление функций и параметров для работы со стеком

протоколов Е6.

3.3 Команды пользователя для работы с функциями модуля

Для ознакомления с интерфейсом программирования приложений на

основе стека протоколов Е6, рассмотрим некоторые функции,

21

представленные в простейших примерах программ клиента и сервера.

Необходимо отметить, что для их корректной работы, необходимо

подключить заголовочный файл e6.h, с помощью директивы include.

#include "e6.h"

После объявления всех необходимых заголовочных файлов,

переменных и структур, приходит черед выполнения функции socket.

socket(PF_E6, SOCK_DGRAM, IPPROTO_UDP)

Эта функция выполняет непосредственное создание сокета и

возвращает его дескриптор, который в дальнейшем может быть использован

другими сетевыми командами. Первый параметр PF_E6, объявляет формат

адреса и пространства имен протокола Е6. Второй параметр SOCK_DGRAM

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

для посылки индивидуально адресованных пакетов в сети. Аргумент

IPPROTO_UDP гласит о том, что для работы создаваемого сокета будет

использоваться протокол пользовательских дейтаграмм UDP.

Далее необходимо определить поля структуры типа sockaddr_e6,

описывающей сокет Е6 (struct sockaddr_e6 server, client;).

client.se6_family = AF_E6; – определение семейства адресов

Е6

client.se6_port = htons (cport); – номер порта Е6

client.se6_addr = E6ADDR_ANY; – адрес для допуска любых

входящих сообщений.

После того как функция socket() успешно выполнена, сокет

существует в пространстве имен (семействе адресов), но не имеет адреса

связанного с ним. Присвоить ему адрес можно с помощью функции

связывания bind()

bind (sock, (struct sockaddr *) &client, size)

Первый ее параметр sock – это дескриптор сокета, который вернула

функция socket().

22

(struct sockaddr *) &client – адрес вышеописанной

структуры типа sockaddr_e6.

size – размер буфера, на который указывает имя, в байтах.

Теперь, когда сокет создан и ассоциирован с адресом, можно

приступить к отправке или получению сообщений. В этом примере,

рассмотрим функции send и recv.

Команда send() служит для передачи сообщений другому сокету.

Она может быть использована только когда сокет находится в состоянии

соединения с другим (подразумевается, что получатель известен).

send ( sock, message1, len, 0 );

Первый параметр – это дескриптор сокета. Второй параметр –

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

Третий содержит в себе длину отправляемого сообщения. И последний –

означает, что других опций не предусмотрено.

Функция recv() получает информацию через сокет с назначенным

дескриптором и сохраняет ее в буфер. Также как и команда send(), она

используется только когда существует соединение с другим сокетом.

recv ( sock, message2, 1500, 0 );

По аналогии с send(), первый параметр – это дескриптор сокета,

второй – указатель на буфер с полученным сообщением, третий –

максимальная длина сообщения в байтах, четвертый означает что функция

работает без использования флагов.

3.4 Взаимодействие загружаемого модуля и статической части ядра

Взаимодействие между командами загружаемого модуля и ядром

обеспечивается посредством библиотеки glibc (GNU libc) а в последствии,

через интерфейс системных вызовов. Когда модуль подключен к ядру и

находится в оперативной памяти, пользователь получает доступ к функциям,

описанным в нем. Во время компиляции пользовательского приложения,

23

библиотека glibc прикомпоновывает все необходимые функции из различных

областей статической части ядра, возвращая блок параметров типа long.

Рассмотрим этот процесс на примере вызова функции связывания

bind().

bind (sock, (struct sockaddr *) &client, size)

На этапе компиляции исходного файла пользовательского приложения,

компилятор, встретив функцию bind(), для связывания сокета с адресом,

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

вызова, соответствующего данной функции и ее параметров в регистры

процессора и последующий вызов прерывания 0х80. В регистры процессора

загружаются следующие значения:

– в EAX загружается номер системного вызова

– в EXB попадает первый параметр функции, в нашем случае, это –

дескриптор сокета

– в EXC загружается второй параметр – адрес структуры типа

sockaddr

– в EDX загружается третий параметр, это – размер буфера

Для выполнения системного вызова в ОС Linux используется функция

system_call(). Эта функция – точка входа для всех системных вызовов.

Ядро реагирует на прерывание 0х80 обращением к функции

system_call(), которая, по сути представляет собой обработчик

прерывания. system_call() помещает копии регистров, содержащих

параметры вызова в стек, при помощи макроса SAVE_ALL и командой call

вызывает нужную системную функцию. Макрос SAVE_ALL используется в

связи с тем, что практически все системные функции ядра написаны на С, и

свои параметры они ищут в стеке, а перемещение параметров в стек

осуществляет этот макрос. Таблица указателей на функции ядра, которые

реализуют системные вызовы, расположена в массиве sys_call_table.

Номер системного вызова, который находится в регистре EAX, является

24

индексом этого массива. Для работы с сокетами существует системный

вызов под названием sys_socketcall, который имеет индекс 102.

Поскольку в EAX находится это значение, следовательно и будет вызвана

соответствующая функция.

Стандартная обработка системных вызовов сокета находится в

функциях, имеющих префикс sys, например sys_bind в файле socket.c,

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

модуля. Определение адреса функции, выполняющей специфическую

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

структуры proto_ops, которая содержит в себе указатели на

соответствующие функции в статической части ядра.

После выполнения системного вызова и последующих операций,

управление вновь передается пользовательскому приложению. Обобщенное

графическое представление вышеописанных процессов изображено на

рис.3.3

#include <socket.h>

main(){

recvfrom(...)

}

recvfrom:: glibc

Процессы

Ядро

Векторы прерываний

Адрес точки входа в ядро

system_call

Блок

параметров

типа long

Оперативная

память

прикладной

процесс

код

пользователя

прерывание

0х80

syscall(SYS_SOCKETCALL)

Ядро

ОС Linux

Рисунок 3.3 – Взаимодействие пользовательского ПО, библиотеки Си

(glibc), ядра ОС Linux и загружаемого модуля

25

3.5 Алгоритмы функций загружаемого модуля

Пришло время рассмотреть алгоритмы функций, реализующих

основные принципы работы протокола Е6. Рассмотрение будет выполняться

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

e6.c.

static void e6_remove_socket(struct hlist_head

*list, struct sock *sk)

– функция для удаления сокета Е6. В ней используются функции для

разблокирования сокета и его взаимоисключения в мультипрограммном

доступе.

static void e6_insert_socket(struct hlist_head

*list, struct sock *sk)

– функция для создания сокета Е6. Использует аналогичные функции

что и e6_remove_socket для блокировки и разблокировки доступа к

сокету и в результате создает его.

static int e6_create(struct net *net, struct socket

*sock, int protocol)

– функция, которая вызывается в socket() и служит для

непосредственного создания сокета текущего семейства протоколов. В

качестве аргументов она получает следующее:

struct net *net – структура для управления опциями сети

struct socket *sock – структура, которая хранит состояние

текущего соединения

int protocol – протокол, который будет использоваться в

создаваемом сокете.

В начале тела функции определяются необходимые переменные и

структуры. Затем происходит проверка, инициализирована ли сеть и в случае

неудачи, возврат кода ошибки EAFNOSUPPORT, которая гласит о том, что

семейство адресов не поддерживается протоколом. После этого происходит

проверка типа передачи данных на соответствие с SOCK_DGRAM, если она

26

провалилась, то возвращается код ошибки ESOCKTNOSUPPORT, она

сообщает о том что такой тип сокета не поддерживается.

Полю state структуры socket присваивается значение

SS_UNCONNECTED, что означает, сокет не присоединен ни к какому другому

сокету.

Заготавливается код ошибки ENOBUFS, в случае если в дальнейшем не

будет достаточно буферного пространства для работы следующих функций.

Вызывается функция

sk_alloc(net, PF_E6, GFP_KERNEL, &e6_proto);

для назначения сокета. В качестве параметров ей передаются:

net – соответствующее пространство имен.

PF_E6 – семейство протоколов Е6.

GFP_KERNEL – приоритет для поиска памяти.

&e6_proto – адрес структуры, определяющей тип сокета.

Если sk_alloc() ничего не возвращает, то подпрограмма завершает

свою работу с кодом ошибки ENOBUFS. Иначе она возвращает структуру

типа sock и выполняются следующие команды.

Функция

e6_sk(sk);

принимая в качестве единственного параметра результат выполнения

sk_alloc(), а именно структуру типа sock и возвращает структуру типа

e6_sock.

Затем полю ops структуры sock присваивается адрес структуры

e6_dgram_ops, которая описывает основные функции работы

дейтаграммного режима передачи данных на базе протокола Е6.

sock_init_data(sock, sk);

Вызывается для полной инициализации сокета.

После этого происходят необходимые корректировки и вызов функции

e6_insert_socket(&e6_sklist, sk);

27

которая выполняет операции открытия доступа к сокету и работа

функции e6_create заканчивается.

static int e6_release(struct socket *sock)

– функция для удаления существующего сокета. Ее параметром

является структура типа socket.

В начале функции, объявляется структура типа sock, которая хранит в

себе информацию о текущем состоянии сокета.

Выполняется функция

e6_remove_socket(&e6_sklist, sk);

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

мультипрограммном доступе.

Далее обнуление некоторых полей структур типа sock и socket.

Затем установка флага гласящего, что сокет не существует и освобождение

интерфейса сокета.

static struct sock *e6_find_port(e6_port_t port)

Функция выполняет поиск порта и возвращает структуру типа sock. В

начале как обычно идет объявление необходимых структур. Затем

вызывается специальный цикл

sk_for_each(sk, node, &e6_sklist)

Его параметры: структура типа sock, структура типа hlist_node и

структура типа hlist_head. Внутри этого цикла происходит поиск порта и

в случае успеха, переход по метке и возврат структуры типа sock.

static e6_port_t e6_bind_port(void)

Предназначена для связывания портов. Не принимает никаких

параметров. В начале, переменная типа e6_port_t получает начальное

значение, объявленное в начале исходного текста в виде константы.

static int e6_bind(struct socket *sock, struct

sockaddr *uaddr, int addr_len)

28

Функция предназначена для связывания сокетов. Параметры:

структуры типов socket, sockaddr и целочисленная переменная –

размер буфера.

В начале тела функции, выполняется описание всех необходимых

структур и переменных. Далее заготавливается код ошибки EINVAL,

который подразумевает, что указан не правильный тип аргумента, в данном

случае длина адреса. Происходит поверка правильности длины адреса, в

случае неудачи переход по метке выхода. В случае, если номер порта в

структуре типа sockaddr_e6 равен 0, то начинается процедура связывания

портов. Заготавливается код ошибки EAGAIN – ресурс временно недоступен

и если работа функции поиска порта заканчивается провалом, то происходит

переход по метке выхода. Если же номер порта в структуре типа

sockaddr_e6 не равен 0, то происходит заготовка кода ошибки

EADDRNOTAVAIL – «невозможно связать указанный адрес», затем проверка

работы функции поиска порта, выход в случае неудачи или определение

адреса в случае успеха. В конце работы подпрограммы происходят

необходимые корректировки опций структур, перезагрузка сокета и выход.

int e6_connect(struct socket *sock, struct sockaddr

* uaddr, int addr_len, int flags)

выполняет процедуру соединения между хостами. В начале

выполняются некоторые переопределения структур. После чего с помощью

условного оператора выполняется проверка длины адреса, переданого в

функцию в качеcтве параметра addr_len на соответствие размеру адреса

типа Е6. В случае если он меньше чем необходимо, возвращается код ошибки

EINVAL, которая сообщает о неподходящем типе аргумента. Затем

выполняется проверка текущего семейства адресов на соответствие

семейству Е6. В случае неудачи возвращается ошибка EAFNOSUPPORT,

которая гласит, что текущее семейство адресов не поддерживается

протоколом. В завершении, функция

29

sk_dst_reset(sk);

выполняет перезагрузку указателя кеша маршрутизации в структуре

типа sock. Далее происходит настройка некоторых опций и установка

сокета в состояние TCP_ESTABLISHED, оно означает, что соединение

успешно установлено.

static int e6_sendmsg(struct kiocb *iocb, struct

socket *sock, struct msghdr *msg, size_t len)

Функция предназначена для отправки сообщений в другой сокет, в

независимости от того, установлено ли соединение. В качестве параметров ей

передаются:

struct kiocb *iocb – структура асинхронного вывода ядра,

описывающая, все необходимые параметры и опции.

struct socket *sock – структура описывает состояние текущего

соединения.

struct msghdr *msg – структура содержащая данные,

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

сокета.

size_t len – переменная описывающая длину сообщения.

В начале функции осуществляется объявление всех необходимых

структур и переменных. Затем проверка и определение адреса Е6, если адрес

отправителя не инициализирован, происходит инициализация переменных

адреса и порта получателя. Иначе происходит проверка длины адреса, в

случае неудачи возвращается ошибка EAGAIN – ресурс временно

недоступен, затем переприсваивание адресов. Происходит проверка

указанных адресов на соответствие их интерфейсу возвратной петли.

Заготовка ошибки ENETDOWN – сеть опущена, проверка состояния

интерфейса, если он не поднят, то выход по метке и возврат ошибки

ENETDOWN. Далее заготовка ошибки EMSGSIZE – сообщение слишком

длинное, за которой следует соответствующая проверка на размер

30

сообщения, в случае неудачи, выход по метке и возврат соответствующей

ошибки. Далее заготовка ошибки ENOBUFS – нет свободного места в буфере,

проверка свободного места в буфере и в случае неудачи выход по метке с

возвратом соответствующей ошибки. Далее копирование и сохранение

информации и необходимых опций, настройка и установка заголовков

пакета, отправка и возврат результата работы функции в длины сообщения.

static struct sock *e6_find_socket(e6_port_t port,

struct e6_addr addr)

Функция выполняет поиск и определение сокета. В качестве

параметров получает номер порта и адрес Е6. Используя функцию

sk_for_each(sk, node, &e6_sklist)

и обобщенный узел списка, она выполняет поиск и в случае удачи,

возвращает структуру типа sock, хранящую в себе состояние текущего

сокета.

static int e6_rcv(struct sk_buff *skb, struct

net_device *dev, struct packet_type *pt, struct

net_device *orig_dev)

Функция участвует в инициализации типа пакетов при подключении

загружаемого модуля к статической части ядра и непосредственной

обработке приходящих пакетов. Принимает в качестве аргументов указатели

на структуры буфера обработки пакетов, сетевого устройства и типа пакета.

После объявления всех необходимых структур, выполняется проверка,

свободен ли буфер для обработки пакета, и если нет, то пакет уничтожается.

Далее происходит проверка адресов на соответствие длине адреса Е6, в

случае неудачи, происходит выход по метке, буфер очищается, а пакет

уничтожается. Далее выполняется поиск сокета с помощью функции

e6_find_socket(hdr->de6p, hdr->de6a);

31

В случае провала – выход по метке и сброс пакета. Если же все

проходит успешно, происходит установление некоторых опций и загрузка

пришедшего пакета в очередь для обработки.

static int e6_recvmsg(struct kiocb *iocb, struct

socket *sock, struct msghdr *msg, size_t len, int

flags)

Функция используется для получения сообщений из сокета и может

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

или нет. По аналогии с e6_sendmsg, она принимает следующие параметры:

struct kiocb *iocb – структура асинхронного вывода ядра,

описывающая, все необходимые параметры и опции.

struct socket *sock – структура описывает состояние текущего

соединения.

struct msghdr *msg – структура содержащая данные,

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

сокета.

size_t len – переменная описывающая длину сообщения.

И дополнительный параметр:

int flags – опции получения сообщений.

В начале – объявление всех необходимых переменных и структур.

Затем попытка получить дейтаграмму и поместить ее в буфер. Далее

происходит проверка получения сообщения, если буфер пуст, то выход по

метке. Далее сравнение длины сообщения с размером актуальных данных в

структуре sk_buff и копирование переменной содержащей длину

сообщения. Далее выполнение некоторых корректировок полей структур,

копирование информации и выход из функции.

static struct net_device *e6_init_dev(const char *

e6devname, const char * e6devaddr, struct e6_addr *

e6myaddr)

32

Функция выполняет инициализацию устройства Е6. В качестве

параметров принимает имя Е6 устройства, адрес Е6 устройства и адрес Е6

В начале выполняются все необходимые объявления переменных и

структур. Затем попытка найти Е6 устройство с помощью команды

e6d = dev_get_by_name(&init_net,e6devname);

В случае неудачи, вывод сообщения, что такое устройство не

существует и выход по метке.

Если адрес устройства Е6 не определен, то начинается его

инициализация. В случае успешного выполнения инициализации адреса,

происходит вывод его 16-ричного представления и установление некоторых

необходимых опций. В случае ошибки, вывод сообщения о том, что

программе не удалось присвоить устройству Е6 адрес. Далее проверка

некоторых дополнительных возможностей и вывод соответствующих

сообщений.

Если устройство не поднято, то выводится соответствующее

сообщение об ошибке, затем происходит попытка запустить устройство и в

случае неудачи выводится сообщение о том, что не удается открыть

устройство. Если же все необходимые операции прошли успешно, то

выводится текущий MAC адрес и длина адреса устройства Е6.

3.6 Анализ функционирования загружаемого модуля на примерах

трассировки основных вызовов

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

готовые приложения клиента и сервера.

Любое сетевое приложение предназначенное для работы на основе

протокола Е6, в данном случае, его реализации в дейтаграммном режиме,

должно включать в себя заголовочный файл e6.h. В нем описаны все

необходимые для работы протокола константы и макросы.

После всех необходимых проверок и приготовлений , для создания

сокета, вызывается функция библиотеки glibc

33

sock = socket(PF_E6, SOCK_DGRAM, IPPROTO_UDP);

которая возвращает дескриптор сокета. В качестве параметров эта

функция принимает семейство протоколов PF_E6, стиль передачи данных

SOCK_DGRAM и протокол, в данном случае IPPROTO_UDP.

Библиотека glibc выполняет предварительную проверку параметров и

формирует блок параметров ввода/вывода, представленный массивом типа

long. Затем происходит вызов функции syscall с указанием номера

системного вызова SYS_SOCKETCALL=102 и номера подфункции

SYS_SOCKET, выполнение которых приводит к генерации программного

прерывания процессора с номером 0х80, предназначенного для реализации

системных вызовов ядра ОС Linux.

Из вектора прерывания 0x80 из оперативной памяти извлекается новое

слово состояния процессора, содержащее адрес обработчика системных

вызовов hsc, и выполняющее переключение режима работы процессора из

пользовательского в системный.

Программа hsc работает как диспетчер (переключатель): она находит в

таблице системных вызовов sys_call_table файла ядра

syscall_table_64.S запись с номером SYS_SOCKETCALL=102,

.long sys_socketcall

содержащую адрес функции sys_socketcall обработчика всех

системных вызовов работы с сокетами.

Программа sys_socketcall является диспетчером подфункций

системного вызова работы с сокетом, который извлекает параметры из блока

параметров и передает управление соответствующей подпрограмме. Для

подфункции SYS_SOCKET вызывается функция sys_socket

err = sys_socket(a0, a1, a[2]);

которая принимает параметры из блока параметров в соответствии с

описанным ранее форматом вызова пользовательской функции socket.

Заголовок программы socket формируется системным макросом

34

SYSCALL_DEFINE3(socket, int, family, int, type,

int, protocol)

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

функции

retval = sock_create(family, type, protocol,

&sock);

в результате ее работы происходит попытка определить семейство

протоколов и владельца сокета и в итоге его создание с помощью функции

sock_release(sock);

внутри которой происходит вызов программы

sock->ops->release(sock);

адрес которой указан в структуре e6_dgram_ops

.release = e6_release,

Что приводит к вызову функции e6_release, описанной в

загружаемом модуле Е6.

Далее управление вновь передается пользовательскому приложению,

где происходит проверка создания сокета, затем заполняется структура типа

sockaddr_e6 client

client.se6_family = AF_E6;

client.se6_port = htons (cport);

client.se6_addr = E6ADDR_ANY;

и происходит вызов функции

bind (sock, (struct sockaddr *) &client, size)

призванной связать дескриптор сокета с локальным именем, то есть с

портом и адресом. В качестве параметров, эта функция принимает

дескриптор сокета, структуру, хранящую адрес сокета, с котором нужно

соединиться и длину адреса. Затем после проверки в glibc, управление вновь

передается ядру ОС Linux и выполняются действия, аналогичные описанным

выше для случая с функцией socket. После того, как управление вернулось

35

пользовательскому приложению, получает свои значения структура типа

sockaddr_e6 server

server.se6_family = AF_E6;

server.se6_port = htons (sport);

server.se6_addr = saddr;

После чего происходит вызов функции

connect( sock, (struct sockaddr *)&server, size )

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

успешного соединения с сервером, начинается цикл обмена сообщениями, в

котором первым делом происходит получение сообщения пользователя

fgets(message1, 1500, stdin);

вычисление его длины плюс символ конца строки

len=strlen(message1)+1;

и вызов функции отправки сообщения

len = send ( sock, message1, len, 0 );

затем вызов

len = recv ( sock, message2, 1500, 0 );

для получения сообщения

Заметим, что после завершения выполнения системного вызова не

всегда следует обратная последовательность действий. Некоторые вызовы

приводят к блокированию текущего процесса, например, для ожидания

поступления сообщения по сети. В это время ядро запускает на выполнение

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

idle в случае отсутствия готовых процессов.

36

ЗАКЛЮЧЕНИЕ

Выполнено подробное описание программной реализации

дейтаграммного режима передачи данных стека протоколов Е6 через

механизм сокетов ОС Linux для версии ядра 2.6.31.5. Рассмотрены основные

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

рядовому специалисту быстро освоиться в специфике работы с ядром Linux

и начать изучение специфики работы протокола Е6. Также, информация

описанная в данной работе может быть использована программистами для

начального изучения интерфейса программирования приложений на базе

стека протоколов Е6.

37

СПИСОК ЛИТЕРАТУРЫ

1. Воробиенко П.П. Всемирная сеть Ethernet? / П.П. Воробиенко, Д.А.

Зайцев, О.Л. Нечипорук // Зв'язок.– 2007, № 5.– C. 14-19.

2. Воробiєнко П.П. Спосіб передачі даних в мережі iз заміщенням

мережного та транспортного рівнів універсальною технологією канального

рівня / П.П. Воробієнко, Д.А. Зайцев, К.Д. Гуляєв / Патент України на

корисну модель № 35773, u2008 03069, Заявл. 11.03.08, Опубл. 10.10.08, Бюл.

№ 19.

3. Guliaiev K.D. Simulating E6 Protocol Networks using CPN Tools / K.D.

Guliaiev, D.A. Zaitsev, D.A. Litvin, E.V. Radchenko // Proc. of Int. Conference on

IT Promotion in Asia. – Tashkent (Uzbekistan), 2008. – P. 203–208.

4. Zaitsev D.A., Bolshakov S.I. E6 Addressing Scheme and Network

Architecture // Journal of Advanced Computer Science and Technology, 1 (1)

(2012) 18-31. (www.sciencepubco.com/index.php/JACST)

5. Звіт про науково-дослідну роботу «Розробка нових систем

адресації глобальних мереж», номер держреєстрації 0108U008900 / Д.А.

Зайцев, Т.Р. Шмельова, К.Д. Гуляєв // Одеса: ОНАЗ, 2009.– 124 с.

6. Zaitsev D.A., Guliaiev K.D. Stack E6 and its Implementation within

Linux Kernel // Journal of Software Engineering and Applications, 2011, 4, 379-

387 (doi:10.4236/jsea.2011.46043).

7. Гуляев К.Д., Зайцев Д.А. Экспериментальная реализация стека

сетевых протоколов Е6 в ядре ОС Linux // Искусственный интеллект, № 2,

2009, с. 105-116

8. Postel J. Transmission control protocol / Postel J. // Information Sciences

Institute: University of Southern California.– 1981, RFC 793.– 85 p.

9. Postel J. User Datagram Protocol / Postel J. // Information Sciences

Institute: University of Southern California.– 1980, RFC 768.– 3 p.

10. IEEE Standard for Information technology—Telecommunications and

information exchange between systems—Local and metropolitan area networks—

38

Specific requirements. Part 3: Carrier Sense Multiple Access with Collision

Detection (CSMA/CD) Access Method and Physical Layer Specifications.–

LAN/MAN Standards Committee of the IEEE Computer Society, Approved 9 June

2005, IEEE-SA Standards Board IP.– 417 p.

11. http://citforum.ru/programming/unix/sockets/

12. http://www.ibm.com/developerworks/ru/library/l-lkm/

13. http://citforum.ru/operating_systems/linux/lkmpg/#whatisakernelmodule

14. http://publib.boulder.ibm.com/infocenter/tpfhelp/current/index.jsp

39

ПРИЛОЖЕНИЕ

40

Листинг 1. Исходный код загружаемого модуля e6.c

/* e6 protocols */

#include <linux/module.h>

#include <linux/kernel.h>

#include <asm/uaccess.h>

#include <linux/skbuff.h>

#include <linux/netdevice.h>

#include <linux/rtnetlink.h>

#include <linux/moduleparam.h>

#include <linux/types.h>

#include <linux/mm.h>

#include <linux/capability.h>

#include <linux/fcntl.h>

#include <linux/socket.h>

#include <linux/in.h>

#include <linux/inet.h>

#include <linux/if_packet.h>

#include <linux/kmod.h>

#include <net/ip.h>

#include <net/protocol.h>

#include <net/sock.h>

#include <linux/errno.h>

#include <linux/timer.h>

#include <asm/system.h>

#include <asm/ioctls.h>

#include <asm/page.h>

#include <asm/cacheflush.h>

#include <asm/io.h>

#include <linux/proc_fs.h>

#include <linux/seq_file.h>

#include <linux/poll.h>

#include <linux/init.h>

#define PF_E6 33

#define AF_E6 PF_E6

#define E6ADDRLEN 6

#define E6PORTSTART 4000

#define E6PORTSTEP 7

#define E6PORTMAX 0xffff

static char *e6_devname = "eth0";

static struct net_device *e6_dev;

static char *lo_devname = "lo";

static struct net_device *lo_dev;

static char *e6_devaddr = NULL;

#define ETH_P_E6 0xe600

41

module_param(e6_devname, charp, 0000);

MODULE_PARM_DESC(e6_devname, "The name of e6 device");

module_param(e6_devaddr, charp, 0000);

MODULE_PARM_DESC(e6_devaddr, "The address of e6

device");

static struct hlist_head e6_sklist;

static DEFINE_RWLOCK(e6_lock);

typedef __u16 e6_port_t;

typedef __u8 e6_addr_t[E6ADDRLEN];

struct e6_addr

{

e6_addr_t s_addr;

};

struct e6_addr e6_myaddr;

static struct e6_addr zero_e6_addr =

{.s_addr={0x00,0x00,0x00,0x00,0x00,0x00}};

static struct e6_addr lo_e6_addr =

{.s_addr={0x00,0x00,0x7f,0x00,0x00,0x01}}; /* e6

0.0.127.0.0.1. */

static e6_port_t e6_port_next = E6PORTSTART;

struct sockaddr_e6

{

sa_family_t se6_family; /* Address family */

e6_port_t se6_port; /* e6 port number. */

struct e6_addr se6_addr; /* e6 address. */

/* Pad to size of `struct sockaddr'. */

unsigned char __pad[sizeof (struct sockaddr) -

sizeof(short int) -

sizeof (e6_port_t) -

sizeof (struct e6_addr)];

};

struct e6_hdr {

struct e6_addr de6a;

struct e6_addr se6a;

__be16 type;

e6_port_t de6p;

e6_port_t se6p;

};

#define E6_TRANSPORT_HEADER_OFFSET (sizeof(struct

e6_hdr)-2*sizeof(e6_port_t))

struct e6_sock {

struct sock sk;

struct e6_addr saddr;

42

e6_port_t sport;

struct e6_addr daddr;

e6_port_t dport;

__u16 cmsg_flags;

};

struct e6_cb

{

struct sockaddr_e6 se6;

};

static inline struct e6_sock *e6_sk(struct sock *sk)

{

return (struct e6_sock *)sk;

}

static struct proto e6_proto = {

.name = "E6",

.owner = THIS_MODULE,

.obj_size = sizeof(struct e6_sock),

};

static void e6_remove_socket(struct hlist_head *list,

struct sock *sk)

{

write_lock_bh(&e6_lock);

sk_del_node_init(sk);

write_unlock_bh(&e6_lock);

}

static void e6_insert_socket(struct hlist_head *list,

struct sock *sk)

{

write_lock_bh(&e6_lock);

sk_add_node(sk, list);

write_unlock_bh(&e6_lock);

}

const struct proto_ops e6_dgram_ops;

static int e6_create(struct net *net, struct socket

*sock, int protocol)

{

struct sock *sk;

struct e6_sock *e6;

int err;

if (net != &init_net)

return -EAFNOSUPPORT;

err = -ESOCKTNOSUPPORT;

if( sock->type != SOCK_DGRAM ) goto out;

sock->state = SS_UNCONNECTED;

43

err = -ENOBUFS;

sk = sk_alloc(net, PF_E6, GFP_KERNEL, &e6_proto);

if (sk == NULL)

goto out;

e6 = e6_sk(sk);

sock->ops = &e6_dgram_ops;

sock_init_data(sock, sk);

sk->sk_no_check = 1;

sk->sk_family = PF_E6;

sk->sk_protocol = protocol;

e6_insert_socket(&e6_sklist, sk);

err=0;

out: return(err);

}

static int e6_release(struct socket *sock)

{

struct sock *sk;

sk = sock->sk;

if (!sk)

goto out;

e6_remove_socket(&e6_sklist, sk);

/*

* Now the socket is dead. No more input will

appear.

*/

sk->sk_state_change(sk); /* It is useless. Just for

sanity. */

sock->sk = NULL;

sk->sk_socket = NULL;

sock_set_flag(sk, SOCK_DEAD);

skb_queue_purge(&sk->sk_receive_queue);

sk_free(sk);

out:

return 0;

}

static struct sock *e6_find_port(e6_port_t port)

{

struct sock *sk;

struct hlist_node *node;

sk_for_each(sk, node, &e6_sklist) {

struct e6_sock *opt = e6_sk(sk);

if ( opt->sport == port )

goto found;

}

44

sk = NULL;

found:

return sk;

}

static e6_port_t e6_bind_port(void)

{

e6_port_t p = e6_port_next;

__u32 np;

np = ((__u32)e6_port_next + E6PORTSTEP) %

((__u32)E6PORTMAX+1);

e6_port_next = (np<E6PORTSTART)? E6PORTSTART + np :

np;

return p;

}

static int e6_bind(struct socket *sock, struct sockaddr

*uaddr, int addr_len)

{

struct sockaddr_e6 *addr = (struct sockaddr_e6

*)uaddr;

struct sock *sk = sock->sk;

struct e6_sock *e6 = e6_sk(sk);

e6_port_t p;

int err;

err = -EINVAL;

if (addr_len < sizeof(struct sockaddr_e6))

goto out;

if( addr->se6_port == 0 )

{

p = e6_bind_port();

/* mutual prime give unique but all the ports

can be exhausted */

err = -EAGAIN;

if (e6_find_port( htons(p) ) != NULL) goto out;

e6->sport = htons(p);

} else

{

err = -EADDRNOTAVAIL;

if( e6_find_port( addr->se6_port ) != NULL )

goto out;

e6->sport = addr->se6_port;

}

e6->saddr = addr->se6_addr;

e6->daddr = zero_e6_addr;

e6->dport = 0;

45

sk_dst_reset(sk);

err = 0;

out:

return err;

}

int e6_connect(struct socket *sock, struct sockaddr *

uaddr, int addr_len, int flags)

{

struct sockaddr_e6 *e6addr = (struct sockaddr_e6

*)uaddr;

struct sock *sk = sock->sk;

struct e6_sock *e6 = e6_sk(sk);

if (addr_len < sizeof(*e6addr))

return -EINVAL;

if (e6addr->se6_family != AF_E6)

return -EAFNOSUPPORT;

sk_dst_reset(sk);

e6->daddr = e6addr->se6_addr;

e6->dport = e6addr->se6_port;

sock->state = TCP_ESTABLISHED;

return(0);

}

static int e6_sendmsg(struct kiocb *iocb, struct socket

*sock, struct msghdr *msg, size_t len)

{

struct sock *sk = sock->sk;

struct sockaddr_e6 *saddr=(struct sockaddr_e6

*)msg->msg_name;

struct sk_buff *skb;

struct net_device *dev = e6_dev;

int err;

struct e6_sock *e6 = e6_sk(sk);

struct e6_hdr *hdr;

struct e6_addr addr, daddr;

e6_port_t port, dport;

addr = e6->saddr;

if(memcmp(&addr,&zero_e6_addr,E6ADDRLEN)==0) addr =

e6_myaddr;

port = e6->sport;

if (saddr == NULL) {

daddr = e6->daddr;

dport = e6->dport;

} else {

if (msg->msg_namelen < sizeof(struct

sockaddr_e6)) {

46

return -EINVAL;

}

daddr = saddr->se6_addr;

dport = saddr->se6_port;

}

if((memcmp(&daddr, &e6_myaddr, E6ADDRLEN) == 0) ||

(memcmp(&daddr, &lo_e6_addr, E6ADDRLEN) == 0) ) /*

loopback */

dev = lo_dev;

err = -ENETDOWN;

if (!(dev->flags & IFF_UP))

goto out_unlock;

err = -EMSGSIZE;

if (len > dev->mtu + sizeof(struct e6_hdr))

goto out_unlock;

err = -ENOBUFS;

skb = sock_alloc_send_skb(sk, len + sizeof(struct

e6_hdr),msg->msg_flags & MSG_DONTWAIT, &err);

if (skb == NULL)

goto out_unlock;

/* Copy data */

skb_reserve(skb, sizeof(struct e6_hdr));

err = memcpy_fromiovec(skb_put(skb,len), msg-

>msg_iov, len);

if (err) goto out_free;

/* Set headers */

skb_push(skb, sizeof(struct e6_hdr));

skb_reset_mac_header(skb);

skb_reset_network_header(skb);

skb_reset_transport_header(skb);

skb->protocol = ETH_P_E6;

skb_set_transport_header(skb,E6_TRANSPORT_HEADER_OF

FSET);

hdr = (struct e6_hdr *)skb_mac_header(skb);

hdr->de6a = daddr;

hdr->se6a = addr;

hdr->type = htons(ETH_P_E6);

hdr->de6p = dport;

hdr->se6p = port;

/* if (dev->hard_header) {

int res;

err = -EINVAL;

res = dev->hard_header(skb, dev,

htons(ETH_P_E6), &addr, NULL, len);

if (res < 0)

47

goto out_free;

}*/

skb->dev = dev;

skb->priority = sk->sk_priority;

/* transmitt */

dev_queue_xmit(skb);

dev_put(dev);

return(len);

out_free:

kfree_skb(skb);

out_unlock:

if (dev)

dev_put(dev);

return err;

}

static struct sock *e6_find_socket(e6_port_t port,

struct e6_addr addr)

{

struct sock *sk;

struct hlist_node *node;

sk_for_each(sk, node, &e6_sklist) {

struct e6_sock *opt = e6_sk(sk);

if ((opt->sport == port || opt->sport == 0) &&

(memcmp(&(opt->saddr),&addr,sizeof(struct

e6_addr)) == 0 || memcmp(&(opt-

>saddr),&zero_e6_addr,sizeof(struct e6_addr)) == 0))

goto found;

}

sk = NULL;

found:

return sk;

}

static int e6_rcv(struct sk_buff *skb, struct

net_device *dev, struct packet_type *pt, struct

net_device *orig_dev)

{

struct e6_hdr *hdr;

struct sock *sk;

struct e6_cb *e6cb;

/* if (skb->pkt_type == PACKET_OTHERHOST)

goto drop;*/

if ((skb = skb_share_check(skb, GFP_ATOMIC)) ==

NULL)

48

return NET_RX_DROP;

/* if (!pskb_may_pull(skb, sizeof(struct e6_hdr)))

goto drop;*/

hdr = (struct e6_hdr *)(skb->data-

E6_TRANSPORT_HEADER_OFFSET);

if((memcmp(hdr->de6a.s_addr, &e6_myaddr, E6ADDRLEN)

!= 0) && (memcmp(hdr->de6a.s_addr, &lo_e6_addr,

E6ADDRLEN) != 0) )

goto drop;

sk = e6_find_socket(hdr->de6p, hdr->de6a);

if (!sk)

goto drop;

e6cb=(struct e6_cb *)skb->cb;

e6cb->se6.se6_family=AF_E6;

e6cb->se6.se6_addr=hdr->se6a;

e6cb->se6.se6_port=hdr->se6p;

if (sock_queue_rcv_skb(sk, skb))

goto drop;

return 0;

drop:

kfree_skb(skb);

return NET_RX_DROP;

}

static int e6_recvmsg(struct kiocb *iocb, struct socket

*sock, struct msghdr *msg, size_t len, int flags)

{

struct sock *sk = sock->sk;

struct sk_buff *skb;

size_t copied;

int err;

msg->msg_namelen = sizeof(struct sockaddr_e6);

skb=skb_recv_datagram(sk,flags,flags&MSG_DONTWAIT,&

err);

if(skb==NULL)

goto out;

copied = skb->len;

if (copied > len)

{

copied=len;

msg->msg_flags|=MSG_TRUNC;

}

/* We can't use skb_copy_datagram here */

err = memcpy_toiovec(msg->msg_iov, skb->data+4,

copied);

if (err)

49

goto out_free;

sk->sk_stamp = skb->tstamp;

if (msg->msg_name)

{

memcpy(msg->msg_name, skb->cb, msg-

>msg_namelen);

}

err = copied;

out_free:

skb_free_datagram(sk, skb);

out:

return err;

}

const struct proto_ops e6_dgram_ops = {

.family = PF_E6,

.owner = THIS_MODULE,

.release = e6_release,

.bind = e6_bind,

.connect = e6_connect,

.socketpair = sock_no_socketpair,

.accept = sock_no_accept,

.getname = sock_no_getname,

.poll = datagram_poll,

.ioctl = sock_no_ioctl,

.listen = sock_no_listen,

.shutdown = sock_no_shutdown,

.setsockopt = sock_common_setsockopt,

.getsockopt = sock_common_getsockopt,

.sendmsg = e6_sendmsg,

.recvmsg = e6_recvmsg,

.mmap = sock_no_mmap,

.sendpage = sock_no_sendpage,

};

static struct net_proto_family e6_family_ops = {

.family = PF_E6,

.create = e6_create,

.owner = THIS_MODULE,

};

static struct packet_type e6_packet_type = {

.type = __constant_htons(ETH_P_E6),

.func = e6_rcv,

};

__u8 e6_xdigit( char c ){__u8 x; if(c>='0'&&c<='9')

x=c-'0'; else if(c>='a'&&c<='f') x=c-'a'+10; else

if(c>='A'&&c<='F') x=c-'A'+10; else x=0; return x;}

50

static struct net_device *e6_init_dev(const char *

e6devname, const char * e6devaddr, struct e6_addr *

e6myaddr)

{

int i, j, ok;

int err=0;

struct net_device *e6d;

__u8 *a = e6myaddr->s_addr;

/* find & set e6_dev */

e6d = dev_get_by_name(&init_net,e6devname);

if (!e6d) {

printk(KERN_ERR "*** e6 (ERR): init: %s doesn't

exist ***\n", e6devname);

goto out;

}

if( e6devaddr != NULL )

{

j=0; ok=1;

for( i=0; i<E6ADDRLEN; i++ )

{

if( e6devaddr[j] == '\0' ) { ok=0; break; }

a[i] = e6_xdigit(e6devaddr[j++]) * 0x10;

if( e6devaddr[j] == '\0' ) { ok=0; break; }

a[i] += e6_xdigit(e6devaddr[j++]);

}

if( ok )

{

printk("<1>*** e6: init: get

e6_devaddr=%02x:%02x:%02x:%02x:%02x:%02x, alen=%d

***\n",

a[0],a[1],a[2],a[3],a[4],a[5],

E6ADDRLEN );

rtnl_lock();

(e6d->netdev_ops)->ndo_stop( e6d );

memcpy(e6d->dev_addr, a, e6d->addr_len );

(e6d->netdev_ops)->ndo_open(e6d);

rtnl_unlock();

if (err)

printk(KERN_ERR "*** e6 (ERR): init:

failed to set e6 addr, err=%d ***\n", err );

}

}

if (!((e6d->netdev_ops)->ndo_poll_controller))

{

51

printk(KERN_ERR "*** e6: init: %s doesn't

support polling ***\n", e6devname );

}

if (!netif_running(e6d)) {

printk(KERN_INFO "*** e6: init: device %s not

up ***\n", e6devname );

rtnl_lock();

err = dev_open(e6d);

rtnl_unlock();

if (err) {

printk(KERN_ERR "*** e6 (ERR): init: %s

failed to open, err=%d ***\n", e6devname, err );

return NULL;

}

}

memcpy( a, e6d->dev_addr, e6d->addr_len );

printk("<1>*** e6: init:

myMAC=%02x:%02x:%02x:%02x:%02x:%02x, alen=%d ***\n",

a[0],a[1],a[2],a[3],a[4],a[5], e6d-

>addr_len );

out:

return e6d;

}

static int __init e6_init(void)

{

int rc = proto_register(&e6_proto, 0);

if (rc != 0) goto out;

printk("<1>*** e6: init: devname=%s ***\n",

e6_devname);

e6_dev = e6_init_dev(e6_devname, e6_devaddr,

&e6_myaddr);

if ( e6_dev == NULL ) { rc=-1; goto out; }

lo_dev = dev_get_by_name(&init_net,lo_devname);

if ( lo_dev == NULL ) { rc=-1; printk(KERN_ERR "***

e6 (ERR): init: %s doesn't exist ***\n", lo_devname);

goto out; }

/* e6_packet_type.dev = e6_dev;*/

sock_register(&e6_family_ops);

dev_add_pack(&e6_packet_type);

out:

return rc;

}

module_init(e6_init);

static void __exit e6_exit(void)

{

52

printk("<1>*** e6: exit: devname=%s ***\n",

e6_devname);

dev_remove_pack(&e6_packet_type);

sock_unregister(PF_E6);

proto_unregister(&e6_proto);

}

module_exit(e6_exit);

MODULE_AUTHOR("Dmitry Zaitsev [email protected] &

Mikhail Kharsun [email protected]");

MODULE_DESCRIPTION("E6 protocols implementation");

MODULE_LICENSE("GPL");

MODULE_ALIAS_NETPROTO(PF_E6);

Примечание: дистрибутив e6.zip, который содержит модуль Е6-

сокет, примеры приложений клиентов и сервера, краткое руководство по

инсталляции размещен на сайте http://daze.ho.ua