Универсальный есно-север под Linux

В данной статье мы рассмотрим разработку универсального эхо-сервера. Однако вначале стоит сказать, чем же наш эхо-сервер, будет отличаться от обычного эхо сервера. Как известно, обыкновенный эхо-сервер работает по следующему алгоритму: прослушивает определенный TCP или UDP порт и как только на этот порт приходят какие либо данные, он сразу пересылает их обратно отправителю. Таким образом эхо-сервер работает исключительно с данными. Однако иногда возникает задача перенаправлять отправителю не только данные но также и служебную информацию, т.е. все полученные пакеты, причем пакеты пришедшие не на определенный порт а на любой. Такая задача может возникнуть, например, при тестировании сетеобразующего оборудования (интеллектуальных маршрутизаторов ) или файрволов на правильность прохождения пакетов ( файрволинга ).

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

Писать наш эхо-сервер будем на языке ANSI C под ОС Linux . При разработке использовалась использовалась ОС Linux Mandrake 10.0. Программа не требует установки никаких дополнительных библиотек, ткак как написана на стандартных типах сокетов в ОС Linux . При разработки были использованы следующие типы сокетов :

Ниже приведен код простейшего эхо-сервера:

#include <stdio.h>

#include <sys/socket.h>

#include <resolv.h>

#include <arpa/inet.h>

#include <errno.h>

#define MAXBUF		1024

int main(int Count, char **Strings)

{   int sockfd;

	struct sockaddr_in self;

	char buffer[MAXBUF];

    if (Count==1) {

	    printf("Не верный формат вызова!!!\n");

	    printf("echo-server [port]\n");

	    exit(1);

    }

/*---Создание сокета TCP---*/

    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )

	{

		perror("Ошибка создания сокета");

		exit(errno);

	}



	/*---Задание адреса и порта серверу---*/

	bzero(&self, sizeof(self));

	self.sin_family = AF_INET;

	self.sin_port = htons((int) Strings[1]);

	self.sin_addr.s_addr = INADDR_ANY;



	/*---Связь сокета с портом---*/

    if ( bind(sockfd, (struct sockaddr*)&self, sizeof(self)) != 0 )

	{

		perror("ошибка связи сокета");

		exit(errno);

	}



	/*---включение прослушивания---*/

	if ( listen(sockfd, 20) != 0 )

	{

		perror("ошибка включения прослушивания");

		exit(errno);

	}

	printf("Выход из программы по нажатию клавиши ENTER ");

	/*---Запускаем цикл до нажатия клавиши ENTER... ---*/

	while (1)

	{       char i;

		int clientfd;

		struct sockaddr_in client_addr;

		int addrlen=sizeof(client_addr);

		printf("---\n");

		/*---проверка соединения (создание канала данных)---*/

		clientfd = accept(sockfd, (struct sockaddr*)&client_addr,&addrlen);

		printf("%s:%d connected\n", inet_ntoa(client_addr.sin_addr),

                ntohs(client_addr.sin_port));



		/*---отправка любого сообщения назад---*/

		send(clientfd, buffer, recv(clientfd, buffer, MAXBUF, 0), 0);



		/*---завершение соединения---*/

		close(clientfd);

		//необходимо сделать???

		scanf("%c",i);

		if (i=='\r') {

			printf("Выход\n");

			break;

		}

	}

	printf("Сервер остановлен!!!\n");

	/*---Закрытие сокета---*/

	close(sockfd);

	return 0;

}

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

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

Определять параметры сетевого интерфейса и переключать его режимы будет функция getifconf (). Прототип данной функции выглядит следующим образом:

int getifconf (__u8 *, struct ifparam *, int ) 

Функция принимает три параметра:

Создавать пакетный сокет будет функция getsock_recv ():

int getsock_recv ( int ) 

Параметром функции является индекс сетевого интерфейса, к которому будет привязан сокет. Далее переводим сетевой интерфейс в режим прослушивания. Для этого получаем значения флагов текущего режима:

if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) 

   {

	 perror("ioctl SIOCGIFFLAGS");

	 close(fd);

	 return -1;

   }

В зависимости от значения третьего параметра функции, устанавливаем или снимаем флаг неразборчивого режима:

    if(mode) ifr.ifr_flags |= IFF_PROMISC;

    else ifr.ifr_flags &= ~(IFF_PROMISC);

Устанавливаем новое значение флагов интерфейса:

 if(ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) 

    {

	  perror("ioctl SIOCSIFFLAGS");

	  close(fd);

	  return (-1);

    }

Далее нам необходимо создать пакетный сокет, который будет принимать пакеты из сети на канальном уровне (кадры Ethernet), так как нам необходимо обрабатывать не только IP-адреса, но и MAC-адреса (подробнее об этом будет рассказано ниже). При работе с пакетными сокетами для хранения адресной информации сетевого интерфейса вместо структуры sockaddr_in используется структура sockaddr_ll, которая обьявлена в файле linux/if_packet.h

    

    struct sockaddr_ll s_ll;

    /*создаем пакетный сокет*/

    sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

    if(sd < 0) return -1;

    memset((void *)&s_ll, 0, sizeof(struct sockaddr_ll));

Также необходимо заполнить поля адресной структуры:


    s_ll.sll_family = PF_PACKET; // тип сокета

    s_ll.sll_protocol = htons(ETH_P_ALL); // тип принимаемого протокола

    s_ll.sll_ifindex = index; // индекс сетевого интерфейса

Если у вас несколько сетевых интерфейсов, то вам необходимо привязать сокет к одному из них, однако если вы хотите перехватывать и перенаправлять пакеты со всех сетевых интерфейсов, то вам привязку сокета осуществлять не нужно. Привязка сокета к интерфейсу осуществляется функцией:

   bind(sd,(struct sockaddr *)&s_ll,sizeof(struct sockaddr_ll))   

