Содержание


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

Часть 33. Виртуальный сетевой интерфейс

Comments

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

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

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

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

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

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

Виртуальный сетевой интерфейс

Исходный код данного примера можно найти в файле virt.c в архиве network.tgz в разделе «Материалы для скачивания», поэтому часть кода будет опущена для облегчения восприятия.

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

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

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

static struct net_device *child = NULL; 

struct priv { 
   struct net_device_stats stats; 
   struct net_device *parent; 
}; 

static rx_handler_result_t handle_frame( struct sk_buff **pskb ) { 
   struct sk_buff *skb = *pskb; 
   if( child ) { 
      struct priv *priv = netdev_priv( child ); 
      priv->stats.rx_packets++; 
      priv->stats.rx_bytes += skb->len; 
      LOG( "rx: injecting frame from %s to %s", skb->dev->name, child->name ); 
      skb->dev = child; 
      return RX_HANDLER_ANOTHER; 
   } 
   return RX_HANDLER_PASS; 
} 

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

static netdev_tx_t start_xmit( struct sk_buff *skb, struct net_device *dev ) { 
   struct priv *priv = netdev_priv( dev ); 
   priv->stats.tx_packets++; 
   priv->stats.tx_bytes += skb->len; 
   if( priv->parent ) { 
      skb->dev = priv->parent; 
      skb->priority = 1; 
      dev_queue_xmit( skb ); 
      LOG( "tx: injecting frame from %s to %s", dev->name, skb->dev->name ); 
      return 0; 
   } 
   return NETDEV_TX_OK; 
} 

static struct net_device_stats *get_stats( struct net_device *dev ) { 
   return &( (struct priv*)netdev_priv( dev ) )->stats; 
} 

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

static void setup( struct net_device *dev ) { 
   int j; 
   ether_setup( dev ); 
   memset( netdev_priv(dev), 0, sizeof( struct priv ) ); 
   dev->netdev_ops = &crypto_net_device_ops; 
   //установить значение MAC-адреса
   for( j = 0; j < ETH_ALEN; ++j )
      dev->dev_addr[ j ] = (char)j; 
} 

int __init init( void ) { 
   int err = 0; 
   struct priv *priv; 
   char ifstr[ 40 ]; 
   sprintf( ifstr, "%s%s", ifname, "%d" ); 
   child = alloc_netdev( sizeof( struct priv ), ifstr, setup ); 
   if( child == NULL ) { 
      ERR( "%s: allocate error", THIS_MODULE->name ); return -ENOMEM; 
   } 
   priv = netdev_priv( child ); 
   //получение информации о родительском «аппаратном» интерфейсе
   priv->parent = __dev_get_by_name( &init_net, link );
   if( !priv->parent ) { 
      ERR( "%s: no such net: %s", THIS_MODULE->name, link ); 
      err = -ENODEV; goto err; 
   } 
   if( priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK ) { 
      ERR( "%s: illegal net type", THIS_MODULE->name ); 
      err = -EINVAL; goto err; 
   } 
   //скопируем параметры родительского интерфейса в его виртуального потомка
   memcpy( child->dev_addr, priv->parent->dev_addr, ETH_ALEN ); 
   memcpy( child->broadcast, priv->parent->broadcast, ETH_ALEN ); 
   if( ( err = dev_alloc_name( child, child->name ) ) ) { 
      ERR( "%s: allocate name, error %i", THIS_MODULE->name, err ); 
      err = -EIO; goto err; 
   } 
   register_netdev( child ); 
   rtnl_lock(); 
   netdev_rx_handler_register( priv->parent, &handle_frame, NULL ); 
   rtnl_unlock(); 
   LOG( "module %s loaded", THIS_MODULE->name ); 
   LOG( "%s: create link %s", THIS_MODULE->name, child->name ); 
   LOG( "%s: registered rx handler for %s", THIS_MODULE->name, priv->parent->name ); 
   return 0; 
err: 
   free_netdev( child ); 
   return err; 
} 

void __exit exit( void ) { 
   struct priv *priv = netdev_priv( child ); 
   if( priv->parent ) { 
      rtnl_lock(); 
      netdev_rx_handler_unregister( priv->parent ); 
      rtnl_unlock(); 
      LOG( "unregister rx handler for %s\n", priv->parent->name ); 
   } 
   unregister_netdev( child ); 
   free_netdev( child ); 
   LOG( "module %s unloaded", THIS_MODULE->name ); 
} 

module_init( init ); 
module_exit( exit ); 

MODULE_AUTHOR( "Oleg Tsiliuric" ); 
MODULE_AUTHOR( "Nikita Dorokhin" ); 
MODULE_LICENSE( "GPL v2" ); 
MODULE_VERSION( "2.1" );

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

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

$ ip addr show dev p7p1
3: p7p1: 
<BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000

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

	    inet 192.168.56.101/24 brd 192.168.56.255 scope global p7p1
 ...

