Мини-HOWTO: Перехват соединений

Версия 1.1, 27 февраля 2000

В этом документе содежится информация по установке и настройке в Linux 2.2.12 перехвата IP-соединений, при помощи системы divert-сокетов, входящей в комплект FreeBSD.


1. Copyright

Copyright 1999(c) by Ilia Baldine. This document may be distributed only subject to the terms and conditions set forth in the LDP License at, except that this document must not be distributed in modified form without the author's consent.


2. Авторские права

Авторские права на русский перевод этого текста принадлежат © 2000 SWSoft Pte Ltd. Все права зарезервированы.

Этот документ является частью проекта Linux HOWTO.

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

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

Мы бы хотели распространить эту информацию по всем возможным каналам. Но при этом сохранить авторские права и быть уведомленными о всех планах распространения HOWTO. Если у вас возникли вопросы, пожалуйста, обратитесь к координатору проекта Linux HOWTO по электронной почте: или к координатору русского перевода Linux HOWTO компании SWSoft Pte Ltd. по адресу


3. Ответственность

Этот документ был разработан в рамках проекта по обеспечению безопасности компьютерных сетей, финансируемого агентством DARPA. Ни я (Ilia Baldine), ни мой работодатель (MCNC), ни DARPA не несут ответственности за нанесенный или потенциальный ущерб, который может вызвать применение программы и/или использование процедур, описанных в документе. Как и множество других сетевых приложений, divert-сокеты могут использоваться как в законной плоскости, так и незаконной, а выбор этой плоскости остается за вами!


4. Вступительное слово

Here's an easy game to play,
Here's an easy thing to say:

If a packet hits a pocket
 on a socket on a port
And the bus is interrupted
  as a very last resort,
And the address of the memory
  makes your floppy disk abort,
Then the socket packet pocket
  has an error to report!!

If your cursor finds a menu item
  followed by a dash,
And the double clicking icon puts  your
  window in the trash,
And your data is corrupted 'cause the
  index doesn't hash,
Then the situation's hopeless, and your
  system's gonna crash!

YOU CAN'T SAY THIS? WHAT A SHAME SIR!
WE'LL FIND ANOTHER GAME SIR

If the label on the cable on the table
  at your house,
Says the network is connected to
  the button on your mouse,
But your packets want to tunnel
  on another protocol,
That's repeatedly rejected
  by the printer down the hall,
And your screen is all distorted
  by the side effects of gauss
So your icons in the window are
  as wavy as a souse,
Then you  may as well reboot and
  go out with a bang,
'Cause as sure as I'm a poet,
  the sucker's gonna hang!
When the copy of your floppy's
  getting sloppy on the disk
And the microcode instructions cause
 unnecessary risc,
Then you have to flash your memory and
  you'll want to RAM your  ROM
Quickly turn off your computer and
  be sure  to tell your mom!

-- Anonymous


5. Введение

Вам, наверное. не раз хотелось перехватить пакеты, проходящие туда-сюда через вашу машину? Нет, я не говорю о простом просмотре пакетов в натуральном виде (как в raw-сокетах или libpcap (tcpdump)). Я говорю именно о том, чтобы перехватить пакет и, затем, возможно, после некоторой модификации, переслать его дальше. Ну что ж, время, когда это было простой мечтой, закончилось - теперь у нас есть divert-сокеты для Linux!

Divert-сокеты именно это и делают - они отфильтровывают пакеты, в соответствии с настройками firewall, и передают их вашей программе. Затем вы можете: просто отправить пакет дальше, модифицировать и отправить, или не пересылать его вообще.

Как вы наверно уже догадались по названию пакета, этот механизм включает в себя использование специальных RAW-сокетов, называемых divert (IPPROTO_DIVERT), позволяющих вам производить передачу и прием данных на них таким же образом, как и на обычных сокетах. Единственное различие состоит в том, что этот сокет привязан к порту - на него можно пересылать конкретные пакеты, попадающие в firewall. Любые данные, проходящие через firewall, могут быть посланы в этот сокет.

Эта система изначально входила в состав FreeBSD. Divert-сокеты для Linux - это адаптированная версия исходного пакета, претендующая на совместимость с ним (по крайней мере на уровне исходных текстов программ, использующих этот механизм).


