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

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

Идея крайне проста: канализировать трафик уже существующего сетевого интерфейса во вновь создаваемый интерфейс с совершенно другими характеристиками (имя, IP, маска, подсеть, …). Один из способов выполнения таких действий в форме модуля ядра Linux мы и обсудим (он не единственный, но другие способы мы обсудим отдельно в другой раз).

О интерфейсах в пару слов


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

Сетевой интерфейс — это то место, где:
  • по каждому принятому из интерфейса пакету создаются экземпляры структуры сокетных буферов (struct sk_buff), далее созданный экземпляр структуры продвигается по стеку протоколов вверх, до его получателя в пространстве пользователя, где он и уничтожается;
  • порождённые где-то на верхних уровнях протоколов пользовательского пространства исходящие экземпляры структуры struct sk_buff должны быть отправлены, а сами экземпляры структуры после этого уничтожаются (или утилизируются в пул).

На протяжении, как минимум, 5-6 последних лет, сетевые интерфейсы неизменно создавались макросом:

#define alloc_netdev( sizeof_priv, name, setup )

Здесь (детали будут понятны из примера модуля):
— sizeof_priv — размер приватной области данных интерфейса (struct net_device), которая будет создана ядром без нашего прямого участия;
— name — символьная строка — шаблон имени интерфейса;
— setup — адрес функции инициализации интерфейса;

В таком, практически неизменном, виде процесс создания интерфейса описан везде в публикациях и упоминается в обсуждениях. Но начиная с ядра 3.17 прототип макроса создания интерфейса меняется (<linux/netdevice.h>):

#define alloc_netdev( sizeof_priv, name, name_assign_type, setup ) 

Как легко видеть, теперь вместо 3-х параметров 4, 3-й из которых — константа, определяющая порядок нумерации создаваемых интерфейсов (исходя из шаблона имени), описанная в том же файле определений:
/* interface name assignment types (sysfs name_assign_type attribute) */
#define NET_NAME_UNKNOWN     0 /* unknown origin (not exposed to userspace) */
#define NET_NAME_ENUM        1 /* enumerated by kernel */
#define NET_NAME_PREDICTABLE 2 /* predictably named by the kernel */
#define NET_NAME_USER        3 /* provided by user-space */
#define NET_NAME_RENAMED     4 /* renamed by user-space */

Это первая тонкость на которую следует обратить внимание. Детальнее мы не будем углубляться в эти детали, важно было только отметить их.

Но созданный так интерфейс ещё не дееспособен, он не выполняет никаких действий. Для того, чтобы «придать жизнь» созданному сетевому интерфейсу, нужно реализовать для него соответствующий набор операций. Вся связь сетевого интерфейса с выполняемыми на нём операциями осуществляется через таблицу операций сетевого интерфейса:
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); 
        ...
        struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);
        ...
}

В ядре 3.09, например, определено 39 операций в struct net_device_ops, и около 50-ти операций в ядре 3.14, но реально разрабатываемые модули реализуют только малую часть из них.

Характерно, что в таблице операций интерфейса присутствует операция передачи сокетного буфера ndo_start_xmit() в физическую среду, но вовсе нет операции приёма пакетов (сокетных буферов). Это совершенно естественно, как мы увидим вскоре: принятые пакеты (например в обработчике аппаратного прерывания IRQ) непосредственно после приёма вызовом netif_rx() (или netif_receive_skb()) тут же помещаются в очередь (ядра) принимаемых пакетов, и далее уже последовательно обрабатываются сетевым стеком. А вот выполнять функцию ndo_start_xmit() — обязательно, хотя бы, как минимум, для вызова API ядра dev_kfree_skb(), который утилизирует (уничтожает) сокетный буфер после успешной (да и безуспешной тоже) операции передачи пакета. Если этого не делать, в системе возникнет слабо выраженная утечка памяти (с каждым пакетом), которая, в конечном итоге, рано или поздно приведёт к краху системы. Это ещё одна тонкость, которую держим в уме.

