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

В этой главе обсуждаются возможности применения 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)

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

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

  1. Входящий

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

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

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

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

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

Удачи!