Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Packages
 Make 
 Iptables 
 Nmap 
 Apache 
 LFS 
 TUX 
 cURL 
 libpcap 
 Parted 
 Httpd 
 File managers 
 FFMPEG 
 RTMP 
 SQL 
 Test 
NEWS
Последние статьи :
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
  SQL 30.07   
  JFS 10.06   
  B-trees 01.06   
 
TOP 20
 P.Collins...1350 
 Steve Pate 1...817 
 QT->Qt3...541 
 Trees...459 
 Python...372 
 Steve Pate 3...371 
 TCP 2...349 
 Rodriguez 6...325 
 Максвелл 3...319 
 Rubni-Corbet -> Глав...319 
 Daniel Bovet 4...319 
 TCP 3...316 
 Mod_perl 2...296 
 Linux Inline Assembly...296 
 MySQL & PosgreSQL...295 
 Robert Love 2...292 
 Daniel Bovet 3...288 
 Rodriguez 2...284 
 Стивенс 6...284 
 UML 3...282 
 
  01.05.2017 : 2190164 посещений 

iakovlev.org

Programming with pcap

Tim Carstens
timcarst at yahoo dot com
Further editing and development by Guy Harris
guy at alum dot mit dot edu

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

Яковлев С :Для начала заберите библиотеку libpcap-1.0.0 и соберите ее. Вы получите бинарник libpcap.a , который надо будет положить в каталог с примерами , иначе вы их не сможете собрать.

Getting Started: The format of a pcap application

  1. Для начала нужно определиться с интерфейсом. На линуксе это может быть eth0, на BSD это может быть xl1. Устройство мы будем хранить в форме строки,или же можно попросить pcap, чтобы оно само определило это устройство.
  2. Инициализация pcap. Мы конкретно укажем pcap , что хотим сниффить сетевой интерфейс. Если надо . то сразу несколько интерфейсов. Различать мы их будем с помощью file handles. Для работы с таким файлом нужно установить соответственную "сессию".
  3. Наш снифинг будет распространяться только на TCP/IP-пакеты, проходящие через порт 23, для этого нужно составить набор правил. Правило хранится в строке и конвертируется во внутренний формат pcap.
  4. Мы запускаем основной цикл сниффинга.pcap ловит достаточную порцию пакетов. Каждый раз . когда он получает новый пакет , он вызывает соответственную функцию. Она может распечатать пакет , сохранить его в файле.
  5. Мы закрываем сессию и приложение

На самом деле все очень просто.5 шагов , один из которых - 3 - опционален. Давайте глянем на реализацию :

Setting the device

Существует 2 подхода для инициализации девайса . В первом случае пользователь набирает его в командной строке в качестве параметра программы :

	#include <stdio.h>
 	#include <pcap.h>
 
 	int main(int argc, char *argv[])
 	{
 		 char *dev = argv[1];
 
 		 printf("Device: %s\n", dev);
 		 return(0);
 	}
 

Теперь 2-й вариант :

	#include <stdio.h>
 	#include <pcap.h>
 
 	int main(int argc, char *argv[])
 	{
 		char *dev, errbuf[PCAP_ERRBUF_SIZE];
 
 		dev = pcap_lookupdev(errbuf);
 		if (dev == NULL) {
 			fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
 			return(2);
 		}
 		printf("Device: %s\n", dev);
 		return(0);
 	}
 

В этом случае pcap сам устанавливает интерфейс. Если при инициализации произойдет ошибка , она будет сохранена в строке errbuf.

Opening the device for sniffing

Создание сессии снифинга - достаточно простая задача. Для этого мы используем pcap_open_live(). Её прототип :

	pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms,
 	    char *ebuf)
 

Первый аргумент - устройство,второй - челое число, определяет максимальное количество байт, которое может быть захвачено pcap, третий аргумент - если он true , то переводит интерфейс в promiscuous mode. Четвертый аргумент - определяет тайм-аут в миллисекундах, время , необходимое для фиксации определенного числа пакетов. Пятый аргумент - строка ошибки. Функция возвращает session handler.

Рассмотрим фрагмент :

	 #include <pcap.h>
 	 ...
 	 pcap_t *handle;
 
 	 handle = pcap_open_live(somedev, BUFSIZ, 1, 1000, errbuf);
 	 if (handle == NULL) {
 		 fprintf(stderr, "Couldn't open device %s: %s\n", somedev, errbuf);
 		 return(2);
 	 }
 

