Салимóненко Дмитрий Александрович

Вычислительные Сети, Системы и Телекоммуникации

ЗАДАНИЕ 3: Анализатор сетевого трафика в LINUX

Введение

В ЗАДАНИИ 2 Вы уже познакомились с азами работы с сетью в Linux, используя низкоуровневые системные вызовы. В частности, рассмотрели основные структуры, в которых ОС записывает информацию о сетевом взаимодействии (протоколы, порты, интерфейсы и т.п.) и функции.

Теперь настала пора двигаться вперед: напишем с Вами низкоуровневый анализатор сетевого трафика.

Для чего может быть полезна эта программа? Например, запустив ее, Вы можете вживую видеть, по каким протоколам, на какие IP-адреса отправляются данные (пакеты) в сеть; а также, с каких адресов приходят пакеты на Ваш компьютер. Актуальность всего этого объяснять, я думаю, не надо. Но, все же – лишь несколько  примеров применения.

Скажем, приобрели Вы (ну, или где-то там взяли, например, на каком-нибудь торренте) некую программу. А вдруг она отсылает некую информацию с Вашего компьютера куда-то?... Как это узнать – куда и отсылает ли? Вот в этом как раз и поможет анализатор сетевого трафика.

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

Так вот, анализируя трафик, можно узнать, с каких адресов приходит ненужные пакеты. А дальше – все просто: ставим запрет доступа с этих адресов и хакеры, как говорится, отдыхают. Хотя, конечно, не все так просто – но я здесь не намерен вдаваться в тонкости того, как осложнить жизнь простому пользователю компьютера (телефона), а также того, как с этим бороться.

Еще пример – хитрые провайдеры. Которые эпизодически держат пользователей в своей локальной сети, не пуская их в интернет. Пока пользователь помучается – пообращается в техническую поддержку, пока ему в стопервый раз посоветуют сообщить результаты работы какой-нибудь утилиты (например, ping), пока представитель службы поддержки «выяснит», используете ли Вы антивирус или нет, и т.д. - время-то и пройдет. А потом – сеть возникнет снова. Так вот – контролировать эти дела (без затрат времени на бесполезное общение со «службой поддержки») вполне можно при помощи анализатора.

Примечание 1. Вообще-то, существует масса готовых программ – утилит, написанных уже кем-то до Вас. Часть из них мы рассматривали в ЗАДАНИИ 1. Такие утилиты есть, пожалуй, для всех операционных систем, в том числе и для Windows, и для Linux. Есть и программы с более расширенным функционалом, которые, к примеру, позволяют фильтровать трафик, отбирая для просмотра сообщения именно такого вида, которые Вас интересуют в наибольшей степени.

Так вот, в рамках данного задания Вы и потренируетесь самостоятельно создать подобную утилиту. Конечно, пока – простую, без изысков. НО: предлагаемый ниже код содержит практически ВСЕ необходимые системные вызовы, требующиеся для анализа сетевого трафика. И стоит добавить к нему визуальный интерфейс, фильтр сообщений, более грамотную обработку ошибок и т.д. – как перед Вами появится готовый программный продукт – сетевой анализатор. Или – прослушивающий сниффер, как называют программы подобного рода.

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

Запустив программу, Вы можете видеть в консоли информацию о сетевом обмене. После запуска программы – откройте (или обновите) в любом браузере какую-нибудь вебстраницу… и посмотрите, что программа выдаст в консоль.

Примечание 3. Так как программа написана на языке С, ее можно легко портировать под Windows. Эта ОС также содержит интерфейс сокетов, поэтому переделки будут незначительны. При этом программа станет двухплатформенной. Если хотите, можете заняться этим – попутно получите навыки создания низкоуровневых кроссплатформенных приложений без использования java и т.д.


I. Теория

↑ К содержанию ↑

Забегая вперед (см. Практику), видим, что, по сравнению с заданием 2, здесь добавились дополнительные структуры. Также, что самое главное – и  это новое – здесь используются так называемые пакетные сокеты.

Вообще, есть много различных способов получить доступ к сетевому интерфейсу в Linux. Например, путем использования специализированных библиотек типа libpcap. Эту библиотеку используют в своей работе утилиты tcpdump и snort. Однако, мы с Вами поступим низкоуровневым способом и откажемся от использования библиотек подобного рода.