6. Получение и сборка исходных текстов

Для использования divert-сокетов в Linux, вам понадобятся две вещи - модифицированные патчем исходные тексты ядра и исходные тексты пакета ipchains версии 1.3.9, также модифицированные соответствующим патчем.


6.1. Где взять исходные тексты?

Оба патча можно взять с веб-сайта divert-сокетов http://www.anr.mcnc.org/~divert Для ядра существуют два варианта - патч к неизмененным исходным текстам ядра версии 2.2.12, или готовые модифицированные исходные тексты ядра 2.2.12 (значительно большие по размеру, чем патч). ipchains находится там же, в виде уже модифицированного пакета с исходными текстами.


6.2. Сборка

Собрать ipchains достаточно просто - дайте команду

make
в подкаталоге ipchains-1.3.9.

Для включения поддержки divert-сокетов, вам надо пересобрать ядро, предварительно настроив его командой:

make config
или
make menuconfig
или
make xconfig
Не забудьте включить опцию "Поддержку неполных и/или разрабатываемых кодов/драйверов" ("Prompt for development and/or incomplete code/drivers"). Существуют всего три опции, влияющих на работу divert-сокетов, и все они описаны в следующей главе.


6.2.1. Опции, необходимые при сборке ядра

Чтобы использовать divert-сокеты, вам надо включить в ядро поддержку firewall и IP-firewall. На работу divert-сокетов влияют три опции сборки ядра:

IP: divert sockets

Включает поддержку divert-сокетов в ядре.

IP: divert pass-through

Определяет поведение правил DIVERT: по умолчанию правило DIVERT, описанное в firewall, отбрасывает пакеты, при отсутствии программы на порте, определенном этим правилом, то есть действует аналогично правилу DENY.

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

IP: always defragment

Определяет, выполнять ли дефрагментирование при передаче данных в сокет. По умолчанию код divert-сокет получает отдельные фрагменты пакетов, имеющих больший, чем MTU, размер и посылает их в таком же виде программе. Задача дефрагментации в этом случае лежит на приложении, использующем divert-сокеты. Более того, приложение не может послать фрагмент пакета, больший, чем MTU - он сразу будет отброшен (это ограничение ядра, а не divert-сокетов - ядра Linux версии до 2.2.x НЕ фрагментируют пакеты с установленной опцией IP_HDRINCL). Обычно в таком поведении нет ничего страшного - в основном, вы просто пересылаете те же фрагменты пакетов, которые получили, и все прекрасно работает - в этом случае, размер фрагментов не будет больше MTU.

Если вы включите опцию always defragment, то все дефрагментирование будет производиться в ядре. Это сильно уменьшает производительность механизма перехвата - каждый большой пакет, который вы хотите перехватить, должен быть сначала собран из фрагментов, и только после этого будет передан вашей программе. Затем, когда вы захотите послать его дальше, он будет снова разбит на части (если в ядре включить эту опцию, то оно будет фрагментировать пакеты, имеющие флаг IP_HDRINCL)

В ядрах Linux версии 2.0.36 подобный выбор не предоставлялся из-за неправильной структуры кода firewall - он обрабатывал только первый фрагмент пакета, а с остальными фрагментами поступал так же, как и с первым, не обрабатывая их. В результате этого, если первый фрагмент был отброшен firewall, то и все остальные отбрасывались дефрагментатором. Поэтому для нормальной работы с divert-сокетами в этой версии ядра вы ДОЛЖНЫ были использовать опцию always defragment для того, чтобы получать весь пакет, а не только его первый фрагмент.

В версии 2.2.12 структура кода firewall была исправлена, и вы можете сами решать, будет ли ядро заниматься (де)фрагментацией, или вы будете делать этой в своей программе.

ВНИМАНИЕ: в версии 1.0.4 divert-сокетов функция дефрагментирования не реализована. Работа над этим продолжается.


7. Использование перехвата соединений

В этой главе обсуждаются возможности применения divert-cокетов, и чем они отличаются от других существующих механизмов перехвата соединений.


7.1. Divert-сокеты и другие подобные системы

Существуют другие пакеты, позволяющие производит перехват IP-пакетов. Ниже описано, чем они отличаются от divert-сокетов:


7.1.1. Сокеты Netlink

Сокеты Netlink могут перехватывать IP-пакеты так же, как и divert sockets - используя firewall. Для них существует специальный тип (AF_NETLINK) и с первого взгляда они ничем не отличаются от divert. Но, на самом деле, существуют два серьезных отличия:

  • В системе Netlink-сокетов нет портов, поэтому очень сложно иметь несколько процессов, перехватывающих разные потоки данных (в divert-сокетах встроено стандартное 16-битное пространство портов - соответственно, у вас может работать параллельно до 65535 процессов, перехватывающих разные потоки данных)

  • В системе Netlink-сокетов не существует простого способа посылать пакеты дальше (обратно в сеть), потому что в нее не встроена защита от повторного перехвата этих же пакетов. В divert-сокетах эта проверка производится автоматически

Если честно, netlink-сокеты существуют не только для перехвата. В общих словах, механизм netlink предназначен для осуществления связи между ядром и пользователем. Существуют, например, netlink-сокеты маршрутизатора, позволяющие вам работать с подсистемой маршрутизации пакетов. Однако, с точки зрения перехвата пакетов, netlink-сокеты не настолько гибки, как divert.


7.1.2. Raw-сокеты

Использование RAW-сокетов - неплохой способ прослушивания сетевого потока (особенно в Linux, в котором RAW-сокеты могут прослушивать и TCP, и UDP-трафик - многие другие UNI*-ы этого не позволяют), но RAW-сокет не может помешать пакету продолжить свой путь до получателя - он просто предоставляет вам копию пакета. Нет никакого способа послать модифицированный пакет дальше, потому что исходный пакет не останавливается. Более того, вы можете фильтровать пакеты только по номеру протокола, который задается при открытии RAW-сокета. RAW-сокеты никоим образом не взаимодействуют с firewall.


7.1.3. libpcap

Библиотека libpcap, и в частности ее наиболее известная утилита tcpdump, позволяет прослушивать трафик, идущий через сетевой интерфейс (это может быть ppp, eth, и т.п.). В ethernet вы можете включить на своей сетевой карте режим " promisc", и она станет обрабатывать весь IP-трафик, идущий по сети, а не только адресованные ей пакеты. Конечно, в libpcap не встроено способов перехвата пакетов, и нет способа их послать. На самом деле, libpcap и divert-сокеты служат для разных целей - их нельзя сравнивать.


7.2. Потоки firewall

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

Входящий поток (Input)

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

Исходящий поток (Output)

через него проходят все пакеты, отправленные этой машиной, а также пересылаемые пакеты

Проходящий поток (Forward)

через него проходят все пакеты, пересылаемые этой машиной.

Переадресованный пакет проходит через все три потока в следующем порядке:

  1. Входящий

  2. Проходящий

  3. Исходящий

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

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


7.3. Использование ipchains

Модифицированная версия ipchains, которую вы возьмете на указанном выше веб-сайте - это утилита, позволяющая изменять правила firewall из командной строки. Существует также способ задать эти правила из программы. В примере программы перехвата будет использоваться именно этот способ - настройка правила DIVERT аналогична настройке правила REDIRECT - указываете DIVERT в роли получателя, номер divert-порта, и у вас все готово.

Синтаксис команды ipchains для настройки правил firewall не изменился. Для использования правила DIVERT, вы должны использовать опцию -j DIVERT <port num> в роли получателя, а все остальное остается без изменений. Например команда

ipchains -A input -p ICMP -j DIVERT 1234
настроит правило divert для ICMP-пакетов - они будут передаваться из входящего потока на порт 1234.

В следующей главе мы опишем, как использовать ipchains совместно с программой перехвата пакетов.


7.4. Пример программы

7.4.1. Текст программы

Здесь приведен пример программы, которая читает пакеты с divert-сокета, выводит их содержимое на экран и затем пересылает дальше. В командной строке ей надо указать номер divert-порта для перехвата.

#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <signal.h>

#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <net/if.h>
#include <sys/param.h>

#include <linux/types.h>
#include <linux/icmp.h>
#include <linux/ip_fw.h>

#define IPPROTO_DIVERT 254
#define BUFSIZE 65535