Все подготовительные этапы пройдены и теперь нам необходимо написать цикл приема пакетов и перенаправления их. Однако если мы сейчас напишем данный цикл, то у нас возникнет следующая проблема. При приеме любого пакета наш эхо-сервер меняет IP-адреса отправителя и получателя и отправляет пакет обратно в сеть. Все как и должно быть, однако здесь не все так просто… Тот пакет, который наш эхо-сервер отправил в сеть (с измененными IP-адресами), будет снова перехвачен самим эхо-сервером и опять произойдет смена IP-адресов о отправка пакета в сеть и т.д. до бесконечности, т.е. происходит зацикливание эхо-сервера, и генерируется лавинообразный поток пакетов как отправителю так и получателю первого принятого пакета. Для того чтобы отличать пакеты отправленные эхо-сервером и не перенаправлять их снова нам необходимо воспользоваться MAC-адресами в пакете, именно поэтому мы создали пакетный сокет, а не обычный RAW - сокет . Таким образом при захвате пакета нам необходимо проверять MAC-адрес отправителя пакета и если это собственный MAC-адрес (так как MAC-адреса в пакетах формируются автоматически), то этот пакет просто игнорировать и не отправлять назад в сеть.

    

  for(;;) {



        memset(buff, 0, ETH_FRAME_LEN); 

        rec = recvfrom(eth0_if, (char*)buff, ifp.mtu + 18, 0, NULL, NULL); 

	 if(rec < 0 || rec >ETH_FRAME_LEN) {

	               perror("recvfrom: ");

		       return -1;        

	};    

	  //выделяем память под IP пакет (без заголовка Ethernet)

	  memset(buff_send,0,rec-14);

      memcpy((void *)&buff_send,buff+14,rec-14);

	   

	 //выделение заголовков протоколов

       memcpy((void *)ð, buff, ETH_HLEN);

       memcpy((void *)&ip, buff + ETH_HLEN, sizeof(struct iphdr));

       memcpy((void *)&ip_send, buff_send, sizeof(struct iphdr));

	     

       if((ip.version) != 4) continue;

       memcpy((void *)&tcp, buff + ETH_HLEN+ ip.ihl * 4, 

	          sizeof(struct tcphdr));

       memcpy((void *)&tcp_send, buff_send + ip_send.ihl * 4, 

	          sizeof(struct tcphdr));

                                                                

 

/* 

*Проверяем  MAC-адрес отправителя если это собственный MAC-адрес,

* то пакет заново не перенаправлям

*/ 

       if ((eth.h_source[0]==my_mac[0]) && (eth.h_source[1]==my_mac[1]) && 

           (eth.h_source[2]==my_mac[2]) && (eth.h_source[3]==my_mac[3]) && 

           (eth.h_source[4]==my_mac[4]) && (eth.h_source[5]==my_mac[5]))

       {

	

	 continue;

       }

       

/*

 *Меняем местами IP-адреса  получателя и отправителя 

 */

        ip_send.saddr=ip.daddr;

        ip_send.daddr=ip.saddr;

			      

	//себе пакеты не перенаправляем

	if (ip_send.daddr==ifp.ip) {

		//printf("Себе пакеты не перенаправляем\n");

                           continue;

		};		

	        //отчет о принятом пакете

if (flag)

fprintf(fp,"Принят пакет: \t%d.%d.%d.%d\t%d.%d.%d.%d\n ",buff_send[12],

        buff_send[13],buff_send[14],buff_send[15],buff_send[16],

		buff_send[17],buff_send[18],buff_send[19]);

	else

printf("Принят пакет: \t%d.%d.%d.%d\t%d.%d.%d.%d\n ",buff_send[12],

       buff_send[13],buff_send[14],buff_send[15],

	   buff_send[16],buff_send[17],buff_send[18],buff_send[19]);

			        

	//выделение и изменеие IP адреса в принятом пакете

	ip1 = buff_send[12];	

              ip2 = buff_send[13];

              ip3 = buff_send[14];

 	ip4 = buff_send[15];



	buff_send[12] = buff_send[16];

	buff_send[13] = buff_send[17];

	buff_send[14] = buff_send[18];

	buff_send[15] = buff_send[19];



	buff_send[16] = ip1;

	buff_send[17] = ip2;

	buff_send[18] = ip3;

	buff_send[19] = ip4;

	//задание адреса назначения пакета

	inet_aton(inet_ntoa(ip_send.daddr),&addr.sin_addr);

        addr.sin_family=AF_INET;

	//создание сокета

	send_socket = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);

	//отправка echo-пакета	

if ((rec<0) &&(rec>ETH_FRAME_LEN)) {

    	res=sendto(send_socket,buff_send,rec-14,0,

		          (struct sockaddr *)&addr,sizeof(addr));

if (res==0)

	{

if (!flag) perror("Ошибка отправки пакета:\n ");

  	} else {

if (flag) fprintf(fp,"Отправлен пакет: \t%d.%d.%d.%d\t%d.%d.%d.%d \n",

                  buff_send[12],buff_send[13],buff_send[14],

				  buff_send[15],buff_send[16],

				  buff_send[17],buff_send[18],buff_send[19]);

	else				

printf("Отправлен пакет: \t%d.%d.%d.%d\t%d.%d.%d.%d\n ",buff_send[12],

       buff_send[13],buff_send[14],buff_send[15],buff_send[16],

	   buff_send[17],buff_send[18],buff_send[19]);

	}

	} ;

	//очистка сокета

	close(send_socket);

}

Вот и все. Универсальный эхо-сервер практически готов. Полный его код с подробными комментариями можно скачать здесь.

Все вопросы, а также замеченные ошибки и неточности пишите автору.