При создании сокета стандартным вызовом socket (int domain, int type, int protocol) параметр domain определяет коммуникационный домен, в котором будет использоваться сокет. Обычно используются значения PF_UNIX для соединений, ограниченных локальной машиной, и PF_INET, для соединений, базирующихся на протоколе IPv4. Аргумент type определяет тип создаваемого сокета и имеет несколько значений.

Примечание. Вы использовали AF_INET вместо PF_INET. Надо знать, что эти константы тождественны друг другу. Однако, в одних функциях применяется PF_INET, в других - AF_INET. В каких конкретно? Не будем вдаваться в эти тонкости, отметив, что даже в ядре Linux есть указанные несоответствия, что же говорить о Вас, студентах, только-только взявшихся за изучение работы технологии сети интернет. Для каждого конкретного случая внимательно читайте документацию. Вместе с тем, правильное указание той или иной константы облегчает портирование программ на другие ОС. Имейте это в виду.

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

  • PF_UNIX (работа сокета ограничивается локальным компьютером, на котором она запущена, без выхода в сеть; т.е. здесь сеть вообще не используется),
  • PF_INET (программа, использующая такой сокет, может выходить в сеть с использованием протоколов ТСР или UDP; выход в сеть осуществляется на транспортном уровне модели OSI),
  • PF_PACKET (сокет используется для отправления и приема пакетов на уровне драйверов устройств; выход в сеть осуществляется на канальном уровне модели OSI).

Обратим внимание на последний абзац: пакетный сокет. Это, с дозволения сказать, «самый низкоуровневый» сокет. Ниже – даже уже не ядро, а, пожалуй, уровень аппаратных абстракций. Т.е., по сути, наша с Вами программа будет работать с сетевым драйвером ОС Linux. Еще ниже – это уже микропрограммное обеспечение сетевой карты, там отчасти необходим Ассемблер. Там, как Вы уже догадались, работают протоколы самого низкого – физического уровня.

Примечание. Работа непосредственно с сетевым драйвером, практически без посредников, хороша по двум причинам:

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

Параметр type определяет вид протокола, при помощи которого будет работать сетевой обмен с использованием созданного сокета.

Вы уже знаете, что существуют сокеты, трех видов: потоковые, дейтаграммные и сырые сокеты (последние позволяют задавать низкоуровневые параметры сетевого взаимодействия по усмотрению программиста). Соответственно, тип сокета (параметр type) может принимать следующие значения:

  • SOCK_STREAM (протокол TCP),
  • SOCK_DGRAM (протокол UDP),
  • SOCK_RAW (протокол IP и ниже).

Таким образом, для того, чтобы создать прослушивающий (на канальном уровне, НЕ на транспортном!) сокет, необходимо будет использовать команду вида

sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

Параметр ETH_P_ALL означает, что прослушиванию подлежат ВСЕ интерфейсы. Можно вместо него применить другой параметр, создав соответствующий фильтр, тогда интерфейсы могут прослушиваться выборочно.

Примечание 1. Вообще-то, вместо SOCK_RAW можно использовать и SOCK_ DGRAM. Отличие между ними в том, что пакеты типа SOCK_RAW передаются драйверу устройства и принимаются от него безо всяких изменений данных пакета. Ибо изменения осуществляет, при необходимости, программа, которая использует такой сокет. Тогда как SOCK_DGRAM работает на более высоком уровне: физический заголовок (MAC-адрес) удаляется перед тем, как пакет отправляется на обработку пользователю. Тем самым, для параллельного (наряду с самой системой) прослушивания идеально подходит именно тип сокета SOCK_RAW.

Примечание 2. Как видим, в качестве протокола, который будет использоваться пакетный сокет, фигурирует даже не UDP (не говоря уже о ТСР), а IP и даже протоколы еще более низкого, канального уровня.

Примечание 3. Имейте в виду, что такое понятие, как порт, используемый сокетом, появляется на транспортном уровне, т.е. на уровне таких протоколов, как ТСР и UDP. Тогда как для пакетных сокетов порт НЕ используется. Подумайте и объясните, почему так. Подсказка: потому, что он там попросту не нужен…

Так что же (как говаривал Сократ): приступим к работе!



II. Практика

↑ К содержанию ↑

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

