Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Books
  Краткое описание
 Linux
 W. R. Стивенс TCP 
 W. R. Стивенс IPC 
 A.Rubini-J.Corbet 
 K. Bauer 
 Gary V. Vaughan 
 Д Вилер 
 В. Сталлинг 
 Pramode C.E. 
 Steve Pate 
 William Gropp 
 K.A.Robbins 
 С Бекман 
 Р Стивенс 
 Ethereal 
 Cluster 
 Languages
 C
 Perl
 M.Pilgrim 
 А.Фролов 
 Mendel Cooper 
 М Перри 
 Kernel
 C.S. Rodriguez 
 Robert Love 
 Daniel Bovet 
 Д Джеф 
 Максвелл 
 G. Kroah-Hartman 
 B. Hansen 
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...5164 
 Trees...935 
 Максвелл 3...861 
 Go Web ...814 
 William Gropp...796 
 Ethreal 3...779 
 Ethreal 4...766 
 Gary V.Vaughan-> Libtool...765 
 Rodriguez 6...756 
 Steve Pate 1...749 
 Ext4 FS...748 
 Clickhouse...748 
 Ethreal 1...736 
 Secure Programming for Li...721 
 C++ Patterns 3...711 
 Ulrich Drepper...693 
 Assembler...687 
 DevFS...655 
 Стивенс 9...644 
 MySQL & PosgreSQL...622 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Глава 25 : Raw socket

Код данной главы лежит тут

Символьные , или не структурированные сокеты (raw socket) , дают 3 возможности , которых нет в TCP :

  
 	1. Позволяют читать-писать пакеты icmp (ping)
 	2. Можно читать-писать ip-датаграммы   
 	3. Можно написать свой собственный протокол
 
При создании символьных сокетов нужно выполнить следующие шаги :
  
 	1. Вызвать функцию socket со вторым аргументом SOCK_RAW .
 		Например : 	int sockfd = socket(AF_INET , SOCK_RAW , protocol)
 		protocol - одна из констант IPPROTO_xxx , например IPPROTO_ICMP
 	2. Параметр сокета IP_HDRINCL можно установить так :
 		int on = 1;
 		if (setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on)) < 0 ) error ...
 	3. Использование bind необязательно
 	4. использование connect необязательно , т.к. в raw socket нет понятия порта  
 
Вывод на символьных сокетах регулируется следующими правилами :
  
 	1. Стандартный вывод делается с помощью sendto или sendmsg 
 	2. Если параметр IP_HDRINCL не установлен,начальный адрес данных начинается сразу после ip-заголовка
 	3. Если параметр IP_HDRINCL установлен ,  начальный адрес данных начинается с первого байта ip-заголовка 
 	4. Ядро фрагментирует пакеты , превышающие MTU .
 
Ввод регулируется следующими правилами :
  
 	1. Пакеты UDP и TCP никогда не передаются на символьный сокет
 	2. Все ICMP-пакеты передаются на символьный сокет
 	3. Все пакеты с непонятным протоколом передаются на символьный сокет
 
Ядро выбирает из нескольких символьных сокетов нужный по следующему принципу :
  
 	1. Протокол пакета должен совпасть с 3-м аргументом функции socket
 	2. Если сокет создан с помощью bind , ip-адрес в пакете должет совпасть с локальным ip
 	3. Для функции connect аналогично 
 
Реализуем общеизвестную программу ping , которая отличается от общеизвестной наличием всего одного параметра и поддержкой IPV6. Принцип работы ping следующий : посылается эхо-запрос icmp и на него приходит эхо-ответ. Формат ICMP-сообщений :
Здесь код=0 , идентификатор - id процесса ping , порядковый номер - это номер отправляемого пакета , в дополнительных данных - время .