Устройство здесь хранится в переменной "somedev", количество байт - в BUFSIZ (см. pcap.h). Устанавливаем устройство в promiscuous mode, делаем отлов пакетов до появления ошибки, которая хранится в errbuf.

Несколько слов о promiscuous / non-promiscuous sniffing: Это 2 различные техники. При non-promiscuous sniffing, мы отловим только те пакеты , которые предназначены именно нам. Все остальное будет просеяно. При Promiscuous mode будет отловлен весь сетевой трафик. В этом случае можно определить аналогичные узлы , которые занимаются тем же самым :-) Promiscuous mode работает при условии non-switched (хаб или свитч не прокатят). При интенсивном сетевом трафике Promiscuous mode может нагрузить машину по полной программе.

Filtering traffic

Сниффер можно использовать для специфических задач. Например , прослушивание порта 23 (telnet) может дать информацию о паролях. Через порт 21 (FTP) пересылаются файлы. DNS traffic идет через порт 53 UDP. Для захвата всего сетевого трафика нужно вызвать pcap_compile() и pcap_setfilter().

Далее мы вызываем pcap_open_live() и работаем с сессией. Мы будем использовать BPF driver напрямую.

Перед использованием фильтра его нужно "скомпилировать". Фильтовочное выражение хранится в строке (char array).

Для компиляции мы вызываем pcap_compile(). Прототип:

	int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, 
 	    bpf_u_int32 netmask)
 

Первый аргумент - session handle (pcap_t *handle ). Второй аргумент - ссылка на память , где будет храниться откомпилированная версия фильтра. Дальше идет само выражение в виде строки Следующий параметр - либо 0 , либо 1(оптимизация). Последний параметр - маска сети. В случае ошибки возвращается -1.

После этого идет функция pcap_setfilter():

	int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
 

1-й аргумент - session handler, 2-й - ссылка на скомпилированную версию выражения.

Код:

	 #include <pcap.h>
 	 ...
 	 pcap_t *handle;		/* Session handle */
 	 char dev[] = "rl0";		/* Device to sniff on */
 	 char errbuf[PCAP_ERRBUF_SIZE];	/* Error string */
 	 struct bpf_program fp;		/* The compiled filter expression */
 	 char filter_exp[] = "port 23";	/* The filter expression */
 	 bpf_u_int32 mask;		/* The netmask of our sniffing device */
 	 bpf_u_int32 net;		/* The IP of our sniffing device */
 
 	 if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
 		 fprintf(stderr, "Can't get netmask for device %s\n", dev);
 		 net = 0;
 		 mask = 0;
 	 }
 	 handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
 	 if (handle == NULL) {
 		 fprintf(stderr, "Couldn't open device %s: %s\n", somedev, errbuf);
 		 return(2);
 	 }
 	 if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
 		 fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
 		 return(2);
 	 }
 	 if (pcap_setfilter(handle, &fp) == -1) {
 		 fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
 		 return(2);
 	 }
 

Этот пример захватывает весь трафик , проходящий через порт 23, в promiscuous mode, на устройстве rl0.

В этом примере функция pcap_lookupnet() в качестве входящего параметра берет имя устройства и возвращает его IP и маску.

The actual sniffing

А теперь поговорим о захвате пакетов.

Есть 2 техники захвата : мы можем либо захватить один пакет за раз , либо группу пакетов в течение какого-то временного цикла. Сначала рассмотрим первый способ , потом второй , для этого будем использовать pcap_next().

Прототип pcap_next():

	u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
 

1-й аргумент - session handler. 2-й - указатель на структуру , хранящую информацию о пакете : время , длина пакета , длина порции или фрагмента . pcap_next() возвращает указатель u_char на пакет.