Последним необходимым нам элементом является структура struct net_device (описана в <linux/netdevice.h>) — описание сетевого интерфейса. Это крупная структура, содержащая не только описание аппаратных средств, но и конфигурационные параметры сетевого интерфейса по отношению к выше лежащим протоколам (пример взят из ядра 3.09):
struct net_device { 
   char  name[ IFNAMSIZ ] ; 
...
   unsigned long  base_addr; /* device I/O address */ 
   unsigned int   irq;       /* device IRQ number  */ 
...
   unsigned       mtu;       /* interface MTU value     */ 
   unsigned short type;      /* interface hardware type */ 
...
   struct net_device_stats stats;
   struct list_head dev_list;
...
   /* Interface address info. */ 
   unsigned char  perm_addr[ MAX_ADDR_LEN ]; /* permanent hw address    */ 
   unsigned char  addr_len;                  /* hardware address length */ 
...
}

Здесь поле type, например, определяет тип аппаратного адаптера с точки зрения ARP-механизма разрешения MAC адресов (<linux/if_arp.h>):
...
#define ARPHRD_ETHER       1    /* Ethernet 10Mbps           */
...
#define ARPHRD_ARCNET      7    /* ARCnet                    */ 
...
#define ARPHRD_IEEE1394   24    /* IEEE 1394 IPv4 - RFC 2734 */
...
#define ARPHRD_IEEE80211 801    /* IEEE 802.11               */

Со структурой сетевого интерфейса обычно создаётся и связывается приватная структура данных (упоминавшаяся ранее), в которой пользователь может размещать произвольные собственные данные любой сложности, ассоциированные с интерфейсом. Это особо актуально, если предполагается, что драйвер может создавать несколько однотипных сетевых интерфейсов. Доступ к приватной структуре данных должен определяться исключительно специально определённой для того функцией netdev_priv(). Ниже показан возможный вид функции — это определение из ядра 3.09, но никто не даст гарантий, что в другом ядре оно радикально не поменяется:
/*     netdev_priv - access network device private data 
 *     Get network device private data 
 */ 
static inline void *netdev_priv( const struct net_device *dev ) { 
        return (char *)dev + ALIGN( sizeof( struct net_device ), NETDEV_ALIGN ); 
} 

Как легко видеть из определения, приватная структура данных дописывается непосредственно в хвост struct net_device — это обычная практика создания структур переменного размера, принятая в языке C начиная с стандарта C89 (и в C99).

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

Модуль виртуального интерфейса


Создаём модуль, который будет перехватывать трафик сетевого ввода-вывода с другого, ранее существующего в системе (физического либо логического), интерфейса, и обеспечивать обработку этих потоков (файл virt.c)…
код модуля
#include <linux/module.h> 
#include <linux/version.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 int open( struct net_device *dev ) { 
   netif_start_queue( dev ); 
   LOG( "%s: device opened", dev->name ); 
   return 0; 
} 

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

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; 
   for( j = 0; j < ETH_ALEN; ++j ) // fill in the MAC address with a phoney 
      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" ); 
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)) 
   child = alloc_netdev( sizeof( struct priv ), ifstr, setup ); 
#else 
   child = alloc_netdev( sizeof( struct priv ), ifstr, NET_NAME_UNKNOWN, setup ); 
#endif 
   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 ); // parent interface  
   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; 
   } 
   /* also, and clone its IP, MAC and other information */ 
   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" ); 


