Разработка модулей ядра Linux

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

Comments

Серия контента:

Этот контент является частью # из серии # статей: Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

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

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

В архиве представлены два варианта интерфейса: упрощённый модуль virtl.ko, замещающий родительский сетевой интерфейс, и полный вариант virt.ko, анализирующий сетевые фреймы протоколов ARP и IP4 и «перехватывающий» только трафик, относящийся к «своему» интерфейсу. Разница состоит в том, что при загрузке упрощённого модуля работа родительского интерфейса прекращается, а в случае с полной версией оба интерфейса работают независимо. Но так как код полного модуля гораздо объёмнее, а общие принципы одинаковы для обоих модулей, то ниже рассматривается только упрощённый вариант. Полный вариант интерфейса также упоминается, но так как его код и протокол испытаний приведены в архиве, то его анализ не должен вызвать сложностей.

Листинг 1. Виртуальный сетевой интерфейс.
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
#include <linux/moduleparam.h>
#include <net/arp.h>
#include <linux/ip.h>

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

static char* link = "eth0";
module_param( link, charp, 0 );

static char* ifname = "virt";
module_param( ifname, charp, 0 );

static int debug = 0;
module_param( debug, int, 0 );

static struct net_device *child = NULL;
static struct net_device_stats stats;  // таблица статистики интерфейса
static u32 child_ip;

struct priv {
   struct net_device *parent;
};

static char* strIP( u32 addr ) {      // вывод IP-адреса в точечной нотации
   static char saddr[ MAX_ADDR_LEN ];
   sprintf( saddr, "%d.%d.%d.%d",
            ( addr ) & 0xFF, ( addr >> 8 ) & 0xFF,
            ( addr >> 16 ) & 0xFF, ( addr >> 24 ) & 0xFF
          );
   return saddr;
}
static int open( struct net_device *dev ) {
   struct in_device *in_dev = dev->ip_ptr;
   struct in_ifaddr *ifa = in_dev->ifa_list;
   LOG( "%s: device opened", dev->name );
   child_ip = ifa->ifa_address;
   netif_start_queue( dev );
   if( debug != 0 ) {
      char sdebg[ 40 ] = "";
      sprintf( sdebg, "%s:", strIP( ifa->ifa_address ) );
      strcat( sdebg, strIP( ifa->ifa_mask ) );
      DBG( "%s: %s", dev->name, sdebg );
   }
   return 0;
}

static int stop( struct net_device *dev ) {
   LOG( "%s: device closed", dev->name );
   netif_stop_queue( dev );
   return 0;
}

static struct net_device_stats *get_stats( struct net_device *dev ) {
   return &stats;
}

// передача фрейма
static netdev_tx_t start_xmit( struct sk_buff *skb, struct net_device *dev ) {
   struct priv *priv = netdev_priv( dev );
   stats.tx_packets++;
   stats.tx_bytes += skb->len;
   skb->dev = priv->parent;   // передача в родительский (физический) интерфейс
   skb->priority = 1;
   dev_queue_xmit( skb );
   DBG( "tx: injecting frame from %s to %s with length: %u",
        dev->name, skb->dev->name, skb->len );
   return 0;
}

static struct net_device_ops net_device_ops = {
   .ndo_open = open,
   .ndo_stop = stop,
   .ndo_get_stats = get_stats,
   .ndo_start_xmit = start_xmit,
};

// приём фрейма
int pack_parent( struct sk_buff *skb, struct net_device *dev,
                 struct packet_type *pt, struct net_device *odev ) {
   skb->dev = child;          // передача фрейма в виртуальный интерфейс
   stats.rx_packets++;
   stats.rx_bytes += skb->len;
   DBG( "tx: injecting frame from %s to %s with length: %u",
        dev->name, skb->dev->name, skb->len );
   return skb->len;
};

static struct packet_type proto_parent = {
   __constant_htons( ETH_P_ALL ), // перехватывать все пакеты: ETH_P_ARP & ETH_P_IP
   NULL,
   pack_parent,
   (void*)1,
   NULL
};

