Машиностроение к компьютерные технологии
Сетевое научное издание
http://www.technomagelpub.ru УДК 004.056.53
Метод перехвата исходящих и сетевых пакетов
Рязанова Н.Ю.1, Рязанцев Б.И.1,
1 *
Филиппов М.В.
Ссылка на статью:
// Машиностроение и компьютерные технологии. 2017. № 12. С. 45-56.
Представлена в редакцию: 11.11.2017 © НП «НЭИКОН»
дящих
АЧрр о у .1шк е ^таЛги 1МГТУ им. Н.Э. Баумана, Москва, Россия
Статья посвящена одной из актуальных проблем обеспечения информационной безопасности — контролю и фильтрации сетевого трафика путем перехвата сетевых пакетов. Для перехвата используются структура ядра struct net_device, описывающая сетевое устройство, и структура struct net_device_ops, в которой перечислены возможные на сетевом устройстве операции; а также две функции: ndo_start_xmit — для обработки исходящих пакетов, и rx_handler — для обработки входящих пакетов. Использование структур и функций ядра обеспечивает нужную стабильность, универсальность и возможность адаптации разработанного программного обеспечения к таким запросам пользователей, как анализ содержания передаваемых в пакетах данных, их шифрование и дешифрирование. Предложенный метод может использоваться для создания межсетевых экранов следующего поколения, реализующих технологии глубокого анализа пакетов, а также он может использоваться как дополнение к существующим межсетевым экранам.
Ключевые слова: фильтрация сетевого трафика, перехват сетевых пакетов, глубокий анализ пакетов, межсетевой экран Netfilter/iptables, сетевое устройство, сокетный буфер
Введение
В связи с быстрорастущими объемами информации, передаваемой по компьютерным сетям, остро встает вопрос обеспечения информационной безопасности в локальных сетях, взаимодействующих с глобальными. Одним из аспектов обеспечения информационной безопасности является контроль и фильтрация сетевого трафика. Эта задача выполняется межсетевым экраном, который часто называют брандмауэром или файрволом. Для того чтобы производить фильтрацию, межсетевой экран должен обеспечить перехват проходящих через него сетевых пакетов.
В ядре Linux имеется фреймворк Netfilter, предоставляющий функции для перехвата пакетов в нескольких точках маршрута прохождения пакетов через сетевой стек системы. Однако при построении ядра имеется возможность не включать в сборку данный фрейм-
ворк (параметр сборки CONFIG_NETFILTER). В качестве примера служит дистрибутив Gentoo, распространяемый в исходных кодах. Кроме того, сегодня звучат идеи о создании полностью отечественной операционной системы. Такая система может быть основана на исходных кодах ядра Linux, однако фреймворк Netfilter может быть исключен из базовой поставки. Также в последнее время ярко выражена тенденция переписывания кода ядра Linux. Модификации затрагивают интерфейсы различных подсистем ядра, в том числе и фреймворка Netfilter. Как показывает практика, изменение программных интерфейсов компонентов, от которых зависит разработанное программное обеспечение, в значительной мере усложняет процесс его сопровождения.
В статье рассматривается способ перехвата входящих и исходящих сетевых пакетов, использующий структуры сетевой подсистемы ядра. Данные структуры используются всеми драйверами сетевых устройств, поэтому можно с уверенностью сказать, что они не будут изменены без обеспечения обратной совместимости с существующими драйверами.
1. Сетевое устройство
Конечной точкой при приеме и передаче данных является сетевое устройство. В отличие от символьных и блочных устройств, в ОС Linux сетевые устройства не создают файлы в каталоге /dev. Вместо этого, доступ к сетевым устройствам осуществляется через сетевые интерфейсы, список которых можно вывести с помощью команды ifconfig. Сетевые устройства описываются структурой struct net_device, определенной в файле <linux/netdevice . h>: struct net_device {
char name[IFNAMSIZ]; /* имя сетевого интерфейса */
int irq; /* номер IRQ-линии */
const struct net_device_ops *netdev_ops;/* таблица операций сетевого устройства */
rx_handler_func_t_rcu *rx_handler; /* адрес функции для обработки входящих пакетов */
void_rcu *rx_handler_data; /* указатель на данные, используемые функцией обработки
входящих пакетов */
};
Структура имеет большое количество полей, однако для перехвата исходящих пакетов достаточно использовать поле netdev_ops, а для перехвата входящих пакетов - поле rx_handler.
Поле netdev_ops, используемое для перехвата исходящих пакетов, содержит адрес структуры struct net_device_ops, которая определена в файле
<linux/netdevice.h>. Структура состоит из указателей на функции, реализующих операции, определенные на сетевом устройстве: struct net_device_ops {
int (*ndo_init)(struct net_device *dev); /* инициализировать устройство */
void (*ndo_uninit)(struct net_device *dev); /* деиницализацирозовать устройство */
int (*ndo_open)(struct net_device *dev); /* "поднять" сетевой интерфейс */ int (*ndo_stop)(struct net_device *dev); /* "опустить" сетевой интерфейс */
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb,
struct net_device *dev); /* отправить пакет */
};
Данные функции могут быть реализованы разработчиком драйвера сетевого устройства, если необходимо выполнять соответствующие действия по управлению устройством. Транслятор GCC построен таким образом, что при написании собственного драйвера разработчик может реализовать только необходимые ему операции. Одной из основных операций является передача пакета в сеть. В структуре struct net_device_ops поле ndo_start_xmit содержит адрес функции, выполняющей передачу пакета в сеть. В качестве аргументов функция принимает адрес сокетного буфера и указатель на структуру соответствующего сетевого устройства.
Сокетный буфер является абстракцией операционной системы и предназначен для передачи пакетов между различными уровнями сетевого стека системы. Сокетный буфер описывается в системе структурой struct sk_buff, определенной в файле <linux/ skbuff. h>. struct sk_buff {
struct sock *sk; /* сокет, владеющий данным сокетным буфером */ struct net_device *dev; /* ассоциированное с пакетом сетевое устройство */
u16 transport_header; /* заголовок транспортного уровня */ u16 network_header; /* заголовок сетевого уровня */ u16 mac_header; /* заголовок канального уровня */
unsigned char *head, *data; /*указатель на блок заголовков пакета , указатель на блок
данных пакета */
};
Сокетный буфер содержит управляющие данные протоколов различных уровней сетевой модели и указатель на передаваемые данные. Поле data содержит адрес массива передаваемых байтов, и анализ именно этих данных позволяет создать межсетевой экран следующего поколения, реализующий технологии глубокого анализа пакетов.
Функция передачи пакета в сеть ndo_start_xmit возвращает значение типа netdev_tx_t, которое является псевдонимом для перечисления netdev_tx, определенного в файле <linux/netdevice.h>: enum netdev_tx {
__NETDEV_TX_MIN = INT_MIN, NETDEV_TX_OK = 0x00, NETDEV_TX_BUSY = 0x01,
};
typedef enum netdev_tx netdev_tx_t;
Это перечисление содержит значение _NETDEV_TX_MIN для того, чтобы в процессе компиляции значения перечисления гарантированно представлялись при помощи целых чисел со знаком. В случае нормального завершения функция передачи пакета в сеть должна вернуть значение NETDEV_TX_OK. Значение NETDEV_TX_BUSY означает, что сетевое устройство по каким-либо причинам не может выполнить передачу пакета.
2. Перехват исходящих пакетов
Предлагаемый способ перехвата исходящих пакетов заключается в перехвате вызова функции ядра ndo_start_xmit, выполняющей передачу пакета в сеть и входящую в состав драйвера сетевого устройства. Для этого требуется подменить исходную функцию передачи пакета в сеть на специально разработанную, в задачи которой и входит анализ передаваемых в пакете данных:
netdev_tx_t ndo_start_xmit(struct sk_buff *skb, struct net_device *dev) {
netdev_tx_t ret = NETDEV_TX_OK; struct filter_out *filter;
read_lock(&filters_out_list_rwlock); if ((filter = find_filter_out(dev)) != NULL) { if (analyze_packet(&filter->funcs_list, skb))
dev_kfree_skb(skb); else
ret = filter->ndo_orig->ndo_start_xmit(skb, dev);
}
read_unlock(&filters_out_list_rwlock); return ret;
}
Данная функция передачи пакета в сеть использует глобальный список filters_out_list. Список обеспечевает возможность перехвата исходящих пакетов на нескольких сетевых устройствах. Элементами списка filters_out_list являются экземпляры объявленной структуры struct filter_out. struct filter_out {
struct net_device *dev; /* сетевое устройство, соответствующее этой структуре */ struct list_head funcs_list; /* список функций обратного вызова, выполняющих анализ
исходящего пакета */ const struct net_device_ops *ndo_orig; /* адрес исходной таблицы операций */ struct net_device_ops ndo_new; /* новая таблица операций */ struct list_head entry; /* поле используется для организации списка */
};
LIST_HEAD(filters_out_list); DEFINE_RWLOCK(filters_out_list_rwlock);
В структуру struct filter_out включены поля для хранения адреса исходной таблицы операций и для хранения новой таблицы операций сетевого устройства. Это необходимо, так как поле netdev_ops в структуре struct net_device является указателем на статическую структуру. Список filters_out_list является разделяемым ресурсом и может использоваться только в режиме монопольного доступа. Для этого используется классическая блокировка чтения-записи.
Функция ndo_start_xmit() получает на вход адрес сокетного буфера и адрес структуры сетевого устройства. По адресу структуры сетевого устройства dev, функция получает доступ к структуре struct filter_out, соответствующей данному сетевому устройству. Полученный адрес передается функции find_filter_out () :
struct filter_out *find_filter_out(struct net_device *dev) {
struct filter_out *curr_filter;
list_for_each_entry(curr_filter, &filters_out_list, entry) { if (curr_filter->dev == dev) return curr_filter;
}
return NULL;
}
Если в списке filter_out_list найдена требуемая структура struct filter_out, то новая функция передачи пакета в сеть получает доступ к ее полю funcs_list, в котором хранится адрес начала списка функций обратного вызова, выполняющих анализ пакета.
Для организации списка функций обратного вызова разработана структура struct filter func:
/* определение типа функции обратного вызова */ typedef bool analyze_fn(const struct sk_buff *skb);
struct filter_func {
analyze_fn *callback_func; /* адрес функции обратного вызова */ struct list_head entry; /* поле исопльзуется для организации списка */
};
Адрес поля funcs_list структуры struct filter_out передается в качестве первого аргумента функции analyze_packet () . В качестве второго аргумента функция analyze_packet () принимает указатель на сокетный буфер, полученный функцией передачи пакета в сеть. Функция analyze_packet() последовательно вызывает функции обратного вызова, анализирующие исходящий пакет. Функция обратного вызова принимает на вход адрес сокетного буфера и возвращает признак наличия в пакете нежелательной для передачи информации.
bool analyze_packet(struct list_head *funcs_list, struct sk_buff *skb) {
struct filter_func *curr_filter_func; list_for_each_entry(curr_filter_func, funcs_list, entry) { if ((*(curr_filter_func->callback_func))(skb)) { return true;
}
}
return false;
}
Если в пакете отсутствует нежелательная для передачи информация, новая функция передачи пакета в сеть вызывает исходную функцию передачи пакета в сеть и передает ей адрес сокетного буфера. Адрес исходной функции передачи пакета в сеть хранится в системной таблице операций сетевого устройства, на которую указывает поле ndo_orig в структуре struct filter_out. В противном случае функция уничтожает пакет, передавая его адрес встроенной в ядро функции dev_kfree_skb () и возвращает значение NETDEV_TX_OK. Уничтожать непереданный исходной функции пакет необходимо во избежание утечки памяти.
3.Перехват входящих пакетов
Кроме операции отправления пакета, сетевое устройство также выполняет прием пакетов, но в таблице операций сетевого устройства отсутствует функция, выполняющая прием пакетов. Это естественно, так как запрос на передачу пакета в сеть является синхронным событием, а поступление пакета из сети является асинхронным событием, обрабатываемым по прерыванию от сетевого устройства. Обработчик прерывания формирует
сокетный буфер и ставит его в очередь ядра принимаемых пакетов, передавая его адрес встроенной в ядро функции netif_receive_skb (), которая должна вызываться в контексте отложенного прерывания. Начиная с версии ядра 2.6.36 в структуре struct net_device сетевого устройства появилось поле rx_handler, содержащее адрес функции для обработки входящих пакетов. Если указатель rx_handler не является нулевым, то управление передается этой функции через цепочку вложенных вызовов из функции netif_receive_skb() . Поле rx_handler_data может использоваться функцией для организации своей работы. Функция обработки входящих пакетов получает в качестве параметра сокетный буфер и возвращает значение типа rx_handler_result_t:
typedef rx_handler_result_t rx_handler_func_t(struct sk_buff **pskb);
Тип rx_handler_result_t является псевдонимом для перечисления rx_handler_result, определенного в файле <linux/netdevice.h>: enum rx_handler_result {
RX_HANDLER_CONSUMED, RX_HANDLER_ANOTHER, RX_HANDLER_EXACT, RX_HANDLER_PASS,
};
typedef enum rx_handler_result rx_handler_result_t;
Функция, обрабатывающая входящие пакеты, должна вернуть значение RX_HANDLER_CONSUMED, если требуется предотвратить обработку пакета сетевой подсистемой. При этом функция также должна уничтожить пакет, передав его адрес встроенной в ядро функции dev_skb_free () . В случае, если функция не нашла пакет нежелательным для передачи, она должна вернуть значение RX_HANDLER_PASS. Это означает, что пакет будет обработан сетевой подсистемой и доставлен получателю - процессу пространства пользователя. Если функция переназначит пакет другому сетевому устройству (с помощью модификации поля dev в структуре struct sk_buff), то она должна вернуть значение RX_HANDLER_ANOTHER. В случае, если пакет должен быть сразу доставлен обработчику протокола на данном сетевом устройстве, то функция должна вернуть значение RX_HANDLER_EXACT.
Для перехвата входящих пакетов нужно записать в поле rx_handler адрес специально разработанной функции, в задачи которой входит анализ пакетов:
rx_handler_result_t rx_handler(struct sk_buff **pskb) {
rx_handler_result_t ret = RX_HANDLER_PASS; struct filter_in *filter;
struct sk_buff *skb = *pskb;
read_lock(&filters_in_list_rwlock);
if ((filter = find_filter_in(skb->dev)) != NULL) { if (analyze_packet(&filter->funcs_list, skb))
dev_kfree_skb(skb); else if (filter->rx_handler_orig)
ret = (*(filter->rx_handler_orig))(pskb);
}
read_unlock(&filters_in_list_rwlock); return ret;
}
Функция rx_handler() использует глобальный список filters_in_list. ^исок обеспечивает возможность перехвата входящих пакетов на нескольких сетевых устройствах. Элементами списка filters_in_list являются экземпляры разработанной структуры struct filter_in. Список filters_in_list является разделяемым ресурсом, поэтому для синхронизации потоков, выполняющих чтение и модификацию списка, используется классическая блокировка чтения-записи: struct filter_in {
struct net_device *dev; /* сетевое устройство, соответствующее этой структуре */
struct list_head funcs_list; /* список функций обратного вызова, выполняющих анализ
входящего пакета */
rx_handler_func_t *rx_handler_orig; /* адрес исходной функции обработки входящих
пакетов */
struct list_head entry; /* поле используется для организации списка */
};
LIST_HEAD(filters_in_list); DEFINE_RWLOCK(filters_in_list_rwlock);
Функция rx_handler () получает на вход указатель на адрес сокетного буфера. Функция читает поле dev сокетного буфера skb, чтобы получить адрес структуры сетевого интерфейса, ассоциированного с данным сокетным буфером. По полученному адресу структуры сетевого устройства, функция получает доступ к структуре struct filter_in, соответствующей данному сетевому устройству. Для этого функция подает полученный адрес на вход разработанной функции find_filter_in () :
struct filter_in *find_filter_in(struct net_device *dev) {
struct filter_in *curr_filter;
list_for_each_entry(curr_filter, &filters_in_list, entry) {
if (curr_filter->dev == dev) return curr_filter;
}
return NULL;
}
Если в списке filter_in_list найдена требуемая структура struct filter_in, то функция rx_handler() получает доступ к ее полю funcs_list, в котором хранится адрес головы списка функций обратного вызова, выполняющих анализ пакета. Адрес поля funcs_list структуры struct filter_in передается в качестве первого аргумента ранее описанной функции analyze_packet() .
Если в пакете отсутствует нежелательная для передачи информация, функция rx_handler() вызывает исходную функцию обработки входящих пакетов, если она была определена. Адрес исходной функции обработки хранится в поле rx_handler_orig в структуре struct filter_in. В противном случае функция уничтожает пакет, передавая его адрес встроенной в ядро функции dev_kfree_skb () и возвращает значение RX_HANDLER_PASS. Уничтожать непереданный исходной функции пакет необходимо во избежание утечки памяти.
4.Загружаемый модуль ядра
Для встраивания описанных функций в ядро используется загружаемый модуль ядра, причем его макрос инициализации не выполняет никаких действий. Все функции загружаемого модуля ядра являются точками входа. Функции отправки исходящих и обработки входящих сетевых пакетов вызываются сетевой подсистемой. Кроме того, в состав модуля включены четыре функции для адаптации модуля под нужды пользователя: две функции для установки и удаления фильтров исходящих пакетов и две функции для установки и удаления фильтров входящих пакетов.
Заключение
В статье предложен универсальный способ анализа данных входящих и исходящих сетевых пакетов, использующий структуры ядра. Использование функций ядра и обеспечивает нужную универсальность и возможность адаптации предложенных функций к потребностям пользователя. Преимуществом данного способа является меньшая зависимость от изменений в коде ядра Linux. Рассмотренный способ может использоваться для
создания межсетевых экранов, в том числе и реализующих технологии глубокого анализа пакетов.
Список литературы
1. Чемодуров А.С., Карпутина А.Ю. Защита интернет-шлюза и фильтрация сетевого трафика корпоративной сети // Научно-методический электронный журнал «Концепт». 2015. № 1. С. 96-100.
2. Орлов С. Межсетевые экраны: расширение функционала // Журнал сетевых решений LAN. 2013. № 6. C. 44-49.
3. Lowth Ch. The hidden treasures of iptables. Режим доступа: http://www.linuxjournal.com/article/7180 (дата обращения 31.10.17).
4. Sourceware tracking system. Error Description 16476. Режим доступа: https://sourceware.org/bugzilla/show bug.cgi?id=16476 (дата обращения 31.10.17).
rd
5. Corbet J., Rubini A., Kroah-Hartman G. Linux devices drivers. 3 ed. Sebastopol: O'Reilly Media Inc., 2005. 615 p.
6. Linux kernel source tree. Режим доступа: https://github.com/torvalds/linux (дата обращения 31.10.17).
7. International standard ISO/IEC 9899:TC3. Programming languages C. Режим доступа: http://www. open-std. org/jtc1/ sc22/wg 14/www/ docs/n1256.pdf (дата обращения 31.10.17).
8. Benvenuti C. Understanding Linux network internals. Sebastopol: O'Reilly Media Inc., 2006. 1035 p.
9. Синицын В. Межсетевой экран Linux. Взгляд изнутри // Системный администратор. 2012. № 12(121). С. 20-27.
10. netif_receive_skb - process receive buffer from network. Driver support. Режим доступа: https://www.kernel.org/doc/htmldocs/networking/API-netif-receive-skb.html (дата обращения 31.10.17).
11. Rosen R. Linux Kernel networking: Implementation and theory. N.Y.: Apress, 2014. 612 с.
Mechanical Engineering & Computer Science
Electronic journal
http://www.technomagelpub.ru
Mechanical Engineering and Computer Science, 2017, no. 12, pp. 45-56.
Received: 11.11.2017
© NP "NEICON"
The Outgoing and Incoming Network Packets Intercepting Method
N.Yu. Ryazanova1, B.I. Ryazantsev1, ':fi]ippovjnike@maiiju
M.V. Filippov1 *
:Bauman Moscow State Technical University, Moscow, Russia
Keywords: network traffic filtering, network packets intercepting, deep packet inspection,
Netfilter/iptables firewall, network device, socket buffer
In connexion with the rapidly growing computer network information capacities, information security of local networks connected with global networks becomes a critical challenge. One of the information security aspects is to control and filter the network traffic by intercepting the incoming and outgoing network packets. This is accomplished owing to firewalls. The Linux kernel 2.4.x included the Netfilter firewall and the iptables utility, which allow us to analyse only the packets headers and their pertaining to specific network connections. In addition, the practice of rewriting the Linux kernel codes complicates the maintenance of the software targeting for this firewall.
The article proposes a network packet intercepting method based directly on the structures and functions of the kernel, so it has no restrictions associated with the inherent Netfilter/iptables functionality. To provide intercepting, are used the struct net_device structure of the kernel that describes a network device and the struct netdeviceops structure that lists operations possible on the network device and two functions: ndo_start_xmit and rx_handler used to process outgoing and incoming packets, respectively. These functions are rewritten in order to include new functionality into the kernel to meet the users' requests. The use of the structures and functions of the kernel provides desirable stability, versatility, and adaptive capability of the developed software for users' requests such as content analysis of data transmitted in packets, their encryption and decryption. The proposed method can be used to create firewalls of the next-generation to implement technology of deep packet inspection, as well as a complement to the available firewalls.
References
1. Chemodurov A.S., Karputina A.Yu. Internet gateway protection and filtering network traffic corporate network. Nauchno-metodicheskij elektronnyj zhurnal «Kontsept» [Scientific and methodological electronic journal «Concept»], 2015, no. 1, pp. 96-100. (in Russian)
2. Orlov S. Firewalls: extending of functionality. Zhurnal setevykh reshenij LAN [J. of network solutions LAN], 2013, no. 6, pp. 44-49 (in Russian).
3. Lowth Ch. The hidden treasures of iptables. Available at: http://www.linuxjournal.com/article/7180, accessed 31.10. 2017.
4. Sourceware tracking system. Error Description 16476. Available at: https://sourceware.org/bugzilla/show_bug.cgi?id=16476, accessed 31.10. 2017.
rd
5. Corbet J., Rubini A., Kroah-Hartman G. Linux devices drivers. 3 ed. Sebastopol: O'Reilly Media Inc., 2005. 615 p.
6. Linux kernel source tree. Available at: https://github.com/torvalds/linux, accessed 31.10. 2017.
7. International Standard ISO/IEC 9899: TC3. Programming languages C. Available at: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf , accessed 31.10.2017.
8. Benvenuti C. Understanding Linux network internals. Sebastopol: O'Reilly Media Inc., 2006. 1035 p.
9. Sinitsyn V. Linux firewall. View from inside. Sistemnyj administrator [System Administrator], 2012, no. 12(121), pp. 20-27 (in Russian).
10. netif_receive_skb - process receive buffer from network. Driver support. Available at: https://www.kernel.org/doc/htmldocs/networking/API-netif-receive-skb.html, accessed 31.10.2017.
11. Rosen R. Linux Kernel networking: Implementation and theory. N.Y.: Apress, 2014. 612 p.