Примечание. Программу необходимо запускать, естественно, с правами администратора. Ибо она обращается непосредственно к сетевому драйверу. Конечно, что ж мы хотели от использования пакетных сокетов и соответствующего им канального уровня (второго по счету) модели OSI…

Я немного исправил код, придав ему работоспособность. И вот что получилось:
#include <net/if.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
//#include <sys/socket.h>
#include <linux/socket.h>
#include <linux/ioctl.h>
//#include <linux/if.h>
#include <linux/types.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
// #include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <sys/ioctl.h>
#include <stdlib.h>
  #include <inttypes.h>
  #include <netdb.h>
#include <arpa/inet.h>
/*
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <net/if.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/types.h>
*/
#ifndef SIOCGIFCONF
  #include <sys/sockio.h>
#endif
#define PROMISC_MODE_ON 1 // флаг включения неразборчивый режим
#define PROMISC_MODE_OFF 0 // флаг выключения неразборчивого режима
struct ifparam {
   __u32 ip;  // IP адрес
   __u32 mask;  // маска подсети
   int mtu;  // размер MTU
   int index;  // индекс интерфейса
} ifp;
struct ifreq ifr; // см. <linux/if.h>
struct in_addr addr;
int getifconf(__u8 *intf, struct ifparam *ifp, int mode)
{
   int fd;
   struct sockaddr_in s;
   memset((void *)&ifr, 0, sizeof(struct ifreq));
// Кстати, а для чего в следующей строчке создается сокет? Ведь никаких данных ни передается, ни принимается.
   if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)  return (-1);
   sprintf(ifr.ifr_name," %s", intf);
/*
* Проверяем флаг режима. Если он установлен в 0, неразборчивый режим
* необходимо отключить, поэтому сразу выполняется переход на метку setmode
*/
   if(!mode) goto setmode;
/*
* Определяем IP адрес сетевого интерфейса
*/
   if(ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
  perror("ioctl SIOCGIFADDR");
  return -1;
   }
   memset((void *)&s, 0, sizeof(struct sockaddr_in));
   memcpy((void *)&s, (void *)&ifr.ifr_addr, sizeof(struct sockaddr));
   memcpy((void *)&ifp->ip, (void *)&s.sin_addr.s_addr, sizeof(__u32));
// Подумайте, возможно ли вместо функций memcpy применить обычное присванивание.
/*
* Определяем маску подсети
*/
   if(ioctl(fd, SIOCGIFNETMASK, &ifr) < 0) {
  perror("ioctl SIOCGIFNETMASK");
  return -1;
   }
   memset((void *)&s, 0, sizeof(struct sockaddr_in));
   memcpy((void *)&s, (void *)&ifr.ifr_netmask, sizeof(struct sockaddr));
   memcpy((void *)&ifp->mask, (void *)&s.sin_addr.s_addr, sizeof(u_long));
/*
* Определяем размер MTU
*/
   if(ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
  perror("ioctl SIOCGIFMTU");
  return -1;
   }
   ifp->mtu = ifr.ifr_mtu;
/*
* Индекс интерфейса
*/
   if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
  perror("ioctl SIOCGIFINDEX");
  return -1;
   }
   ifp->index = ifr.ifr_ifindex;
/*
* Устанавливаем заданный режим работы сетевого интерфейса
*/
setmode:
/*
* Получаем значение флагов
*/
   if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
  perror("ioctl SIOCGIFFLAGS");
  close(fd);
  return -1;
   }
/*
* В зависимости от значения третьего параметра функции, устанавливаем
* или снимаем флаг неразборчивого режима
*/
   if(mode) ifr.ifr_flags |= IFF_PROMISC;
   else ifr.ifr_flags &= ~(IFF_PROMISC);
