Перехват соединений

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

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

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

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

Сборка

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

make

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

make config
или
make menuconfig
или
make xconfig

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

Чтобы использовать 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-сокетов функция дефрагментирования не реализована. Работа над этим продолжается.

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

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

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

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

Сокеты Netlink

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

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

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

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

Raw-сокеты

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

libpcap

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

Потоки firewall

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

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

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

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

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

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

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

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

Входящий

Проходящий

Исходящий

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

Использование 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 совместно с программой перехвата пакетов.

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

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

Здесь приведен пример программы, которая читает пакеты с 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 ядра не влияет на поведение программы.

Пределы

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

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

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

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

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

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

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

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

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

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

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

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

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

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