В этой главе обсуждаются возможности применения divert-cокетов, и чем они отличаются от других существующих механизмов перехвата соединений.
Существуют другие пакеты, позволяющие производит перехват IP-пакетов. Ниже описано, чем они отличаются от divert-сокетов:
Сокеты Netlink могут перехватывать IP-пакеты так же, как и divert sockets - используя firewall. Для них существует специальный тип (AF_NETLINK) и с первого взгляда они ничем не отличаются от divert. Но, на самом деле, существуют два серьезных отличия:
В системе Netlink-сокетов нет портов, поэтому очень сложно иметь несколько процессов, перехватывающих разные потоки данных (в divert-сокетах встроено стандартное 16-битное пространство портов - соответственно, у вас может работать параллельно до 65535 процессов, перехватывающих разные потоки данных)
В системе Netlink-сокетов не существует простого способа посылать пакеты дальше (обратно в сеть), потому что в нее не встроена защита от повторного перехвата этих же пакетов. В divert-сокетах эта проверка производится автоматически
Использование RAW-сокетов - неплохой способ прослушивания сетевого потока (особенно в Linux, в котором RAW-сокеты могут прослушивать и TCP, и UDP-трафик - многие другие UNI*-ы этого не позволяют), но RAW-сокет не может помешать пакету продолжить свой путь до получателя - он просто предоставляет вам копию пакета. Нет никакого способа послать модифицированный пакет дальше, потому что исходный пакет не останавливается. Более того, вы можете фильтровать пакеты только по номеру протокола, который задается при открытии RAW-сокета. RAW-сокеты никоим образом не взаимодействуют с firewall.
Библиотека libpcap, и в частности ее наиболее известная утилита tcpdump, позволяет прослушивать трафик, идущий через сетевой интерфейс (это может быть ppp, eth, и т.п.). В ethernet вы можете включить на своей сетевой карте режим " promisc", и она станет обрабатывать весь IP-трафик, идущий по сети, а не только адресованные ей пакеты. Конечно, в libpcap не встроено способов перехвата пакетов, и нет способа их послать. На самом деле, libpcap и divert-сокеты служат для разных целей - их нельзя сравнивать.
В Linux существует три потока пакетов: входящий (input), исходящий (output) и проходящий (forward). Существуют также учетные потоки, но они нас не интересуют. В зависимости от происхождения пакета, он проходит через один или несколько из следующих потоков:
через него проходят все пакеты, попадающие в машину извне - пакеты, предназначенные для этой машины, и пакеты, которые будут переданы дальше.
через него проходят все пакеты, отправленные этой машиной, а также пересылаемые пакеты
через него проходят все пакеты, пересылаемые этой машиной.
Переадресованный пакет проходит через все три потока в следующем порядке:
Входящий
Проходящий
Исходящий
Для полной ясности надо придерживаться следующего правила - проходящий поток должен использоваться только для отбрасывания пакетов, не предназначенных для этой машины, и не посланных ей. Если вам интересны и пересылаемые пакеты и пакеты, относящиеся к этой машине, - используйте входящий или исходящий потоки. Перехват однотипных пакетов на входящем и исходящем потоках не только создаст проблемы с пересылкой, но и, что более важно, в этом просто нет необходимости.
Модифицированная версия ipchains, которую вы возьмете на указанном выше веб-сайте - это утилита, позволяющая изменять правила firewall из командной строки. Существует также способ задать эти правила из программы. В примере программы перехвата будет использоваться именно этот способ - настройка правила DIVERT аналогична настройке правила REDIRECT - указываете DIVERT в роли получателя, номер divert-порта, и у вас все готово.
Синтаксис команды ipchains для настройки правил firewall не изменился. Для использования правила DIVERT, вы должны использовать опцию -j DIVERT <port num> в роли получателя, а все остальное остается без изменений. Например команда
ipchains -A input -p ICMP -j DIVERT 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 |
Если вы не использовали опцию pass-through при сборке ядра, то добавление в первой строчке правила firewall приведет к тому, что пакеты, отвечающие этому правилу, будут отбрасываться, если нет программы перехвата. Подробнее читайте выше.
Если вы хотите, чтобы правило firewall задавалось вашей программой - соберите ее с опцией -DFIREWALL, и она будет перехватывать все ICMP-пакеты из входящего потока. Она также автоматически удалит правило DIVERT по окончании работы. В этом случае опция pass-through ядра не влияет на поведение программы.
По моему мнению, область применения divert-сокетов может быть ограничена лишь пределами вашего воображения. Мне будет очень интересно услышать о программах, использующих divert-сокеты.
Удачи!