Обзор функций программы ping :
Программа состоит из 2-х частей : одна читает , другая отсылает : Приведем текст хидера ping.h :

  
 //ping/ping.h
 
 #define	BUFSIZE		1500
 
 			/* globals */
 char	 recvbuf[BUFSIZE];
 char	 sendbuf[BUFSIZE];
 
 int		 datalen;			/* #bytes of data, following ICMP header */
 char	*host;
 int		 nsent;				/* add 1 for each sendto() */
 pid_t	 pid;				/* our PID */
 int		 sockfd;
 int		 verbose;
 
 			/* function prototypes */
 void	 proc_v4(char *, ssize_t, struct timeval *);
 void	 proc_v6(char *, ssize_t, struct timeval *);
 void	 send_v4(void);
 void	 send_v6(void);
 void	 readloop(void);
 void	 sig_alrm(int);
 void	 tv_sub(struct timeval *, struct timeval *);
 
 struct proto {
   void	 (*fproc)(char *, ssize_t, struct timeval *);
   void	 (*fsend)(void);
   struct sockaddr  *sasend;	/* sockaddr{} for send, from getaddrinfo */
   struct sockaddr  *sarecv;	/* sockaddr{} for receiving */
   socklen_t	    salen;		/* length of sockaddr{}s */
   int	   	    icmpproto;	/* IPPROTO_xxx value for ICMP */
 } *pr;
 
Структура proto служит для обработки различий между IPV4 и IPV6.

Функция main :

  
 struct proto	proto_v4 = { proc_v4, send_v4, NULL, NULL, 0, IPPROTO_ICMP };
 
 #ifdef	IPV6
 struct proto	proto_v6 = { proc_v6, send_v6, NULL, NULL, 0, IPPROTO_ICMPV6 };
 #endif
 
 int	datalen = 56;		/* data that goes with ICMP echo request */
 
 int main(int argc, char **argv)
 {
 	int				c;
 	struct addrinfo	*ai;
 
 	opterr = 0;		/* don't want getopt() writing to stderr */
 	while ( (c = getopt(argc, argv, "v")) != -1) {
 		switch (c) {
 		case 'v':
 			verbose++;
 			break;
 
 		case '?':
 			err_quit("unrecognized option: %c", c);
 		}
 	}
 
 	if (optind != argc-1)
 		err_quit("usage: ping [ -v ] < hostname>");
 	host = argv[optind];
 
 	pid = getpid();
 	Signal(SIGALRM, sig_alrm);
 
 	ai = Host_serv(host, NULL, 0, 0);
 
 	printf("PING %s (%s): %d data bytes\n", ai->ai_canonname,
 		   Sock_ntop_host(ai->ai_addr, ai->ai_addrlen), datalen);
 
 		/* 4initialize according to protocol */
 	if (ai->ai_family == AF_INET) {
 		pr = &proto_v4;
 #ifdef	IPV6
 	} else if (ai->ai_family == AF_INET6) {
 		pr = &proto_v6;
 		if (IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 *)
 								 ai->ai_addr)->sin6_addr)))
 			err_quit("cannot ping IPv4-mapped IPv6 address");
 #endif
 	} else
 		err_quit("unknown address family %d", ai->ai_family);
 
 	pr->sasend = ai->ai_addr;
 	pr->sarecv = Calloc(1, ai->ai_addrlen);
 	pr->salen = ai->ai_addrlen;
 
 	readloop();
 
 	exit(0);
 }
 
Устанавливаем количество дополнительных данных - 56 байт . IP4-датаграмма будет длиной 84 байта : 56 байт данных , 20 байт ip-заголовка , 8 байт icmp-заголовка. IP6-датаграмма будет длиной 104 байта . Время отправки будет храниться в первых 8 байтах данных .

Символьный сокет создается в функции readloop

  
 void readloop(void)
 {
 	int				size;
 	char			recvbuf[BUFSIZE];
 	socklen_t		len;
 	ssize_t			n;
 	struct timeval	tval;
 
 	sockfd = Socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);
 	setuid(getuid());		/* don't need special permissions any more */
 
 	size = 60 * 1024;		/* OK if setsockopt fails */
 	setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
 
 	sig_alrm(SIGALRM);		/* send first packet */
 
 	for ( ; ; ) {
 		len = pr->salen;
 		n = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, pr->sarecv, &len);
 		if (n < 0) {
 			if (errno == EINTR)
 				continue;
 			else
 				err_sys("recvfrom error");
 		}
 
 		Gettimeofday(&tval, NULL);
 		(*pr->fproc)(recvbuf, n, &tval);
 	}
 }
 
Пытаемся установить размер приемного буфера сокета 60 * 1024 , заведомо большего , чем по умолчанию - тем самым мы уменьшаем вероятность его переполнения . Запускаем обработчик сигнала SIGALRM раз в секунду

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

