Разработка модулей ядра 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