char *progname;

#ifdef FIREWALL

char *fw_policy="DIVERT";
char *fw_chain="output";
struct ip_fw fw;
struct ip_fwuser ipfu;
struct ip_fwchange ipfc;
int fw_sock;

/* удаляем все существующие правила firewall */
void intHandler (int signo) {

  if (setsockopt(fw_sock, IPPROTO_IP, IP_FW_DELETE, &ipfc, sizeof(ipfc))==-1) {
    fprintf(stderr, "%s: невозможно удалить правило: %s\n", progname, strerror(errno));
    exit(2);
  }

  close(fw_sock);
  exit(0);
}

#endif

int main(int argc, char** argv) {
  int fd, rawfd, fdfw, ret, n;
  int on=1;
  struct sockaddr_in bindPort, sin;
  int sinlen;
  struct iphdr *hdr;
  unsigned char packet[BUFSIZE];
  struct in_addr addr;
  int i, direction;
  struct ip_mreq mreq;

  if (argc!=2) {
    fprintf(stderr, "Использование: %s <port number>\n", argv[0]);
    exit(1);
  }
  progname=argv[0];

  fprintf(stderr,"%s:Создание сокета\n",argv[0]);
  /* открываем divert-сокет */
  fd=socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT);

  if (fd==-1) {
    fprintf(stderr,"%s:Невозможно открыть divert-сокет\n",argv[0]);
    exit(1);
  }

  bindPort.sin_family=AF_INET;
  bindPort.sin_port=htons(atol(argv[1]));
  bindPort.sin_addr.s_addr=0;

  fprintf(stderr,"%s:Подключение сокета\n",argv[0]);
  ret=bind(fd, &bindPort, sizeof(struct sockaddr_in));

  if (ret!=0) {
    close(fd);
    fprintf(stderr, "%s: Ошибка bind(): %s",argv[0],strerror(ret));
    exit(2);
  }
#ifdef FIREWALL
  /* сначала заполняем поля правила */
  bzero(&fw, sizeof (struct ip_fw));
  fw.fw_proto=1; /* ICMP */
  fw.fw_redirpt=htons(bindPort.sin_port);
  fw.fw_spts[1]=0xffff;
  fw.fw_dpts[1]=0xffff;
  fw.fw_outputsize=0xffff;

  /* заполняем структуру fwuser */
  ipfu.ipfw=fw;
  memcpy(ipfu.label, fw_policy, strlen(fw_policy));

  /* заполняем структуру fwchange */
  ipfc.fwc_rule=ipfu;
  memcpy(ipfc.fwc_label, fw_chain, strlen(fw_chain));

  /* открываем сокет */
  if ((fw_sock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW))==-1) {
    fprintf(stderr, "%s: невозможно создать raw-сокет: %s\n", argv[0], strerror(errno));
    exit(2);
  }

  /* записываем правило */
  if (setsockopt(fw_sock, IPPROTO_IP, IP_FW_APPEND, &ipfc, sizeof(ipfc))==-1) {
    fprintf(stderr, "%s невозможно установить правило firewall: %s\n", argv[0], strerror(errno));
    exit(2);
  }

  /* устанавливаем обработчик сигнала для удаления правила */
  signal(SIGINT, intHandler);
#endif /* FIREWALL */

  printf("%s: Ожидание данных...\n",argv[0]);
  /* читаем данные */
  sinlen=sizeof(struct sockaddr_in);
  while(1) {
    n=recvfrom(fd, packet, BUFSIZE, 0, &sin, &sinlen);
    hdr=(struct iphdr*)packet;

    printf("%s: Содержимое пакета:\n",argv[0]);
	for( i=0; i<40; i++) {
		printf("%02x ", (int)*(packet+i));
		if (!((i+1)%16)) printf("\n");
	};
    printf("\n");

    addr.s_addr=hdr->saddr;
    printf("%s: Адрес отправителя: %s\n",argv[0], inet_ntoa(addr));
    addr.s_addr=hdr->daddr;
    printf("%s: Адрес получателя: %s\n", argv[0], inet_ntoa(addr));
    printf("%s: IF-адрес получателя: %s\n", argv[0], inet_ntoa(sin.sin_addr));
    printf("%s: Номер протокола: %i\n", argv[0], hdr->protocol);

    /* пересылка */

#ifdef MULTICAST
   if (IN_MULTICAST((ntohl(hdr->daddr)))) {
	printf("%s: Multicast-адрес!\n", argv[0]);
	addr.s_addr = hdr->saddr;
	errno = 0;
	if (sin.sin_addr.s_addr == 0)
	    printf("%s: set_interface вернул %i ошибку номер =%i\n", argv[0], setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)), errno);
    }