Пример , показывающий использование pcap_next() для захвата пакета:.

	 #include <pcap.h>
 	 #include <stdio.h>
 
 	 int main(int argc, char *argv[])
 	 {
 		pcap_t *handle;			/* Session handle */
 		char *dev;			/* The device to sniff on */
 		char errbuf[PCAP_ERRBUF_SIZE];	/* Error string */
 		struct bpf_program fp;		/* The compiled filter */
 		char filter_exp[] = "port 23";	/* The filter expression */
 		bpf_u_int32 mask;		/* Our netmask */
 		bpf_u_int32 net;		/* Our IP */
 		struct pcap_pkthdr header;	/* The header that pcap gives us */
 		const u_char *packet;		/* The actual packet */
 
 		/* Define the device */
 		dev = pcap_lookupdev(errbuf);
 		if (dev == NULL) {
 			fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
 			return(2);
 		}
 		/* Find the properties for the device */
 		if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
 			fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf);
 			net = 0;
 			mask = 0;
 		}
 		/* Open the session in promiscuous mode */
 		handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
 		if (handle == NULL) {
 			fprintf(stderr, "Couldn't open device %s: %s\n", somedev, errbuf);
 			return(2);
 		}
 		/* Compile and apply the filter */
 		if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
 			fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
 			return(2);
 		}
 		if (pcap_setfilter(handle, &fp) == -1) {
 			fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
 			return(2);
 		}
 		/* Grab a packet */
 		packet = pcap_next(handle, &header);
 		/* Print its length */
 		printf("Jacked a packet with length of [%d]\n", header.len);
 		/* And close the session */
 		pcap_close(handle);
 		return(0);
 	 }
 

Устройство определяется с помощью pcap_lookupdev() в режиме promiscuous mode. Находится 1-й пакет на 23-м порту (telnet) и выводится размер пакета в байтах. Новая функция - pcap_close() - про нее попозже.

Другая техника захвата посложнее и возможно , более полезная. Чаще вместо pcap_next() используется pcap_loop() или pcap_dispatch(). Для их понимания нужно усвоить , что такое callback function.

Принципиально в Callback functions нет ничего такого особенно нового. Концепция такова : пусть у меня есть программа , которая ожидает событие от какого-то порта. Допустим , мы ждем . когда пользователь нажмет на клавишу. Каждый раз при нажатии клавиши вызывается функция - callback function. Аналогичные функции используются в pcap, но вместо ожидания нажатия они ждут , когда произойдет захват пакета. Называются они pcap_loop() и pcap_dispatch().

Прототип pcap_loop() :

	int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
 

1-й аргумент - session handle. 2-й - указывает , сколько пакетов для pcap_loop() войдет в одну порцию. 3-й - имя самой callback function , без параметров. 4-й - обычно NULL. Функция pcap_dispatch() почти индентична. Разница лишь в том , что pcap_dispatch() получает лишь первую порцию пакетов , в то время как pcap_loop() будет продолжать до тех пор , пока счетчик не обнулится.

Перед тем как показать пример использования pcap_loop(), нужно проверить формат callback function. Нельзя произвольно определять прототип callback's prototype. Прототип нашей callback function:

	void got_packet(u_char *args, const struct pcap_pkthdr *header,
 	    const u_char *packet);
 

Тип у функции - void. 1-й аргумент соответствует последнему аргументу функции pcap_loop() и передается оттуда каждый раз. 2-й аргумент - pcap header. Структура pcap_pkthdr определена в pcap.h :

	struct pcap_pkthdr {
 		struct timeval ts; /* time stamp */
 		bpf_u_int32 caplen; /* length of portion present */
 		bpf_u_int32 len; /* length this packet (off wire) */
 	};
 

Наиболее интересен в got_packet последний аргумент. Это еще один указатель на u_char, и он указывает на первый байт данных пакета, который захвачен функцией pcap_loop().

Он представляет из себя набор структур - Ethernet header, IP header, TCP header. Этот указатель указывает на сериализованную версию этих структур. Чтобы использовать эти структуры , надо использовать преобразование - typecasting.

Определения этих структур :