Функция обработки ICMP4 :

  
 void
 proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv)
 {
 	int				hlen1, icmplen;
 	double			rtt;
 	struct ip		*ip;
 	struct icmp		*icmp;
 	struct timeval	*tvsend;
 
 	ip = (struct ip *) ptr;		/* start of IP header */
 	hlen1 = ip->ip_hl << 2;		/* length of IP header */
 
 	icmp = (struct icmp *) (ptr + hlen1);	/* start of ICMP header */
 	if ( (icmplen = len - hlen1) < 8)
 		err_quit("icmplen (%d) < 8", icmplen);
 
 	if (icmp->icmp_type == ICMP_ECHOREPLY) {
 		if (icmp->icmp_id != pid)
 			return;			/* not a response to our ECHO_REQUEST */
 		if (icmplen < 16)
 			err_quit("icmplen (%d) < 16", icmplen);
 
 		tvsend = (struct timeval *) icmp->icmp_data;
 		tv_sub(tvrecv, tvsend);
 		rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
 
 		printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",
 				icmplen, Sock_ntop_host(pr->sarecv, pr->salen),
 				icmp->icmp_seq, ip->ip_ttl, rtt);
 
 	} else if (verbose) {
 		printf("  %d bytes from %s: type = %d, code = %d\n",
 				icmplen, Sock_ntop_host(pr->sarecv, pr->salen),
 				icmp->icmp_type, icmp->icmp_code);
 	}
 }
 
Чтобы убедиться , что ответ предназначен для нас , проверяем идентификатор , вычисляем RTT .

Обработчик сигнала SIGALRM :

  
 void
 sig_alrm(int signo)
 {
 	(*pr->fsend)();
 
 	alarm(1);
 	return;		/* probably interrupts recvfrom() */
 }
 
При отсылке запроса вычисляется контрольная сумма ICMP по заголовку и всем следующим за ним данным :

  
 void
 send_v4(void)
 {
 	int			len;
 	struct icmp	*icmp;
 
 	icmp = (struct icmp *) sendbuf;
 	icmp->icmp_type = ICMP_ECHO;
 	icmp->icmp_code = 0;
 	icmp->icmp_id = pid;
 	icmp->icmp_seq = nsent++;
 	Gettimeofday((struct timeval *) icmp->icmp_data, NULL);
 
 	len = 8 + datalen;		/* checksum ICMP header and data */
 	icmp->icmp_cksum = 0;
 	icmp->icmp_cksum = in_cksum((u_short *) icmp, len);
 
 	Sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
 }
 
Перед отправкой нужно посчитать контрольную сумму для Интернета , которая является суммой обратных кодов (см. in_cksum).

Теперь напишем свою облегченную версию программы traceroute . Она у меня например выдает такой результат :

  
 # ./traceroute iakovlev.org
 	traceroute to iakovlev.org (62.213.78.144): 30 hops max, 12 data bytes
 	...     
  3   ae0-201.RT.M9.MSK.RU.retn.net (87.245.255.53)  1.782 ms  1.115 ms  1.221 ms
  4   ReTN-Caravan.ge1-4.m9-3.caravan.ru (87.245.255.146)  1.123 ms  1.134 ms  1.100 ms
  5   vlan65.ge1-3.office-1.caravan.ru (217.23.131.150)  1.494 ms  1.519 ms  1.644 ms
  6   iakovlev.org (62.213.78.144)  1.569 ms  1.585 ms  2.215 ms
 	
 Результат кстати совпадает один в один со стандартной утилитой 
 
Она начинает свою работу с отправки udp-датаграммы , которая вынуждает первый же маршрутизатор вернуть icmp-сообщение об ошибке Time exceeded . Затем полю TTL (ограничение пересылок) увеличивается значение до 1 , и посылается следующая udp-датаграмма , которая достигает второго маршрутизатора . Когда udp достигает получателя , необходимо его заставить вернуть icmp-ошибку Port unreachable .