Здесь всё достаточно просто, но некоторых отдельных комментариев заслуживают следующие моменты:
  • После создания интерфейса alloc_netdev() мы связываем его операции через таблицу crypto_net_device_ops. Здесь определены операции (поля): .ndo_open и .ndo_stop (которые вызываются при запуске и остановке интерфейса командой ifconfig up/down), .ndo_get_stats (запрос статистики интерфейса) и .ndo_start_xmit (передача пакета).
  • Через приватную область данных мы сохраняем связь с родительским интерфейсом в нами определённой структуре struct priv (в файлах примеров показано несколько различных вариантов использования приватной области для связывания).
  • В таблице операций нет (да и быть не может по логике) функции приёма сокетных буферов. Но вызовом netdev_rx_handler_register() (который появился только в ядре 2.6.36) мы можем добавить в очередь обработки принимаемых пакетов (для родительского интерфейса) собственную функцию-фильтр handle_frame(), которая будет вызываться для каждого приходящего с этого интерфейса пакета.
  • На время добавления фильтра к очереди, нам необходимо кратковременно заблокировать доступ к очереди (иначе нас может ожидать аварийный результат). Это достигается вызовами rtnl_lock() и rtnl_unlock().
  • При передаче исходящего сокетного буфера в сеть (функция start_xmit()) мы просто подменяем в структуре сокетного буфера интерфейс, через который физически должна производиться отправка.
  • При приёме, наоборот, сокетные буфера, создаваемые в родительском интерфейсе, подменяются на виртуальный.


Как это работает?


Выберем любой существующий и работоспособный сетевой интерфейс (в Fedora 16 один из Ethernet интерфейсов назывался как p7p1 — это хорошая иллюстрация того, что интерфейсы могут иметь очень разнообразные имена):
$ 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
    inet6 fe80::a00:27ff:fe9e:202/64 scope link
    valid_lft forever preferred_lft forever 

Установим на него свой новый виртуальный интерфейс и конфигурируем его на IP подсеть (192.168.50.0/24), отличную от исходной подсети интерфейса 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 
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0 
          TX packets:27 errors:0 dropped:0 overruns:0 carrier:0 
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 b)  TX bytes:5027 (4.9 KiB) 

Самый простой и быстрый способ создать ответный конец коммуникации (нам ведь нужно как-то тестировать свою работу?) для такой новой (192.168.50.2/24) подсети на другом хосте LAN, это создать алиасный IP для сетевого интерфейса этого удалённого хоста, по типу:
$ 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 
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0 
          TX packets:223 errors:0 dropped:0 overruns:0 carrier:0 
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 b)  TX bytes:36730 (35.8 KiB) 
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 
64 bytes from 192.168.50.1: icmp_req=2 ttl=64 time=0.210 ms 
64 bytes from 192.168.50.1: icmp_req=3 ttl=64 time=0.184 ms 
64 bytes from 192.168.50.1: icmp_req=4 ttl=64 time=0.242 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 
$ sudo tcpdump -i virt0 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode 
listening on virt0, link-type EN10MB (Ethernet), capture size 65535 bytes 
00:13:02.228615 IP 192.168.50.1 > 192.168.50.2: ICMP echo request, id 5609, seq 1, length 64 
00:13:02.228716 ARP, Request who-has 192.168.50.1 tell 192.168.50.2, length 28 
00:13:02.228786 ARP, Reply 192.168.50.1 is-at 0a:00:27:00:00:00 (oui Unknown), length 46 
00:13:02.228803 IP 192.168.50.2 > 192.168.50.1: ICMP echo reply, id 5609, seq 1, length 64 
00:13:03.227996 IP 192.168.50.1 > 192.168.50.2: ICMP echo request, id 5609, seq 2, length 64 
00:13:03.228059 IP 192.168.50.2 > 192.168.50.1: ICMP echo reply, id 5609, seq 2, length 64 
00:13:04.228016 IP 192.168.50.1 > 192.168.50.2: ICMP echo request, id 5609, seq 3, length 64 
...
00:14:09.236014 ARP, Request who-has 192.168.50.2 tell 192.168.50.1, length 46 
00:14:09.236052 ARP, Reply 192.168.50.2 is-at 08:00:27:9e:02:02 (oui Unknown), length 28 
tcpdump: pcap_loop: The interface went down 
16 packets captured 
16 packets received by filter 
0 packets dropped by kernel 