/* Ethernet addresses are 6 bytes */
 #define ETHER_ADDR_LEN	6
 
 	/* Ethernet header */
 	struct sniff_ethernet {
 		u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
 		u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
 		u_short ether_type; /* IP? ARP? RARP? etc */
 	};
 
 	/* IP header */
 	struct sniff_ip {
 		u_char ip_vhl;		/* version << 4 | header length >> 2 */
 		u_char ip_tos;		/* type of service */
 		u_short ip_len;		/* total length */
 		u_short ip_id;		/* identification */
 		u_short ip_off;		/* fragment offset field */
 	#define IP_RF 0x8000		/* reserved fragment flag */
 	#define IP_DF 0x4000		/* dont fragment flag */
 	#define IP_MF 0x2000		/* more fragments flag */
 	#define IP_OFFMASK 0x1fff	/* mask for fragmenting bits */
 		u_char ip_ttl;		/* time to live */
 		u_char ip_p;		/* protocol */
 		u_short ip_sum;		/* checksum */
 		struct in_addr ip_src,ip_dst; /* source and dest address */
 	};
 	#define IP_HL(ip)		(((ip)->ip_vhl) & 0x0f)
 	#define IP_V(ip)		(((ip)->ip_vhl) >> 4)
 
 	/* TCP header */
 	struct sniff_tcp {
 		u_short th_sport;	/* source port */
 		u_short th_dport;	/* destination port */
 		tcp_seq th_seq;		/* sequence number */
 		tcp_seq th_ack;		/* acknowledgement number */
u_char th_offx2; /* data offset, rsvd */ #define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4) u_char th_flags; #define TH_FIN 0x01 #define TH_SYN 0x02 #define TH_RST 0x04 #define TH_PUSH 0x08 #define TH_ACK 0x10 #define TH_URG 0x20 #define TH_ECE 0x40 #define TH_CWR 0x80 #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) u_short th_win; /* window */ u_short th_sum; /* checksum */ u_short th_urp; /* urgent pointer */ };

Кстати: Тут автор пишет , что на Slackware Linux 8 box (stock kernel 2.2.19) (какая древность :-)) это вообще не компилится. Проблема оказалась в include/features.h. Для разрешения проблемы нужно было определить
#define _BSD_SOURCE 1
Можно также использовать альтернативную TCP header structure, которая лежит тут : here. У меня на 10-й сузе все собралось на ура.

Эти хидеры фигурируют в данных пакета.

Как выудить данные из TCP пакета ?

/* ethernet headers - длина всегда 14 байт */
 #define SIZE_ETHERNET 14
 
 	const struct sniff_ethernet *ethernet; /* The ethernet header */
 	const struct sniff_ip *ip; /* The IP header */
 	const struct sniff_tcp *tcp; /* The TCP header */
 	const char *payload; /* Packet payload */
 
 	u_int size_ip;
 	u_int size_tcp;
 

И теперь магическое преобразование :

	ethernet = (struct sniff_ethernet*)(packet);
 	ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
 	size_ip = IP_HL(ip)*4;
 	if (size_ip < 20) {
 		printf("   * Invalid IP header length: %u bytes\n", size_ip);
 		return;
 	}
 	tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
 	size_tcp = TH_OFF(tcp)*4;
 	if (size_tcp < 20) {
 		printf("   * Invalid TCP header length: %u bytes\n", size_tcp);
 		return;
 	}
 	payload = (u_char *)(packet + SIZE_ETHERNET + size_ip + size_tcp);
 

u_char - это указатель на адрес памяти.

Если значение этого указателя равно X , то адрес структуры sniff_ethernet тоже равно X, отсюда мы можем найти адрес следующей структуры - X плюс длина Ethernet header,равная 14 ,или SIZE_ETHERNET.

Далее аналогично получаем адрес IP header, но он не имеет фиксированной длины; длина равна числу 4-байтных слов. Это число умножаем на 4 и получаем длину следующей структуры . Минимальная длина - 20 байт.

TCP header также имеет переменную длину;его длина - это "data offset" TCP header, и минимум это тоже 20 байт.

Таблица :

Variable Location (in bytes)
sniff_ethernet X
sniff_ip X + SIZE_ETHERNET
sniff_tcp X + SIZE_ETHERNET + {IP header length}
payload X + SIZE_ETHERNET + {IP header length} + {TCP header length}

Структура sniff_ethernet = X. sniff_ip = X + 14 байт, или SIZE_ETHERNET). sniff_tcp = X +14 + (4 * IP header length).

Теперь мы знаем , как установить callback function, вызвать ее, и вычислить атрибуты захваченного пакета.

Исходники лежат тут

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье
Galaran
  большое спасибо за перевод)
2009-08-21 07:31:42
doctor
  отличная статья, спасибо
2011-06-14 16:29:16
дима
  спасибо, то что надо!
2012-06-15 17:05:37
Alatar
  Статья хорошая в качестве вводной, но следует отметить, что код магического преобразования очень условный - 

 он подразумевает, что Вы точно уверены, что это фрейм Ethernet DIX, он содержит именно IP, а он, в свою очередь, - именно TCP.

 Надо помнить, что на самом деле SIZE_ETHERNET тоже не константа, а между эзернетом и IP могут прятаться инкапсулирующие протоколы, 

 например VLAN_TAG. 
2013-08-29 19:02:49