/*
* Устанавливаем новое значение флагов интерфейса
*/
   if(ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) {
  perror("ioctl SIOCSIFFLAGS");
  close(fd);
  return (-1);
   }
   return 0;
}
int getsock_recv(int index)
{
   int sd; // дескриптор сокета
/*
* При работе с пакетными сокетами для хранения адресной информации
* сетевого интерфейса вместо структуры sockaddr_in используется структура
* sockaddr_ll (см. <linux/if_packet.h>)
*/
   struct sockaddr_ll s_ll;
/*
* Cоздаем пакетный сокет. Т.к. MAC-адреса мы тоже собираемся обрабатывать (и потому не хотим, чтобы он извлекался из пакета предыдущим протоколом),
* параметр type системного вызова socket принимает значение SOCK_RAW
*/
   sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
   if(sd < 0) return -1;
   memset((void *)&s_ll, 0, sizeof(struct sockaddr_ll));
/*
* Заполним поля адресной структуры s_ll
*/
   s_ll.sll_family = PF_PACKET; // тип сокета
   s_ll.sll_protocol = htons(ETH_P_ALL); // тип принимаемого протокола
   s_ll.sll_ifindex = index; // индекс сетевого интерфейса
/*
* Привязываем сокет к сетевому интерфейсу. В принципе, делать это не
* обязательно, если на хосте активен только один сетевой интерфейс.
* При наличии двух и более сетевых плат пакеты будут приниматься сразу со всех
* активных интерфейсов, и если нас интересуют пакеты только из одного сегмента
* сети, целесообразно выполнить привязку сокета к нужному интерфейсу
*/
   if(bind(sd, (struct sockaddr *)&s_ll, sizeof(struct sockaddr_ll)) < 0) {
  close(sd);
  return -1;
   }
   return sd;
}
/*
* В буфере buff будут сохранятся принятые сетевые пакеты.
* Значение ETH_FRAME_LEN равно максимальной длине кадра Ethernet (1514)
* и определено в <linux/if_ether.h>
*/
__u8 buff[ETH_FRAME_LEN];
/*
* Функция, которая заменит стандартный обработчик сигнала SIGINT.
* Задача этой функции - по приходу сигнала SIGINT вывести интерфейс из
* состояния "Promiscuous mode" в обычный режим
*/
void mode_off()
{
   if(getifconf("eth0", &ifp, PROMISC_MODE_OFF) < 0) {
  perror("getifconf");
  exit(-1);
   }
   return;
}
/*
* Главная функция
*/
int main()
{
   __u32 num = 0;
   int eth0_if, rec = 0, ihl = 0;
   struct iphdr ip; // структура для хранения IP заголовка пакета
   struct tcphdr tcp; // TCP заголовок
   struct ethhdr eth; // заголовок Ethernet-кадра
   static struct sigaction act;
/*
* Получаем параметры сетевого интерфейса eth0 и переводим его
* в неразборчивый режим
*/
   if(getifconf("eth0", &ifp, PROMISC_MODE_ON) < 0) {
  perror("getifconf");
  return -1;
   }
/*
* Отобразим полученные параметры сетевого интерфейса
*/
int IpAddr_my;
  // Дополнительное задание: добавьте цикл, в котором перебирались бы все имеющиеся сетевые интерфейсы
    int Ip1,Ip2,Ip3,Ip4;
    IpAddr_my = ifp.ip;
    Ip1=(int)( IpAddr_my>>24 ) & 0xff;
    Ip2=(int)( IpAddr_my>>16 ) & 0xff;
    Ip3=(int)( IpAddr_my>>8 ) & 0xff;
    Ip4=(int)( IpAddr_my & 0xff);
    printf("Приватный IP-адрес  = \%d.\%d.\%d.\%d\n",Ip4,Ip3,Ip2,Ip1);
int Mask_my;
  // Дополнительное задание: добавьте цикл, в котором перебирались бы все имеющиеся сетевые интерфейсы (см. ЗАДАНИЕ 2, листинг 3).
  Mask_my = ifp.mask;
  Ip1=(int)( IpAddr_my>>24 ) & 0xff;
      Ip2=(int)( IpAddr_my>>16 ) & 0xff;
  Ip3=(int)( IpAddr_my>>8 ) & 0xff;
      Ip4=(int)( IpAddr_my & 0xff);
      printf("Маска подсети \%d.\%d.\%d.\%d\n",Ip4,Ip3,Ip2,Ip1);
   printf("MTU - %d\n", ifp.mtu);
   printf("Индекс - %d\n", ifp.index);
/*
* Получим дескриптор пакетного сокета
*/
   if((eth0_if = getsock_recv(ifp.index)) < 0) {
  perror("getsock_recv");
  return -1;
   }
/*
* Определим новый обработчик сигнала SIGINT - функцию mode_off
*/
   act.sa_handler = mode_off;
   sigfillset(&(act.sa_mask));
   sigaction(SIGINT, &act, NULL);
/*
* Запускаем бесконечный цикл приема пакетов
*/
   for(;;) {
  memset(buff, 0, ETH_FRAME_LEN);
  rec = recvfrom(eth0_if, (char *)buff, ifp.mtu + 18, 0, NULL, NULL);
  if(rec < 0 || rec > ETH_FRAME_LEN) {
      perror("recvfrom");
      return -1;
  }
  memcpy((void *)&eth, buff, ETH_HLEN);
  memcpy((void *)&ip, buff + ETH_HLEN, sizeof(struct iphdr));
  if((ip.version) != 4) continue;
  memcpy((void *)&tcp, buff + ETH_HLEN + ip.ihl * 4, sizeof(struct tcphdr));
/*
* MAC-адреса отправителя и получателя
*/
  printf("\n%u\n", num++);
  printf("%.2x:%.2x:­%.2x:%.2x:%.2x:%.2x \t->\t",
  eth.h_source[0], eth.h_source[1], eth.h_source[2],
  eth.h_source[3], eth.h_source[4], eth.h_source[5]);
  printf("%.2x:%.2x:%­.2x:%.2x:%.2x:%.2x\n",
  eth.h_dest[0], eth.h_dest[1], eth.h_dest[2],
  eth.h_dest[3], eth.h_dest[4], eth.h_dest[5]);
  printf("Длина заголовка - %d, ", (ip.ihl * 4));
  printf("длина пакета - %d\n", ntohs(ip.tot_len));
/*
* Если транспортный протокол - TCP, отобразим IP адреса и порты
* получателя и отправителя
*/
  if(ip.protocol == IPPROTO_TCP) {
addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
printf("Приватный IP-адрес для ТСР-пакета %s\n", inet_ntoa(addr));
      printf("%s (%d)\t->\t",inet_ntoa(*(struct in_addr*) &ip.saddr), ntohs(tcp.source));
      printf(" (%d)\t->\t", ntohs(tcp.source));
      printf("%s (%d)\t->\t",inet_ntoa(*(struct in_addr*) &ip.daddr), ntohs(tcp.dest));
//      printf("%s (%d)\n",inet_ntoa(ip.daddr), ntohs(tcp.dest));
      printf("TCP пакет\n");
  }
   }
   return 0;
}