И далее создать (теперь уже наоборот, на удалённом хосте) полноценную сессию SSH к новому виртуальному интерфейсу:
$ ssh 192.168.50.2 
Nasty PTR record "192.168.50.2" is set up for 192.168.50.2, ignoring 
olej@192.168.50.2's password: 
Last login: Tue Apr  3 10:21:28 2012 from 192.168.1.5 
$ uname -a 
Linux fedora16vm.localdomain 3.3.0-8.fc16.i686 #1 SMP Thu Mar 29 18:33:55 UTC 2012 i686 i686 i386 GNU/Linux 
$ exit 
logout 
Connection to 192.168.50.2 closed. 
$

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

Что дальше?


Проницательный читатель, да ещё если он внимательно читал предыдущий текст, вправе в этом месте воскликнуть: «Но ведь ваш виртуальный интерфейс не дополняет, а замещает родительский?». Да, в показанном варианте именно так: загрузка такого модуля запрещает трафик по родительскому интерфейсу, но выгрузка модуля опять восстанавливает его.

Этому несчастью легко помочь. Для того чтобы создаваемый виртуальный сетевой интерфейс мог работать независимо в дополнение к родительскому, необходимо:
  • В фильтрах (и приёма и передачи) анализировать поле IP-адреса в структуре сокетного буфере и производить подмену интерфейса только для IP, принадлежащего виртуальному интерфейсу.
  • На приёме разделить обработку сокетных буферов, соответствующим протоколам IP и ARP, потому как структуры данных этих протоколов, естественно, отличаются (поле struct sk_buff*->protocol).

Это выглядит, возможно, сложновато в словесном описании, но в коде модуля всё достаточно просто, и добавляет не более 25 строк кода. И такой вариант приведен в архиве примеров (подкаталог virt-full, здесь этот код не приводится, чтобы не перегружать текст):
$ ping 192.168.50.2 
PING 192.168.50.2 (192.168.50.2) 56(84) bytes of data. 
64 bytes from 192.168.50.2: icmp_req=1 ttl=64 time=0.473 ms 
64 bytes from 192.168.50.2: icmp_req=2 ttl=64 time=0.256 ms 
64 bytes from 192.168.50.2: icmp_req=3 ttl=64 time=0.281 ms 
^C 
--- 192.168.50.2 ping statistics --- 
3 packets transmitted, 3 received, 0% packet loss, time 1999ms 
rtt min/avg/max/mdev = 0.256/0.336/0.473/0.099 ms 
$ ping 192.168.56.101 
PING 192.168.56.101 (192.168.56.101) 56(84) bytes of data. 
64 bytes from 192.168.56.101: icmp_req=1 ttl=64 time=2.63 ms 
64 bytes from 192.168.56.101: icmp_req=2 ttl=64 time=0.306 ms 
64 bytes from 192.168.56.101: icmp_req=3 ttl=64 time=0.225 ms 
^C 
--- 192.168.56.101 ping statistics --- 
3 packets transmitted, 3 received, 0% packet loss, time 2002ms 
rtt min/avg/max/mdev = 0.225/1.053/2.630/1.115 ms 
$ dmesg | tail -n19 
[58382.498200] virt0: no IPv6 routers present 
[58391.368273] device virt0 entered promiscuous mode 
[58409.904046] ! rx: IP4 to IP=192.168.50.2 
[58409.904050] ! rx: injecting frame from p7p1 to virt0 
[58409.904197] ! tx: injecting frame from virt0 to p7p1 
[58409.904212] ! rx: ARP for 192.168.50.2 
[58409.904214] ! rx: injecting frame from p7p1 to virt0 
[58409.904262] ! tx: injecting frame from virt0 to p7p1 
[58410.903427] ! rx: IP4 to IP=192.168.50.2 
[58410.903431] ! rx: injecting frame from p7p1 to virt0 
[58410.903531] ! tx: injecting frame from virt0 to p7p1 
[58411.903447] ! rx: IP4 to IP=192.168.50.2 
[58411.903451] ! rx: injecting frame from p7p1 to virt0 
[58411.903547] ! tx: injecting frame from virt0 to p7p1 
[58414.694485] ! rx: ARP for 192.168.56.101 
[58414.696846] ! rx: IP4 to IP=192.168.56.101 
[58415.696508] ! rx: IP4 to IP=192.168.56.101 
[58416.696572] ! rx: IP4 to IP=192.168.56.101 
[58419.712245] ! rx: ARP for 192.168.56.101 

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