int __init init( void ) {
   void setup( struct net_device *dev ) { // вложенная функция (GCC)
      int j;
      ether_setup( dev );
      memset( netdev_priv( dev ), 0, sizeof( struct priv ) );
      dev->netdev_ops = &net_device_ops;
      for( j = 0; j < ETH_ALEN; ++j )     // установить фиктивный MAC-адрес
         dev->dev_addr[ j ] = (char)j;
   }
   int err = 0;
   struct priv *priv;
   char ifstr[ 40 ];
   sprintf( ifstr, "%s%s", ifname, "%d" );
   child = alloc_netdev( sizeof( struct priv ), ifstr, setup );
   if( child == NULL ) {
      ERR( "%s: allocate error", THIS_MODULE->name ); return -ENOMEM;
   }
   priv = netdev_priv( child );
   priv->parent = __dev_get_by_name( &init_net, link ); // родительский интерфейс
   if( !priv->parent ) {
      ERR( "%s: no such net: %s", THIS_MODULE->name, link );
      err = -ENODEV; goto err;
   }
   if( priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK ) {
      ERR( "%s: illegal net type", THIS_MODULE->name );
      err = -EINVAL; goto err;
   }
   memcpy( child->dev_addr, priv->parent->dev_addr, ETH_ALEN );
   memcpy( child->broadcast, priv->parent->broadcast, ETH_ALEN );
   if( ( err = dev_alloc_name( child, child->name ) ) ) {
      ERR( "%s: allocate name, error %i", THIS_MODULE->name, err );
      err = -EIO; goto err;
   }
   register_netdev( child );         // зарегистрировать новый интерфейс
   proto_parent.dev = priv->parent;
   dev_add_pack( &proto_parent );    // установить обработчик фреймов для родителя
   LOG( "module %s loaded", THIS_MODULE->name );
   LOG( "%s: create link %s", THIS_MODULE->name, child->name );
   return 0;
err:
   free_netdev( child );
   return err;
}

void __exit exit( void ) {
   dev_remove_pack( &proto_parent ); // удалить обработчик фреймов
   unregister_netdev( child );
   free_netdev( child );
   LOG( "module %s unloaded", THIS_MODULE->name );
   LOG( "=============================================" );
}

module_init( init );
module_exit( exit );

MODULE_AUTHOR( "Oleg Tsiliuric" );
MODULE_LICENSE( "GPL v2" );
MODULE_VERSION( "3.6" );

От рассмотренных ранее примеров код отличается тем, что после регистрации нового сетевого интерфейса virt0 он выполняет вызов dev_add_pack(), предварительно установив в структуре packet_type поле dev на указатель родительского интерфейса: входящий трафик с этого интерфейса и будет перехватываться определённой в структуре функцией pack_parent(). Эта функция фиксирует статистику интерфейса и, самое главное, подменяет в сокетном буфере указатель родительского интерфейса на виртуальный. Обратная подмена (виртуального на физический) происходит в функции отправки фрейма start_xmit().

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

$ ip address
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:52:b9:e0 brd ff:ff:ff:ff:ff:ff
...
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:0f:13:6d brd ff:ff:ff:ff:ff:ff
...
$ sudo insmod ./virt.ko link=eth1 debug=1
$ sudo ifconfig virt0 192.168.50.19
$ sudo ifconfig virt0
virt0     Link encap:Ethernet  HWaddr 08:00:27:0f:13:6d
          inet addr:192.168.50.19  Bcast:192.168.50.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe0f:136d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
...

Как видно, наш интерфейс пока не получил никакой информации, так как значение параметра RX packets (число принятых байт) равно нулю.

Создадим на другом компьютере алиасный IP для тестируемой подсети (192.168.50.0/24) и организуем отправку трафика на созданный интерфейс.

$ sudo ifconfig vboxnet0:1 192.168.50.1
$ ping 192.168.50.19
PING 192.168.50.19 (192.168.50.19) 56(84) bytes of data.
64 bytes from 192.168.50.19: icmp_req=1 ttl=64 time=0.627 ms
...
^C
--- 192.168.50.19 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.305/0.419/0.627/0.148 ms

На этом же компьютере в отдельном терминале можно наблюдать за трафиком (в отдельном терминале), фиксируемым утилитой tcpdump.