Заголовочный файл trace.h

  
 //traceroute/trace.h
 
 #define	BUFSIZE		1500
 
 struct rec {					/* format of outgoing UDP data */
   u_short	rec_seq;			/* sequence number */
   u_short	rec_ttl;			/* TTL packet left with */
   struct timeval	rec_tv;		/* time packet left */
 };
 
 			/* globals */
 char	 recvbuf[BUFSIZE];
 char	 sendbuf[BUFSIZE];
 
 int		 datalen;			/* #bytes of data, following ICMP header */
 char	*host;
 u_short	 sport, dport;
 int		 nsent;				/* add 1 for each sendto() */
 pid_t	 pid;				/* our PID */
 int		 probe, nprobes;
 int		 sendfd, recvfd;	/* send on UDP sock, read on raw ICMP sock */
 int		 ttl, max_ttl;
 int		 verbose;
 
 			/* function prototypes */
 char	*icmpcode_v4(int);
 char	*icmpcode_v6(int);
 int		 recv_v4(int, struct timeval *);
 int		 recv_v6(int, struct timeval *);
 void	 sig_alrm(int);
 void	 traceloop(void);
 void	 tv_sub(struct timeval *, struct timeval *);
 
 struct proto {
   char	*(*icmpcode)(int);
   int	 (*recv)(int, struct timeval *);
   struct sockaddr  *sasend;	/* sockaddr{} for send, from getaddrinfo */
   struct sockaddr  *sarecv;	/* sockaddr{} for receiving */
   struct sockaddr  *salast;	/* last sockaddr{} for receiving */
   struct sockaddr  *sabind;	/* sockaddr{} for binding source port */
   socklen_t   		salen;	/* length of sockaddr{}s */
   int			icmpproto;	/* IPPROTO_xxx value for ICMP */
   int	   ttllevel;		/* setsockopt() level to set TTL */
   int	   ttloptname;		/* setsockopt() name to set TTL */
 } *pr;
 
Функция traceloop - основной цикл обработки:
  
 void
 traceloop(void)
 {
 	int					seq, code, done;
 	double				rtt;
 	struct rec			*rec;
 	struct timeval		tvrecv;
 
 	recvfd = Socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);
 	setuid(getuid());		/* don't need special permissions any more */
 
 	sendfd = Socket(pr->sasend->sa_family, SOCK_DGRAM, 0);
 
 	pr->sabind->sa_family = pr->sasend->sa_family;
 	sport = (getpid() & 0xffff) | 0x8000;	/* our source UDP port# */
 	sock_set_port(pr->sabind, pr->salen, htons(sport));
 	Bind(sendfd, pr->sabind, pr->salen);
 
 	sig_alrm(SIGALRM);
 
 	seq = 0;
 	done = 0;
 	for (ttl = 1; ttl <= max_ttl && done == 0; ttl++) {
 		Setsockopt(sendfd, pr->ttllevel, pr->ttloptname, &ttl, sizeof(int));
 		bzero(pr->salast, pr->salen);
 
 		printf("%2d  ", ttl);
 		fflush(stdout);
 
 		for (probe = 0; probe < nprobes; probe++) {
 			rec = (struct rec *) sendbuf;
 			rec->rec_seq = ++seq;
 			rec->rec_ttl = ttl;
 			Gettimeofday(&rec->rec_tv, NULL);
 
 			sock_set_port(pr->sasend, pr->salen, htons(dport + seq));
 			Sendto(sendfd, sendbuf, datalen, 0, pr->sasend, pr->salen);
 
 			if ( (code = (*pr->recv)(seq, &tvrecv)) == -3)
 				printf(" *");		/* timeout, no reply */
 			else {
 				char	str[NI_MAXHOST];
 
 				if (sock_cmp_addr(pr->sarecv, pr->salast, pr->salen) != 0) {
 					if (getnameinfo(pr->sarecv, pr->salen, str, sizeof(str),
 									NULL, 0, 0) == 0)
 						printf(" %s (%s)", str,
 								Sock_ntop_host(pr->sarecv, pr->salen));
 					else
 						printf(" %s",
 								Sock_ntop_host(pr->sarecv, pr->salen));
 					memcpy(pr->salast, pr->sarecv, pr->salen);
 				}
 				tv_sub(&tvrecv, &rec->rec_tv);
 				rtt = tvrecv.tv_sec * 1000.0 + tvrecv.tv_usec / 1000.0;
 				printf("  %.3f ms", rtt);
 
 				if (code == -1)		/* port unreachable; at destination */
 					done++;
 				else if (code >= 0)
 					printf(" (ICMP %s)", (*pr->icmpcode)(code));
 			}
 			fflush(stdout);
 		}
 		printf("\n");
 	}
 }
 
Нам нужны 2 сокета : символьный , на который мы читаем все вернувшиеся icmp-сообщения , и udp , с которого мы посылаем пробные пакеты .

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

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

 Автор  Комментарий к данной статье