Такой вот, относительно небольшой, программный код. Ниже рассмотрим его принцип работы, устройство и особенности.


Что делает программа?

↑ К содержанию ↑

- после запуска на выполнение анализатор определяет параметры сетевого интерфейса eth0, такие как IP-адрес, маска подсети, размер MTU, индекс, и переводит интерфейс в неразборчивый режим (promiscuous mode).

В этом режиме интерфейс принимает все пакеты, циркулирующие в сети, даже если они не адресованы(!) данному хосту; это имеет значение в локальных сетях:

- создается пакетный сокет и выполняется его привязка к выбранному сетевому интерфейсу (eth0). Далее анализатор в бесконечном цикле выполняет прием сетевых пакетов и отображает данные об этом пакете - MAC-адреса и IP-адреса отправителя и получателя, размер пакета, размер IP заголовка, тип транспортного протокола (TCP/spanDP), порт отправителя и получателя. Выход из цикла осуществляется по приходу сигнала SIGINT (генерируется комбинацией клавиш Ctrl-C);

- получив сигнал SIGINT, анализатор прерывает цикл приема пакетов, снимает флаг неразборчивого режима с сетевого интерфейса и завершает выполнение.


Общее устройство (логика) программы
↑ К содержанию ↑

Определять параметры сетевого интерфейса и переключать его режимы будет функция getifconf(). Прототип данной функции выглядит следующим образом:

   int getifconf(__u8 *, struct ifparam *, int)

Функция принимает три параметра:

  1. - указатель на строку, содержащую символьное имя сетевого интерфейса;
  2. - указатель на структуру, в которой будут сохранены параметры сетевого интерфейса. Определение этой структуры было рассмотрено в ЗАДАНИИ 2;
  3. - флаг, определяющий режим работы интерфейса.

Создавать пакетный сокет будет функция getsock_recv():

   int getsock_recv (int)

Параметром функции является индекс сетевого интерфейса, к которому будет привязан сокет.


Принцип работы
↑ К содержанию ↑

Рассмотрим, как работает программа. Прежде всего, мы видим, что по сравнению с Заданием 2, добавились дополнительные структуры:

struct iphdr ip; // структура для хранения IP заголовка пакета
struct tcphdr tcp; // TCP заголовок
struct ethhdr eth; // заголовок Ethernet-кадра
static struct sigaction act; // Реакция на сигналы (например, на Ctrl + C)

Вызывается функция getifconf с параметрами "eth0", &ifp, PROMISC_MODE_ON. В качестве eth0 фигурирует интерфейс, трафик с которым будет перехватываться. Ссылка на структуру ifp (потомок структуры ifparam) необходима для того, чтобы получить параметры соединения, такие, как IP-адрес, маска подсети, размер MTU, индекс интерфейса. Наконец, через PROMISC_MODE_ON устанавливается так называемый неразборчивый режим (см. ниже), который позволяет перехватывать абсолютно ВСЕ пакеты, неважно, кто их отправитель/адресат.

Функция getifconf записывает в поле ifr_name структуры ifr имя сетевого интерфейса (в данном случае – eth0). Если неразборчивый режим отключать НЕ нужно, то происходит определение IP-адреса сетевого интерфейса путем вызова уже знакомой нам функции ioctl.

Далее, как обычно, структура s очищается (заполняется нулями).

Функция

memcpy((void *)&s, (void *)&ifr.ifr_addr, sizeof(struct sockaddr))

заполняет структуру s значением IP-адреса, а функция

memcpy((void *)&ifp->ip, (void *)&s.sin_addr.s_addr, sizeof(__u32))

задает поле ip структуры ifp (см. листинг 5 Методических указаний по ВССТ).

Затем определяется маска сети. Обратите внимание, что в функции ioctl теперь применен параметр SIOCGIFNETMASK вместо SIOCGIFADDR. Как Вы уже знаете (см. Задание 2), функция ioctl может очень многое в области сетевых соединений: все зависит от того, какой параметр ей будет передан.

Далее, определяем размер MTU, что осуществляется применением SIOCGIFMTU в функции ioctl.

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

Затем На экран выводится информация, характеризующая соединение (IP-адрес компьютера в локальной сети, маска сети, размер MTU).


После этого путем вызова функции getsock_recv создается пакетный сокет и определяется его дескриптор. Обратите внимание на строчку

sd = sосket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

Первым параметром функции socket идет PF_PACKET. Помните, ранее мы обычно указывали AF_INET (или PF_INET)? Так вот, ранее мы пользовались сокетами, созданными на транспортном (сетевом) уровне протоколов. А теперь используем канальные сокеты. Для них, как Вы уже знаете, понятие «порт» не существует.

При работе с пакетными сокетами для хранения адресной информации сетевого интерфейса вместо структуры sockaddr_in используется структура sockаddr_ll (см. <linuх/if_packet.h>)

   struct sockaddr_ll s_ll;

Вторым параметром функции socket идет SOCK_RAW. В принципе, можно было бы использовать и SOCK_DGRAM. Однако, т.к. MAC-адреса мы тоже собираемся обрабатывать (и потому не хотим, чтобы он извлекался из пакета предыдущим протоколом), параметр type функции socket принимает значение SOCK_RAW.

Далее – все, как и раньше: заполняются поля структуры (теперь уже s_ll, потомка структуры sockaddr_ll). Обратите внимание, что раньше (при использовании AF_INET) задавался порт; теперь - индекс сетевого интерфейса. Также задается тип принимаемого (транспортного!) протокола. В стеке ТСР/IP это – протоколы ТСР, UDP.

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

Дальнейшее объяснение работы осуществите САМОСТОЯТЕЛЬНО.


Результаты работы программы
↑ К содержанию ↑

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

Приватный IP-адрес  = 192.168.145.135
Маска подсети 192.168.145.135
MTU - 1500
Индекс - 2
0
хх:хх:хх:хх:хх:хх  -> ff:ff:ff:ff:ff:ff
Длина заголовка - 20, длина пакета – 68
1
хх:хх:хх:хх:хх:хх  -> ff:ff:ff:ff:ff:ff
Длина заголовка - 20, длина пакета – 68
2
хх:хх:хх:хх:хх:хх  -> 01:00:5e:00:00:fc
Длина заголовка - 20, длина пакета - 50
3
хх:хх:хх:хх:хх:хх  -> ff:ff:ff:ff:ff:ff
Длина заголовка - 20, длина пакета - 78
4
хх:хх:хх:хх:хх:хх  -> ff:ff:ff:ff:ff:ff
Длина заголовка - 20, длина пакета – 78