После этого можно установить созданный виртуальный интерфейс и настроить его на подсеть, отличную от p7p1:

$ sudo insmod virt2.ko link=p7p1 
$ sudo ifconfig virt0 192.168.50.2 
$ ifconfig virt0 
virt0     Link encap:Ethernet  HWaddr 08:00:27:9E:02:02 
          inet addr:192.168.50.2  Bcast:192.168.50.255  Mask:255.255.255.0 
          inet6 addr: fe80::a00:27ff:fe9e:202/64 Scope:Link 
...

Проще всего создать "противоположный" конец такой подсети на другом LAN-хосте, указав новый IP как alias для сетевого интерфейса этого хоста, как показано ниже.

$ sudo ifconfig vboxnet0:1 192.168.50.1
$ ifconfig 
... 
vboxnet0  Link encap:Ethernet  HWaddr 0A:00:27:00:00:00  
          inet addr:192.168.56.1  Bcast:192.168.56.255  Mask:255.255.255.0 
          inet6 addr: fe80::800:27ff:fe00:0/64 Scope:Link 
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 
...
vboxnet0:1 Link encap:Ethernet  HWaddr 0A:00:27:00:00:00  
          inet addr:192.168.50.1  Bcast:192.168.50.255  Mask:255.255.255.0 
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

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

$ ping 192.168.50.1 
PING 192.168.50.1 (192.168.50.1) 56(84) bytes of data. 
64 bytes from 192.168.50.1: icmp_req=1 ttl=64 time=0.371 ms 
...
^C 
--- 192.168.50.1 ping statistics --- 
4 packets transmitted, 4 received, 0% packet loss, time 3001ms 
rtt min/avg/max/mdev = 0.184/0.251/0.371/0.074 ms

Примечание: В листинге 1 был представлен упрощённый вариант модуля, который полностью перехватывает трафик родительского интерфейса, замещая его. Но в разделе «Материалы для скачивания» в архиве network.tgz можно найти файл virt-full.с, в котором IP-адрес получателя обрабатывается корректно.

Анализ поведения интерфейса

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

struct net_device_stats { 
   unsigned long  rx_packets;     /* общее число полученных пакетов    */ 
   unsigned long  tx_packets;     /* общее число переданных пакетов */ 
...
}

Поля этой структуры должны заполняться в коде модуля статистическими данными проходящих пакетов (например, при передаче пакета должно инкрементироваться значение tx_packets).

В пространстве пользователя эту структуру можно получить с помощью функции ndo_get_stats в таблице операций struct net_device_ops (эти поля были показаны в листинге 1). Модуль должен реализовать аналогичную функцию и поместить её в struct net_device_ops. Это необходимо, чтобы получить статистику сетевого интерфейса при вызове ifconfig или через интерфейс файловой системы /proc, как это реализовано для всех других сетевых интерфейсов:

$ ifconfig wlan0 
wlan0     Link encap:Ethernet  HWaddr 00:13:02:69:70:9B  
          inet addr:192.168.1.22  Bcast:192.168.1.255  Mask:255.255.255.0 
          inet6 addr: fe80::213:2ff:fe69:709b/64 Scope:Link 
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 
          TX packets:9070 errors:0 dropped:0 overruns:0 carrier:0 
...

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

  1. если модуль обслуживает единственный сетевой интерфейс, то структура может размещаться на глобальном уровне кода модуля.
    static struct net_device_stats *stats;
    ...
    static struct net_device_stats *my_get_stats( struct net_device *dev ) { 
       return &stats; 
    } 
    ...
    static struct net_device_ops ndo = { 
       ...
       .ndo_get_stats = my_get_stats, 
    };
  2. часто эта структура размещается как составная часть структуры приватных данных (которая рассматривалась в предыдущей статье), которую разработчик связывает с сетевым интерфейсом.
    static struct net_device *my_dev = NULL;
    struct my_private { 
       struct net_device_stats stats; 
       ...
    }; 
    ...
    static struct net_device_stats *my_get_stats( struct net_device *dev ) { 
       struct my_private *priv = (my_private*)netdev_priv( dev );   
       return &priv->stats; 
    } 
    ...
    static struct net_device_ops ndo = { 
       ...
       .ndo_get_stats = my_get_stats, 
    }
    ...
    void my_setup( struct net_device *dev ) { 
       memset( netdev_priv( dev ), 0, sizeof( struct my_private ) ); 
       dev->netdev_ops = &ndo; 
    } 
    int __init my_init( void ) { 
        my_dev = alloc_netdev( sizeof( struct my_private ), "my_if%d", my_setup ); 
    }
  3. можно использовать структуру, реализованную непосредственно в определении интерфейса - внутри структуры struct net_device.
    static struct net_device_stats *my_get_stats( struct net_device *dev ) { 
       return &dev->stats; 
    }

Все три варианта использования можно найти в файлах virt.c, virt1.c и virt2.c в архиве network.tgz.

Заключение

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

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


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


Похожие темы


Комментарии

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

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