$ sudo tcpdump -i vboxnet0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vboxnet0, link-type EN10MB (Ethernet), capture size 65535 bytes
...
18:41:01.740607 ARP, Request who-has 192.168.50.19 tell 192.168.50.1, length 28
18:41:01.741104 ARP, Reply 192.168.50.19 is-at 08:00:27:0f:13:6d (oui Unknown), length 28
18:41:01.741116 IP 192.168.50.1 > 192.168.50.19: ICMP echo request, 
id 8402, seq 1, length 64
...
18:41:03.741471 IP 192.168.50.19 > 192.168.50.1: ICMP echo reply, 
id 8402, seq 3, length 64
18:41:06.747701 ARP, Request who-has 192.168.50.1 tell 192.168.50.19, length 28
...

Теперь вернёмся к вопросу, как создать полноценный виртуальный интерфейс, работающий только со своим трафиком и не нарушающий работу родительского интерфейса.

Для этого необходимо объявить два независимых обработчика протоколов и зарегистрировать их в функции инициализации модуля, как показано в листинге 2.

Листинг 2. Создание полноценного виртуального интерфейса.
// обработчик фреймов ETH_P_ARP
int arp_pack_rcv( struct sk_buff *skb, struct net_device *dev,
                  struct packet_type *pt, struct net_device *odev ) {
   ...
   return skb->len;
};

static struct packet_type arp_proto = {
   __constant_htons( ETH_P_ARP ),
   NULL,
   arp_pack_rcv,  // фильтр пртокола ETH_P_ARP
   (void*)1,
   NULL
};

// обработчик фреймов ETH_P_IP
int ip4_pack_rcv( struct sk_buff *skb, struct net_device *dev,
                  struct packet_type *pt, struct net_device *odev ) {
   ...
   return skb->len;
};

static struct packet_type ip4_proto = {
   __constant_htons( ETH_P_IP ),
   NULL,
   ip4_pack_rcv,    // фильтр пртокола ETH_P_IP
   (void*)1,
   NULL
};

// регистрация обработчиков
// перехват только с родительского интерфейса
   arp_proto.dev = ip4_proto.dev = priv->parent;
   dev_add_pack( &arp_proto );
   dev_add_pack( &ip4_proto );

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

Используя такой полноценный модуль, можно открыть с хостом две параллельные SSH-сессии на разные интерфейсы (использующие разные IP), которые будут одновременно использовать единый общий физический интерфейс:

$ ssh olej@192.168.50.17
olej@192.168.50.17's password:
Last login: Mon Jul 16 15:52:16 2012 from 192.168.1.9
...
$ ssh olej@192.168.56.101
olej@192.168.56.101's password:
Last login: Mon Jul 16 17:29:57 2012 from 192.168.50.1
...
$ who
olej     tty1         2012-07-16 09:29 (:0)
olej     pts/0        2012-07-16 09:33 (:0.0)
olej     pts/1        2012-07-16 12:22 (192.168.1.9)
olej     pts/4        2012-07-16 15:52 (192.168.1.9)
olej     pts/6        2012-07-16 17:29 (192.168.50.1)
olej     pts/7        2012-07-16 17:31 (192.168.56.1)

Команда who выполняется уже в SSH-сессии на самом удалённом хосте, к которому были осуществлены два независимых подключения из двух различных подсетей (последние две строки вывода), на самом деле представляющий один и тот же хост.

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

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

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     *               255.255.255.0   U     2      0        0 wlan0
default         192.168.1.1     0.0.0.0         UG    0      0        0 wlan0

До тех пор, пока в этой таблице не появится строка (строки), определяющие поведение разрабатываемого интерфейса, никакого положительного результата в поведении модуля добиться невозможно. Чтобы эта строка появилась в таблице, её необходимо добавить туда с помощью команды route. В дальнейшем подобные действия могут выполняться синхронно с инсталляцией модуля при установке пакета. Но на этапе разработки гибкое управление роутингом становится залогом успеха. Эта особенность существенно отличает процесс разработки сетевых модулей ядра от процесса создания драйверов потоковых устройств в /dev.

И, конечно же, при отладке сетевых модулей ядра незаменимым инструментом становятся утилиты для анализа трафика, например, tcpdump.

Заключение

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

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=845563
ArticleTitle=Разработка модулей ядра Linux: Часть 35. Дополнительные аспекты использования модулей ядра для создания сетевых интерфейсов
publish-date=11132012