и т.д.

Примечание. В целях простоты, я заменил свои МАС-адреса, относящиеся к моему сетевому интерфейсу, символами «х» и «у». МАС-адреса сторонних сетевых интерфейсов оставлены как есть.

А теперь запустим браузер и там откроем (или обновим) любую вебстраницу, например, Rambler.ru. И вот что видим:

...
50
уу:уу:уу:уу:уу:уу  -> ­00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 40
Приватный IP-адрес для ТСР-пакета 255.255.255.0
192.168.145.135 (49200)  -> (49200)  ->  136.243.42.207 (443)  ->  TCP пакет
51
00:50:56:fa:44:ec  -> уу:уу:уу:уу:уу:уу
Длина заголовка - 20, длина пакета - 148
Приватный IP-адрес для ТСР-пакета 255.255.255.0
136.243.42.207 (443)  ->   (443)  ->  192.168.145.135 (49200)  ->  TCP пакет
52
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 40
Приватный IP-адрес для ТСР-пакета 255.255.255.0
192.168.145.135 (49200) ->   (49200)  ->  136.243.42.207 (443)  ->  TCP пакет
53
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 202
Приватный IP-адрес для ТСР-пакета 255.255.255.0
192.168.145.135 (49200)  ->   (49200)  ->  136.243.42.207 (443)  ->  TCP пакет
54
00:50:56:fa:44:ec  -> уу:уу:уу:уу:уу:уу
Длина заголовка - 20, длина пакета - 40
Приватный IP-адрес для ТСР-пакета 255.255.255.0
136.243.42.207 (443)  ->   (443)  ->  192.168.145.135 (49200)  ->  TCP пакет
55
уу:уу:уу:уу:уу:уу -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 207
Приватный IP-адрес для ТСР-пакета 255.255.255.0
192.168.145.135 (44007)  -> (44007)  ->  81.19.82.0 (443)  ->  TCP пакет
56
00:50:56:fa:44:ec -> уу:уу:уу:уу:уу:уу
Длина заголовка - 20, длина пакета - 40
Приватный IP-адрес для ТСР-пакета 255.255.255.0
81.19.82.0 (443) ->   (443)  ->  192.168.145.135 (44007)  ->  TCP пакет
57
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 58
58
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 58
59
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 58
60
00:50:56:fa:44:ec -> уу:уу:уу:уу:уу:уу
Длина заголовка - 20, длина пакета - 1400
Приватный IP-адрес для ТСР-пакета 255.255.255.0
81.19.82.0 (443)  ->   (443)  -> 192.168.145.135 (44008)  ->  TCP пакет
61
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 40
Приватный IP-адрес для ТСР-пакета 255.255.255.0
192.168.145.135 (44008)  ->   (44008)  ->  81.19.82.0 (443)  ->  TCP пакет
62
00:50:56:fa:44:ec -> уу:уу:уу:уу:уу:уу
Длина заголовка - 20, длина пакета - 1400
Приватный IP-адрес для ТСР-пакета 255.255.255.0
81.19.82.0 (443)  ->   (443)  ->  192.168.145.135 (44008)  ->  TCP пакет

и т.д.
Т.е. мы напрямую видим, как наш компьютер обменивается сообщениями с другим. Вначале «собеседником» был адрес 136.243.42.207. Потом – адрес 81.19.82.0.
А затем – программа вышла из бесконечного цикла и завершила свою работу с сообщением

138
уу:уу:уу:уу:уу:уу  -> 00:50:56:fa:44:ec
Длина заголовка - 20, длина пакета - 40
Приватный IP-адрес для ТСР-пакета 255.255.255.0
192.168.145.135 (49200)  ->   (49200)  ->  136.243.42.207 (443)  ->  TCP пакет
recvfrom: Success

Подумайте, почему работа программы прекратилась?


III. Самостоятельная разработка