Комментарии (11)


  1. Ivan_83
    02.11.2015 19:33
    +1

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

    Впрочем, в линухе и не только можно не лезть в ядро а заюзать netmap и получить пакеты в юзерспейс приложении, что то там с ними сделать и заслать либо в сеть либо в ядро.
    Ещё есть PF_RING (только линух) и DPDK, тоже чтобы получить пакеты в юзерспейсе.
    Есть ещё диверт сокеты, но они тормозные и пара трюков с нетграфом — тоже не сильно лучше.
    Скорость от того что оно юзерспейс не падает практически, иногда и возрастает за счёт отсутствия кучи ненужных проверок и пакетной обработки (всмысле пачка пакетов разом + зеро копи).
    Кодить проще, дебажить сильно легче — система то в корку не падает в случае чего.


    1. Olej
      02.11.2015 20:41
      +5

      Поставить FreeBSD и заюзать

      Смешные люди…
      Если бы я хотел сказать: «FreeBSD» — я бы написал FreeBSD. А если бы хотел Windows (свят-свят-свят...) — то написал бы Windows.
      Но я написал — Linux.


  1. Kolyuchkin
    03.11.2015 00:05

    Хорошая статья) Спасибо) Я в свое время задачу «дополнительной обработки/изменения» сетевого трафика решил с помощью своего target-модуля iptables. Хотя, слово «решил» тут приведено с преувеличением — скорее «смакетировал», не понравилось мне мое решение (ваше лучше), не люблю писать «срочно» и «результат нужен вчера». Остались нерешенными вопросы корректной и красивой пристыковки «оверхеда» к пакету…
    Я Вас правильно понял, что Вы намерены продолжение статьи писать? Жду))


    1. Olej
      03.11.2015 00:50

      намерены продолжение статьи писать?

      Не то, чтобы продолжение, но то же самое можно сделать другим, альтернативным способом на уровне L3 стека. По свободе опишу…
      То, что нечто подобное можно сделать и с помощью iptables, интуитивно понятно… и ещё и другими средствами. Интересно было бы посмотреть как… в деталях.


  1. milabs
    03.11.2015 16:18
    +1

    Что же так много ошибок в статьях делаете.

    __dev_get_by_name ничего не лочит, её нельзя использовать вот просто так. Как результат — получите кучу проблем. Рядом же есть функция dev_get_by_name, которая и по списку аккуратно ходит и на выходе dev_hold над найденным устройством делает. Пионэры.


    1. Olej
      03.11.2015 19:22

      Что?… опять будем подбрасывать говно не вентилятор?… в ожидании «эффекта»… image

      Что же так много ошибок в статьях делаете.

      Нет… я слышал про дикие племена, у которых система счисления: один, два, много… но чтобы один — это и было «много»?… это уже что-то из области психиатрии…

      __dev_get_by_name ничего не лочит, её нельзя использовать вот просто так. Как результат — получите кучу проблем.

      А что?… прям так совсем и нельзя?
      1. во-первых, там эта функция нужна только для чтения… не обязательно там что-то «лочить»…
      2. во-вторых,… такой вот пыанэр как Jerry Cooperstein, давний участник команды разработчиков ядра… из числа основных, он вот использует… и не подозревает, сердешный, о куче поджидающих его проблем… — в примерах кодов вот к этой своей книжке и использует:
      image

      image


  1. milabs
    03.11.2015 22:56
    +2

    Олег, мне понятна ваша страсть к прекрасному, а также стремление поучать и наставлять. Это либо проходит в определённом возрасте, либо приобретает забавные весьма формы. Ваша стилистика, она «как бэ намекаэ», да.

    Что касается г-на Jerry Cooperstein, то похоже он внёс весь свой вклад в развитие ядра Linux в эпоху доисторического материализма, т.е. когда git'а ещё не было. Я что-то не нашёл ни одного его коммита и упоминания его персоны в исходниках ядра. Подскажите, где посмотреть чем он так знаменит, кроме посредственной и давно утратившей актуальности книги, а также должности Training Program Director в Linux Fondation? Кажется, у вас много общего — оба два любите чему-то поучать :-)

    Далее, относительно вашего опуса:

    во-первых, там эта функция нужна только для чтения… не обязательно там что-то «лочить»…


    Снимаю шляпу, оригинально. Именно потому что что-то там читаем и не нужно лочить. Браво.

    Отсюда вывод напрашивается сам собой — «бумажный» тренер тренирует бумажное стадо. Ну понятно же, что вы в принципе не в теме ядерной разработки. Не практик ни разу. Так и пишите в начале статьи, — «я, автор, не разбираюсь в предмете, но мне интересно и я хочу услышать ваши комментарии по поводу ...».

    Ну и напоследок вам с г-ном Jerry Cooperstein'ом пару вопросов:
    1. Что будет если в момент поиска устройства с использованием __dev_get_by_name кто-то выгрузит драйвер сетевой карты?
    2. Что будет если в ходе работы вашего модуля кто-то выгрузит модуль сетевой карты родительского устройства?
    3. Почему RTNL-lock захватывается только на момент установки/снятия обработчика?

    Подскажу ответ на 3-й вопрос: кто-то словил ассерт от ядра, тогда как в аналогичной ситуации при вызове __dev_get_by_name без лока ядро варнинг не кидает. Отсюда, собственно, выводы. Пионэры :-)


  1. ValdikSS
    05.11.2015 01:34
    +1

    создать алиасный IP для сетевого интерфейса этого удалённого хоста
    vboxnet0:1
    Выкиньте уже вы ifconfig, зачем вы его используете?


    1. Olej
      05.11.2015 02:25

      Выкиньте уже вы ifconfig, зачем вы его используете?


      Изобразив снега, и лёд,
      И Нил, и дуб, и огород,
      И даже — мёд!
      (На случай, если вдруг Медведь
      Придёт картину посмотреть…)

      (с) Сергей Михалков


      1. ValdikSS
        05.11.2015 02:33
        +1

        Except for the patch mass that Debian accumulated, the net-tools package has not seen any upstream development after version 1.60, released sometime about April 15 2001.
        Ifconfig uses the ioctl access method to get the full address
        information, which limits hardware addresses to 8 bytes.
        Because Infiniband address has 20 bytes, only the first 8 bytes
        are displayed correctly.
        Ifconfig is obsolete! For replacement check ip.
        ifconfig makes secondary addresses look like separate interfaces

        People often get the impression that labeled secondary addresses are a separate interface (thanks to the dumb output of ifconfig, as a result of ioctl limitations), which in fact is not the case. You cannot use eth0:1 in iptables nor iproute2, so do not even think of eth0:1 being an interface on its own.

        Similarly, you cannot set flags on these “interfaces”.

        И еще несколько причин, по которым его использовать не стоит. В некоторых дистрибутивах net-tools (ifconfig, route, arp, netstat) уже не поставляется.


        1. Olej
          05.11.2015 02:42

          Вам шашечки или ехать?

          Утилита ifconfig используется во всех UNIX-like OS… и я привык её в них использовать на протяжении многих лет. Можно, я буду использовать то, что мне, любимому, ближе?

          Мы обсуждать станем в комментариях предмет того, о чём пишется в статье… или о том, что вы хорошо знаете утилиту ip из iproute2?