#endif

#ifdef REINJECT
   printf("%s Пересылка DIVERT %i байт\n", argv[0], n);
   n=sendto(fd, packet, n ,0, &sin, sinlen);
   printf("%s: переслано %i байт.\n", argv[0], n);

   if (n<=0)
     printf("%s: Ошибка номер %i\n", argv[0], errno);
   if (errno == EBADRQC)
     printf("errno == EBADRQC\n");
   if (errno == ENETUNREACH)
     printf("errno == ENETUNREACH\n");
#endif
  }
}

Вы можете просто скопировать эту программу и откомпилировать ее. Если вы хотите разрешить пересылку - соберите ее с флагом -DREINJECT, в противном случае, она будет только перехватывать пакеты.

Чтобы использовать эту программу, соберите ядро и ipchains-1.3.8 (как это сделать, описано выше. Установите правило в любой из потоков firewall: входящий, исходящий или проходящий, затем пошлите пакеты, соответствующие правилу, и смотрите их содержимое, которое будет появляться на экране. После этого пакеты будут посылаться дальше, если вы использовали соответствующую опцию при компиляции.

Например, после команды:

ipchains -A output -p TCP -s 172.16.128.10 -j DIVERT 4321
interceptor 4321
будут перехвачены все TCP-пакеты, идущие от машины 172.16.128.10 (предположим, что ваша машина - это шлюз). Программа перехватит их, выдаст на экран и только затем отправит дальше.

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

Если вы хотите, чтобы правило firewall задавалось вашей программой - соберите ее с опцией -DFIREWALL, и она будет перехватывать все ICMP-пакеты из входящего потока. Она также автоматически удалит правило DIVERT по окончании работы. В этом случае опция pass-through ядра не влияет на поведение программы.


7.5. Пределы

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

Удачи!


8. Варианты использования перехвата соединений

8.1. Модификация пакетов

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

  • Перед пересылкой должна пересчитываться контрольная сумма в заголовке IP-пакета

  • Поле IP ID будет заполняться за вас, если вы оставите его равным 0.

  • Обновление длины пакета зависит от вас.

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


8.2. Посылка пакетов без перехвата

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

В дополнение к этому, вы должны будете передать divert-сокету структуру sockaddr_in (см. программу), в которой будет описываться, куда передать пакет. Если вы заполните эту структуру нулями или передадите вместо нее NULL, то divert-сокет будет пытаться передать пакет наружу. Если же вы укажете в структуре sockaddr_in адрес одного из сетевых интерфейсов, то divert-сокет попытается послать пакет "внутрь" машины, как будто он пришел извне. Все адреса, конечно, должны быть заданы в соответствии с порядком байт в сетевом адресе.

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


8.3. Фрагментация

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


9. Источники дополнительной информации

9.1. Веб-сайт

Как уже было упомянуто выше, более подробная информация о divert-сокетах находится на веб-сайте Divert-сокетов для Linux http://www.anr.mcnc.org/~divert.


9.2. Список рассылки

Существует список рассылки, который можно найти на веб-сайте. Чтобы подписаться на этот список, отправьте письмо с пустой темой, написав в нем:

subscribe divert
по адресу [email protected]. Адрес списка рассылки [email protected].

Чтобы отказаться от подписки, пошлите по адресу [email protected] письмо с пустой темой, написав в нем:

unsubscribe divert


10. Планы на будущее

Как уже было упомянуто в разделе "Ответственность", работа над divert-сокетами производится в рамках проекта разработки систем безопасности сетей, финансируемого DARPA. Мы продолжим работу по адаптации будущих версий ядра к divert-сокетам по мере возможности. По той причине, что ядра версии 2.4 уже недалеко, мы полностью пропустим серию ядер 2.3.x.