↑ К содержанию ↑
  1. Реализуйте циклы, перебирающие ВСЕ имеющиеся интерфейсы в ОС (для в двух местах, см. программный код),
  2. Предложите вариант(ы), как устранить причину самопроизвольного прекращения работы программы.
  3. Реализуйте перебор ВСЕХ протоколов (а не только ТСР). Для этого вначале следует сделать запрос имени протокола. А затем выводить это имя. Т.е. должно выводиться сообщение вида: «Приватный IP-адрес для ХХХ-пакета», где «ХХХ» - имя соответствующего протокола.
  4. Программа выдает сообщение: «Приватный IP-адрес для ТСР-пакета 255.255.255.0». Что является не совсем верным, так как это не IP-адрес, а маска. Надо переделать программу так, чтобы выдавался именно IP-адрес.
  5. Программа должна выводить доменное имя хоста, с которого открывается вебстраница (соответствующее IP-адресу). Например: rambler.ru. Добавьте этот функционал в конце бесконечного цикла.
  6. Программа должна указывать версию сетевого протокола (IPv4 или IPv6). Добавьте этот функционал в конце бесконечного цикла.

Контрольные вопросы:
↑ К содержанию ↑
  1. Что представляет собой тип переменных __u32?
  2. Почему в программе вначале используется обычный сокет, а потом – пакетный?
  3. Охарактеризуйте локальные (приватные) и публичные IP-адреса. Приведите примеры адресов обоих видов.
  4. Что представляет собой константа IFNAMSIZ? Ее значение, назначение.
  5. На каком уровне протоколов (по модели OSI)  работает пакетный сокет?
  6. Что делает (может делать) функция ioctl? Какой ее параметр изменяется щля придания ей различной функциональности?
  7. Какая структура используется при создании пакетных сокетов вместо sockaddr_in?
  8. Возможно ли вместо функций memcpy применить обычное присванивание?
  9. Что такое неразборчивый режим? Какая константа служит для его включения?
  10. Поясните, для чего используется переход  if(!mode) goto setmode? Каким образом изменяется работа программы в случае, если включен неразборчивый режим и почему?
  11. Что такое MTU?
  12. Что такое индекс сетевого интерфейса? Чем он отличается от номера порта?
  13. Что дает использование SOCK_RAW вместо SOCK_DGRAM? Можно ли первый параметр использовать для сокетов типа PF_INET?
  14. В чем отличие по смыслу констант AF_INET и PF_INET? А по значению?
  15. Что такое  0xff?
  16. Подробно объясните, как осуществляется извлечение отдельных частей IP-адреса сетевого интерфейса в коде:
  17. int Ip1,Ip2,Ip3,Ip4;
        IpAddr_my = ifp.ip;
        Ip1=(int)( IpAddr_my>>24 ) & 0xff;
        Ip2=(int)( IpAddr_my>>16 ) & 0xff;
        Ip3=(int)( IpAddr_my>>8 ) & 0xff;
        Ip4=(int)( IpAddr_my & 0xff);
  18. Что представляет собой переменная eth0_if?
  19. Что означает «установлен обработчик события»? Что именно вносит в работу программы установка обработчика?
  20. Почему при определении нового обработчика сигнала SIGINT – через функцию mode_off
       act.sa_handler = mode_off;
    перед точкой с запятой нет скобок ()?
  21. Что представляет собой константа ETH_HLEN? Чему она равна?
  22. Для какой версии сетевого протокола будет работь программа: для IPv4 или для IPv6? Если будет работать для обоих версий, то в чем будет отличие?
  23. Приведите примеры IP-адресов, записанных в форматах каждого из этих протоколов.
  24. Чем отличается МАС-адрес от IP-адреса?
  25. Какая структура используется для работы с МАС-адресами?
  26. В строчке
     printf("%s (%d)\t->\t",inet_ntoa(*(struct in_addr*) &ip.saddr), ntohs(tcp.source)); используется конструкция *(struct in_addr*). Для чего она, что делает? Что выдаст компилятор в случае ее отсутствия?
  27. Почему символ указателя присутствует дважды?
  28. Почему в строчке printf("%s (%d)\n",inet_ntoa(ip.daddr), ntohs(tcp.dest)); указанная выше конструкция не используется?
  29. Что делает строчка addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;  ?
  30. Почему в Листинге 1 (см. Задание 2)  в похожей строчке
     *addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
    вместо самой переменной addr используется указатель на нее?
  31. Объясните запись, выдаваемую программой в консоль
    81.19.82.0 (443)  ->   (443)  ->  192.168.145.135 (44007)  ->  TCP пакет
    в соответствии с функциональностью программы.

С уважением, Салимоненко Д.А.