diff -ruN dhcp-3.0.5/relay/dhcrelay.c dhcp-3.0.5.patched/relay/dhcrelay.c
--- dhcp-3.0.5/relay/dhcrelay.c	2006-04-28 01:38:30.000000000 +0400
+++ dhcp-3.0.5.patched/relay/dhcrelay.c	2008-11-13 13:54:02.000000000 +0300
@@ -40,6 +40,47 @@
 #include "dhcpd.h"
 #include "version.h"
 
+
+/* START PATCH CODE */
+#include "relay/rad2dhcp.h"
+
+XID_NODE * xid_list_top = 0;					/* Указатель на начало списка кэша DHCP */
+XID_NODE * xid_list_ptr = 0;					/* указатель на текущий элемент списка кэша DHCP */
+struct counter_node * counter = 0;				/* Указатель на нулевой элемент массива-счётчика частоты запросов */
+uint16_t counter_end = 0;						/* Содержит индекс последующего элемента за последним
+												   используемым элементом счётчика */
+uint16_t counter_size = MIN_COUNTER_SIZE;       /* Указывает текущий размер массива счётчика частоты запросов */
+time_t xid_timeout = DEFAULT_XID_TIMEOUT;       /* Время в течение которого хранятся узлы кэша */
+uint16_t radius_port = RADIUS_PORT;				/* Порт на который релей отправляет RADIUS запросы. По умолчанию равен 1812 */
+float max_qps = MAX_DHCP_QPS;							/* Максимально допустимая частота отправки DHCP запросов
+														   всеми клиентами суммарно */
+float max_qps_from_host = MAX_QPS_FROM_HOST;			/* Максимально допустимая частота 
+														   отправки запросов DHCP одним клиентом */
+uint16_t cache_list_len = MAX_CACHE_LIST_LEN;			/* Максимальная длина кэша DHCP. Переменная нужна для возможности 
+															задания этого параметра из командной строки. 
+															В данной версии - не реализовано */
+int16_t dhcp_cache_len = 0;								/* Текущая длина кэша */
+time_t normal_clients_counter[QUERY_TIME_RING_LEN];		/* Массив используемый как кольцевой буфер для
+														   подсчёта частоты запросов в секунду */
+uint16_t ring_ptr;						/* Указатель на последний элемент кольцевого буфера для подсчёта частоты DHCP запросов */
+struct sockaddr_in * servers;			/* Указатель на список массив хранящий адреса RADIUS серверов */
+int servers_count;						/* Число доступных RADIUS серверов */
+int unfinished_requests;				/* Переменная хранящая число необработанных RADIUS-сервером запросов. 
+											Т.е. число DHCP запросов для которых не получены ответы RADIUS сервера.
+											Переменная используется для обнаружения "падения" RADIUS сервера и 
+											переключения на резервный сервер, если таковой указан */
+int server_index = BASIC_SERVER_INDEX;	/* Индекс массива серверов, указывающий на RADIUS сервер 
+                                            к которому в текущий момент времени пересылаются запросы */
+int time_to_restore_primary_server_index;	/* Время (в секундах) через которое производится попытка переключиться
+												на первичный RADIUS сервера, если на данный момент осуществлено 
+												переключение на резервный */
+time_t primary_server_down_time;	/* Фиксируется время (секунд), в которое "упал" PRIMARY RADIUS сервер */
+int updating_arp_cache_perm;		/* Указывает - обновлять ли ARP кэш при получении RADIUS сообщений с cached == 0 */
+int arp_cache_perm_len = DEF_ARP_CACHE_PERM_LEN;	/* Длина массива хранящего кэш статических (флаг PERM) ARP записей */
+int arp_entry_count;								/* Число зафиксированных ARP в текущий момент времени */
+struct arp_entry * arp_cache_perm;					/* Указатель на начало динамического массива содержащего ARP записи */
+/* END PATCH CODE */
+
 static void usage PROTO ((void));
 
 TIME default_lease_time = 43200; /* 12 hours... */
@@ -92,12 +133,19 @@
 u_int16_t local_port;
 u_int16_t remote_port;
 
+/* START PATCH COMMENT */
+/*
 struct server_list {
 	struct server_list *next;
 	struct sockaddr_in to;
 } *servers;
+*/
+/* END PATCH COMMENT */
 
 static char copyright [] = "Copyright 2004-2006 Internet Systems Consortium.";
+/* START PATCH CODE */
+const static char patched[] = "Patched by Chebotarev Roman. Patch version " PATCH_VERSION;
+/* END PATCH CODE */
 static char arr [] = "All rights reserved.";
 static char message [] = "Internet Systems Consortium DHCP Relay Agent";
 static char url [] = "For info, please visit http://www.isc.org/sw/dhcp/";
@@ -108,7 +156,11 @@
 {
 	int i;
 	struct servent *ent;
-	struct server_list *sp = (struct server_list *)0;
+    int serv_arr_size = DEFAULT_SERV_ARR_SIZE;
+/* START PATCH COMMENT */
+/*	int serv_pointer = 0;*/
+/*	struct server_list *sp = (struct server_list *)0;*/   
+/* END PATCH COMMENT */
 	int no_daemon = 0;
 	int quiet = 0;
 	isc_result_t status;
@@ -144,6 +196,12 @@
 	/* Set up the OMAPI wrappers for the interface object. */
 	interface_setup ();
 
+/* START PATCH CODE */
+    /* Инициализируем память под массив серверов */
+    servers = calloc(serv_arr_size, sizeof(struct sockaddr_in));
+    if(!servers)
+        log_fatal("FATAL: Can't allocate memory for servers list.");
+/* END PATCH CODE */
 	for (i = 1; i < argc; i++) {
 		if (!strcmp (argv [i], "-p")) {
 			if (++i == argc)
@@ -185,7 +243,86 @@
 			if (++i == argc)
 				usage ();
 			dhcp_max_agent_option_packet_length = atoi (argv [i]);
-		} else if (!strcmp (argv [i], "-m")) {
+		} 
+        /* START PATCH CODE */
+		else if (!strcmp (argv [i], "-u"))	/* Если задан этот параметр - обновлять ARP таблицу хоста на котором */
+		{									/* запущен релей */
+			updating_arp_cache_perm = 1;
+		}else if( !strcmp(argv[i], "-h") )
+		{
+			patch_help();
+		}else if (!strcmp(argv[i], "-s"))	/* Shared secret для доступа к RADIUS серверу */
+        {
+            if(++i == argc) 
+				usage();
+            if(strlen(argv[i]) > 16)		/* INCOMPLETE: необходимо обрабатывать бОльшие длины */
+			{ 
+				log_fatal ("'Shared secret' length must be <= 16");
+				exit(0);
+			}
+            strncpy(rad_secret, argv[i], strlen(argv[i]));
+        }
+        else if (!strcmp(argv[i], "-P"))	/* "Пароль" авторизации DHCP пользователей. Для всех пользователей одинаков */
+        {									/* и потому - задаётся через командную строку. */
+            if(++i == argc)					/* INCOMPLETE: необходимо обрабатывать бОльшие длины */
+				usage();
+            if(strlen(argv[i]) > 16)
+			{
+				log_fatal ("Radius password length must be <= 16");
+				exit(0);
+			}
+            strncpy(rad_users_passwd, argv[i], strlen(argv[i]));
+        }
+        else if (!strcmp(argv[i], "-t"))	/* Время в течение которого хранится узел DHCP кэша, указывается в секундах */
+        {
+            if(++i == argc) 
+				usage();
+            if(!(xid_timeout = atoi(argv[i])))
+            {
+                log_fatal ("Invalid cache timeout value");
+                exit(0);
+            }
+        }
+        else if (!strcmp(argv[i], "-R"))	/* Номер порта RADIUS сервера */
+        {
+            if(++i == argc) 
+				usage();
+            if(! (radius_port = atoi(argv[i])))
+            {
+                log_fatal ("Invalid RADIUS port value");
+                exit(0);
+            }
+        }
+        else if( !strcmp(argv[i], "-f"))	/* Максимально допустимая частота DHCP запросов от отдельного хоста */
+        {
+            if(++i == argc) usage();
+            if(! (max_qps_from_host = atof(argv[i])))
+            {
+                log_fatal ("Invalid 'maximum queries per second from host' value");
+                exit(0);
+            }
+        }
+        else if( !strcmp(argv[i], "-Q"))	/* Максимальная частота всех поступающих DHCP запросов */
+        {
+            if(++i == argc) usage();
+            if(! (max_qps = atof(argv[i])))
+            {
+                log_fatal ("Invalid \"maximum queries per second\" value");
+                exit(0);
+            }
+        }
+        else if( !strcmp(argv[i], "-T"))	/* Время через которое производится попытка переключиться */
+        {									/* на основной сервер, в случае использования резервного */
+            if(++i == argc) usage();
+            if(! (time_to_restore_primary_server_index = atoi(argv[i])))
+            {
+                log_fatal ("Invalid \"time to restore primary index\" value");
+                exit(0);
+            }
+        }
+        /* p d D i q a s c A m P R f t Q T u h*/
+        /* END PATCH CODE */
+        else if (!strcmp (argv [i], "-m")) {
 			if (++i == argc)
 				usage ();
 			if (!strcasecmp (argv [i], "append")) {
@@ -220,6 +357,8 @@
 					       he -> h_addr_list [0]);
 				}
 			}
+/* START PATCH COMMENT */
+            /*
 			if (iap) {
 				sp = ((struct server_list *)
 				      dmalloc (sizeof *sp, MDL));
@@ -230,16 +369,54 @@
 				memcpy (&sp -> to.sin_addr,
 					iap, sizeof *iap);
 			}
+            */
+/* END PATCH COMMENT */
+/* START PATCH CODE */
+			if(iap)
+			{
+				if(servers_count == serv_arr_size)	/* Если необходимо, то увеличиваем размер */
+				{									/* массива хранящего список RADIUS серверов */
+					serv_arr_size *= 2;
+					log_info("INFO: Realloc memory for servers array. Set new size = %d", serv_arr_size);
+					if( !(servers = realloc(servers, sizeof(struct sockaddr_in) * serv_arr_size) ) )
+						log_fatal("FATAL: Failed realloc memory for servers arrya.");
+				}
+				memcpy(&servers[servers_count++].sin_addr, iap, sizeof(*iap));
+			}
+/* END PATCH CODE */
  		}
 	}
 
+/* START PATCH CODE */
+	/* Проверка правильности задания параметров */
+    if(!strlen(rad_secret) || !strlen(rad_users_passwd)) 
+                                                        usage();
+    bzero(normal_clients_counter, sizeof(normal_clients_counter)); /* Обнуляем кольцевой буфер */
+    /* Создаём динамический массив для счётчика запросов */
+    counter = malloc(sizeof(struct counter_node) * MIN_COUNTER_SIZE);
+    
+	if(!counter)
+        log_fatal("FATAL: malloc error for counter");
+
+    bzero(counter, MIN_COUNTER_SIZE);
+	if(updating_arp_cache_perm)
+	{
+		if( !(arp_cache_perm = calloc(arp_cache_perm_len, sizeof(struct arp_entry))) )
+			log_fatal("FATAL: Can't allocate memory for copy ARP cache.");
+		if(! get_arp_cache_perm())
+			log_fatal("FATAL: Can't get ARP cache. Try launch dhcrelay without updating ARP cache option.\n");
+	}
+/* END PATCH CODE */
+    
 	if ((s = getenv ("PATH_DHCRELAY_PID"))) {
 		path_dhcrelay_pid = s;
 	}
 
 	if (!quiet) {
 		log_info ("%s %s", message, DHCP_VERSION);
-		log_info (copyright);
+/* START PATCH CODE */
+        log_info (patched);
+/* END PATCH CODE */
 		log_info (arr);
 		log_info (url);
 	} else {
@@ -259,11 +436,22 @@
 	remote_port = htons (ntohs (local_port) + 1);
   
 	/* We need at least one server. */
-	if (!sp) {
+/* START PATCH COMMENT */
+	/* Отключаем дефолтную проверку наличия списка DHCP серверов */
+/*
+    if (!sp) {
 		usage ();
 	}
-
-	/* Set up the server sockaddrs. */
+*/
+/* END PATCH COMMENT */
+/* START PATCH CODE */
+	/* И включаем проверку наличия списка RADIUS-серверов */
+    if(!servers_count){
+        usage ();
+    }
+/* END PATCH CODE */
+/* START PATCH COMMENT */
+    /* Set up the server sockaddrs. *//*
 	for (sp = servers; sp; sp = sp -> next) {
 		sp -> to.sin_port = local_port;
 		sp -> to.sin_family = AF_INET;
@@ -271,7 +459,19 @@
 		sp -> to.sin_len = sizeof sp -> to;
 #endif
 	}
-
+*/
+/* END PATCH COMMENT */
+/* START PATCH CODE */
+	/* Правильно заполняем структуры адресов серверов */
+    for(i = 0; i < servers_count; ++i)
+    {
+        servers[i].sin_port = local_port;
+        servers[i].sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+		servers[i].sin_len = sizeof(servers[i]);
+#endif
+    }
+/* END PATCH CODE */
 	/* Get the current time... */
 	GET_TIME (&cur_time);
 
@@ -316,6 +516,15 @@
 		pid = setsid ();
 	}
 
+    /* START PATCH CODE */
+	/* Установка обработчиков сигналов */
+    if(signal(SIGSTAT, print_stat) == SIG_ERR)							/* Для вывода статистики */
+        log_error("ERROR: signal() for handling SIGSTAT failed. Runtime statistic not available.");
+    if(signal(SIGNAL_SET_PRI_SERVER, set_default_server) == SIG_ERR)	/* Для принудительного включения первичного */
+        log_error("ERROR: signal() for handling SIGNAL_SET_PRI_SERVER failed.");	/* RADIUS-сервера */
+    if(signal(SIGNAL_CHANGE_SERV, change_server) == SIG_ERR)			/* Для принудительной смены RADIUS сервера */
+        log_error("ERROR: signal() for handling SIGNAL_CHANGE_SERV failed.");	/* на следующий по списку */
+    /* END PATCH CODE */
 	/* Start dispatching packets and timeouts... */
 	dispatch ();
 
@@ -323,19 +532,1587 @@
 	return 0;
 }
 
-void relay (ip, packet, length, from_port, from, hfrom)
-	struct interface_info *ip;
-	struct dhcp_packet *packet;
-	unsigned length;
-	unsigned int from_port;
-	struct iaddr from;
-	struct hardware *hfrom;
+//**************************************************************************
+/* START PATCH CODE */
+/* 
+	Функция удаляющая кэшированные xid узлы при изменении соответствия IP - MAC клиента.
+	Такое возможно в случае смены MAC-адреса клиента: старому IP адресу будет соответстовать
+	новый MAC адрес, старая информация из кэша должна быть удалёна.
+*/
+int delete_xid_if_mac_not_equal(uint32_t ip, uint8_t * mac)
 {
-	struct server_list *sp;
-	struct sockaddr_in to;
-	struct interface_info *out;
-	struct hardware hto, *htop;
+	XID_NODE * xid_ptr = xid_list_top;
+	while(xid_ptr)
+	{
+		if( (xid_ptr -> out_packet.yiaddr.s_addr == ip) &&
+			memcmp(mac, xid_ptr -> out_packet.chaddr, MAC_ADDR_LEN)
+		  )
+		{
+			xid_ptr = delete_xid(xid_ptr);
+		}
+		else 
+			xid_ptr = xid_ptr -> next;
+	}
+	return 1;
+}
+
+/* 
+	Получаем ARP-кэш статически привязанных MAC адресов из ядра ОС.
+	ДАННЫЙ ВАРИАНТ ПОДХОДИТ ТОЛЬКО ДЛЯ LINUX!!! 
+	INCOMPLETE: Необходимо разработать версию под *BSD
+*/
+int get_arp_cache_perm(void)
+{
+	
+    int type, flags, num;
+    char ip[16];
+    char string[256];
+    char mask[20];
+    char device[20];
+	unsigned char hw[MAC_ADDR_LEN];
+    FILE * proc_fd = fopen(PROC_ARP, "r");
+    if(!proc_fd)
+		log_fatal("FATAL: Can't open system ARP-table in: %s", PROC_ARP);
+	arp_entry_count = 0;
+    bzero(string, sizeof(string));
+    bzero(ip, sizeof(ip));
+    while(fgets(string, sizeof(string), proc_fd))
+    {
+		if(arp_entry_count == arp_cache_perm_len)
+		{
+			arp_cache_perm_len *= 2;
+			log_info("INFO: Reallocating memory for ARP cache. New size is: %d", arp_cache_perm_len);
+			arp_cache_perm = realloc(arp_cache_perm, arp_cache_perm_len * sizeof(struct arp_entry));
+			if(!arp_cache_perm)
+				return 0;
+		}
+		num = sscanf(string, "%s 0x%x 0x%x %X:%X:%X:%X:%X:%X %100s %100s\n",
+						ip, &type, &flags, 
+						&hw[0], &hw[1], &hw[2], &hw[3], &hw[4], &hw[5], 
+						mask, device);
+        if(num < 4)
+            continue;
+        if(flags & ATF_PERM)
+		{
+			if(!inet_aton(ip, &arp_cache_perm[arp_entry_count].ip))
+			{
+				log_error("ERROR: Can't convert IP from ARP cache to binary format.");
+				continue;
+			}
+			memcpy(arp_cache_perm[arp_entry_count].mac, hw, MAC_ADDR_LEN);
+			++ arp_entry_count;
+		}
+    }
+	return 1;
+}
+
+/*
+	Функция обновляющая ARP таблицу ОС согласно информации полученной с RADIUS сервера.
+	Выполняется при смене физического адреса клиента в базе RADIUS.
+*/
+int update_arp_cache_perm(uint32_t ip, unsigned char * mac)
+{
+	int i;
+	int ip_found = 0;
+	/* Обновляем информацию в собственной ARP таблице dhcrelay */
+	for(i = 0; i < arp_entry_count; ++i)
+	{
+		if(arp_cache_perm[i].ip.s_addr == ip)
+		{
+			if(!memcmp(arp_cache_perm[i].mac, mac, MAC_ADDR_LEN))	/* Если переданный и найденный MAC адреса равны, */
+				return 0;											/* то выходим, т.к. нечего обновлять */
+			ip_found = 1;
+			break;
+		}
+	}
+	if(!ip_found)	/* Если IP адрес не найден в текущей ARP таблице,  */
+	{				/* то добавляем его в таблицу */
+		if(arp_entry_count == arp_cache_perm_len)	/* Если ARP таблица dhcrelay заполнена на 100%, */
+		{											/* выделяем для неё дополнительную память */
+			arp_cache_perm_len *= 2;
+			arp_cache_perm = realloc(arp_cache_perm, arp_cache_perm_len * sizeof(struct arp_entry));
+			if(!arp_cache_perm)
+				return -1;
+		}
+		arp_cache_perm[i].ip.s_addr = ip;	/* Заполняем поле IP адрес в новой строке ARP таблицы dhcrelay */
+		++ arp_entry_count;
+	}
+	memcpy(arp_cache_perm[i].mac, mac, MAC_ADDR_LEN);	/* Копируем новый MAC адрес в ARP таблицу dhcrelay */
+
+	/* Обновляем ARP таблицу ядра ОС */
+	struct arpreq req;
+    uint32_t sockfd;
+    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+    {
+		log_error("ERROR: Create socket for update ARP cache failed.");
+		return -1;
+    }
+    bzero((char *) &req, sizeof(req));
+    req.arp_pa.sa_family = PF_INET;
+    memcpy((char *)&req.arp_pa.sa_data + 2, &ip, sizeof(ip));
+    req.arp_ha.sa_family = PF_LOCAL;
+    memcpy(req.arp_ha.sa_data, mac, MAC_ADDR_LEN);
+    req.arp_flags = ATF_PERM | ATF_COM;
+    if (ioctl(sockfd, SIOCSARP, &req) < 0)
+    {
+		log_error("ERROR: ioctl for set ARP entry failed.");
+        return -1;
+    }
+
+	return 1;
+}
+/*
+	Функция выполняет подсчёт общего усреднённого числа DHCP запросов от всех клиентов 
+	за единицу времени TIME_DELTA, а так же - подсчёт усреднённого числа запросов
+	от конкретного хоста указанного в mac_addr. Любой из параметров 
+	функции может быть равен нулю. Вызов функции с обоими параметрами равными нулю
+	можно применить	для принудительного уменьшения размера счётчика, 
+	без внешнего запроса от DHCP клиента - если в счётчике будут обнаружены
+	устаревшие элементы.
+*/
+float qps_hosts(uint8_t * mac_addr, float * qps_summary)
+{
+    float hits = 0;
+    time_t now = time(0);
+    int i = 0;
+
+    if(qps_summary) 
+        *qps_summary = 0;
+    /* Проверяем достаточно-ли места для добавления нового элемента в счётчик */
+    if(mac_addr && (counter_size <= counter_end) )
+    {   /* Перераспределяем память для счётчика */
+        counter_size *= 2; /* Увеличиваем размер массива счётчика в 2 раза*/
+        counter = realloc(counter, sizeof(struct counter_node) * counter_size);
+        if(!counter)
+        {
+            log_fatal("FATAL: realloc for resizing QPS counter failed!");
+        }
+        log_info("INFO: Realloc memory for QPS counter. New counter size is: %u", counter_size);
+    }
+    if(mac_addr)
+    {   /* Добавляем новый элемент в счётчик */
+        memcpy(counter[counter_end].mac_addr, mac_addr, MAC_ADDR_LEN);
+        counter[counter_end].timestamp = now;
+    }
+    
+    ++ counter_end;
+
+    /* Начало обработки статистической информации */
+    for(;i < counter_end; i++)
+    {   /* Это условие выполняется если узел устарел */
+        if( (now - counter[i].timestamp) > TIME_DELTA )
+        {   /* Удаляем устаревший узел */
+            counter[i] = counter[counter_end - 1]; /* Просто переносим в него 
+                                значение последнего элемента счётчика */
+            -- counter_end;     /* И укорачиваем счётчик на 1 элемент */
+            -- i;
+            continue;
+        }
+
+        /* Производим подсчёт запросов для конкретного хоста */
+        if( mac_addr    /* Считаем только если в функцию передан адрес хоста-источника */
+            &&
+            !memcmp(counter[i].mac_addr, mac_addr, MAC_ADDR_LEN)
+            &&
+            (now - counter[i].timestamp <= TIME_DELTA_HOST)
+          )
+        {   /* Найден узел содержащий информацию о хосте пославшем данный запрос */
+            ++hits;
+        }
+    }
+    /* Производим подсчёт общего числа запросов за TIME_DELTA. Т.к. все устаревшие 
+		узлы уже удалены, то общее число запросов равно числу значимых элементов счётчика */
+    if(qps_summary)
+        *qps_summary = counter_end;
+    /* Проверяем, если значение размера счётчика больше минимально допустимого
+	    и хотя бы 60% массива счётчика не используется, то перераспределяем память 
+		освобождая не используемые участки */
+    if(counter_size > MIN_COUNTER_SIZE
+      &&
+      (counter_end - 1) < ((counter_size / 10) * 4) /* Во второй части сравнения вычисляется 40% размера счётчика */
+      )
+    {
+        counter_size /= 2;
+        counter = realloc(counter, sizeof(struct counter_node) * counter_size);
+        if(!counter)
+            log_fatal("FATAL: realloc failed for QPS counter!");
+        log_info("INFO: Realloc memory for QPS counter. New counter size is: %u", counter_size);
+    }
+    /* Вычисляем среднее число запросов в секунду за TIME_DELTA */
+    if(qps_summary)
+        (*qps_summary) /= TIME_DELTA;
+    return hits / TIME_DELTA_HOST;
+}
+
+/* 
+	Подсчитывает среднее число DHCP запросов в секунду за время TIME_DELTA секунд от всех клиентов
+*/
+float count_queries_per_sec(void)
+{
+    uint16_t queries_count = 0;
+    time_t  now = time(0);
+    int     i = 0;
+
+    time_t oldest_time = 0, newest_time = 0;
+
+    if(ring_ptr == QUERY_TIME_RING_LEN)
+        oldest_time = normal_clients_counter [0];
+    else
+        oldest_time = normal_clients_counter [ring_ptr];
+    if(ring_ptr == 0)
+        newest_time = normal_clients_counter [QUERY_TIME_RING_LEN - 1];
+    else
+        newest_time = normal_clients_counter [ring_ptr - 1];
+
+    if( (now - newest_time) > TIME_DELTA)
+        return 0;
+
+    if(newest_time - oldest_time > 0)
+        return QUERY_TIME_RING_LEN / (newest_time - oldest_time) ;
+    return (float)0xFFFFFFFF;
+}
+
+/*
+	Функция сменяющая текущий используемый RADIUS сервер 
+	при получении внешнего сигнала SIGNAL_CHANGE_SERV
+*/
+void change_server(int sign)
+{
+    log_info("INFO: Recv SIGNAL_CHANGE_SERV signal.");
+    if(servers_count > 1)
+    {
+        if(server_index == PRIMARY_SERVER_INDEX)
+            primary_server_down_time = time(0);
+        if(server_index == servers_count - 1)
+            server_index = 0;
+        else ++server_index;
+        log_info("INFO: Changing RADIUS server to %s (#%d)", 
+            inet_ntoa(servers[server_index].sin_addr), server_index + 1 );
+        unfinished_requests = 0;
+    }
+    else log_info("ERROR: can't change the server, because there are no duplicating servers.");
+    return;
+}
+
+/*
+	Функция устанавливающая текущим RADIUS сервером - первичный сервер, т.е. сервер 
+	заданный первым в параметрах командной строки при получении сигнала SIGNAL_SET_PRI_SERVER
+*/
+void set_default_server(int sign)
+{
+    log_info("INFO: receiving SIGNAL_SET_PRI_SERVER signal. Current server is: %s",
+                inet_ntoa(servers[server_index].sin_addr));
+    server_index = BASIC_SERVER_INDEX;
+    log_info("INFO: Set RADIUS server to: %s", inet_ntoa(servers[server_index].sin_addr));
+    return;
+}
+
+/*
+	Функция выводящая в лог статистическую информацию и содержимое DHCP кэша релея
+	при получении сигнала SIGSTAT
+*/
+void print_stat(int sign)
+{
+    XID_NODE * tmp_ptr = xid_list_top;
+    int numb = 0;
+    struct in_addr ip;
+    time_t ttl;
+    float qps = 0;
+    char dhcp_info[1024];
+
+    log_info ("#*************************************************************************#");
+    log_info ("dhcrelay patch version: %s", PATCH_VERSION);
+    log_info ("Current XID's list:");
+    log_info ("---");
+    for(;tmp_ptr; ++numb)
+    {
+        log_info("XID number:   %u", numb + 1);
+        log_info("XID value:    %u", ntohl(tmp_ptr -> xid));
+        log_info("XID rad_req.id: %u", tmp_ptr -> req_header.id);
+        ip.s_addr = tmp_ptr -> if_index;
+        log_info("XID if_index: %u (%s)", ntohl(tmp_ptr -> if_index), inet_ntoa(ip));
+        log_info("XID MAC address: %s", print_hw_addr (1, MAC_ADDR_LEN, tmp_ptr-> chaddr));
+        switch(tmp_ptr -> server_msg_type)
+        {
+            case DHCPOFFER:     log_info("Last server response: DHCPOFFER"); break;
+            case DHCPACK:       log_info("Last server response: DHCPACK"); break;
+            case DHCPNAK:       log_info("Last server response: DHCPNAK"); break;
+			case 0:				log_info("Last server response: Server not respond yet"); break;
+            default:            log_info("Unknown server response: %u", tmp_ptr -> server_msg_type); break;
+        }
+		switch(tmp_ptr -> client_msg_type)
+		{
+			case DHCPDISCOVER:	log_info("Last client request: DHCPDISCOVER"); break;
+			case DHCPREQUEST:	log_info("Last client request: DHCPREQUEST"); break;
+			default:			log_info("Last client request: unknown client request type"); break;
+		}
+        
+        if(tmp_ptr -> dhcp_flags & htons (BOOTP_BROADCAST))log_info("XID flags: BROADCAST");
+        ttl = xid_timeout - (time(0) - tmp_ptr -> timestamp);
+        log_info("XID ttl: %u", (ttl > 0)? ttl: 0);
+
+        if((tmp_ptr -> out_packet.op == BOOTREPLY) && tmp_ptr -> allow_cache)
+        {
+            bzero(dhcp_info, sizeof(dhcp_info));
+            memcpy(&ip, &tmp_ptr -> out_packet.yiaddr, sizeof(struct in_addr));
+            strcat(dhcp_info, "you addr: ");
+            strcat(dhcp_info, inet_ntoa(ip));
+            if(get_dhcp_option(&tmp_ptr -> out_packet, tmp_ptr -> out_pack_len, DHO_SUBNET_MASK, &ip.s_addr))
+                strcat(dhcp_info, " subnet mask: ");
+            strcat(dhcp_info, inet_ntoa(ip));
+            if(get_dhcp_option(&tmp_ptr -> out_packet, tmp_ptr -> out_pack_len, DHO_ROUTERS, &ip.s_addr))
+                strcat(dhcp_info, " default gw: ");
+            strcat(dhcp_info, inet_ntoa(ip));
+            log_info("DHCP info: '%s'", dhcp_info);
+        }
+        log_info("---");
+
+        tmp_ptr = tmp_ptr -> next;
+    }
+    log_info ("End XID list. Summary XID count: %u.", numb);
+    qps_hosts(0, &qps);
+    log_info ("QPS: %g", count_queries_per_sec());
+    log_info ("DHCP cache lenght: %u", dhcp_cache_len);
+    if(server_index != PRIMARY_SERVER_INDEX)
+    {
+        char * down_time;
+        down_time = ctime(&primary_server_down_time);
+        down_time[strlen(down_time) - 1] = 0;
+        log_info("Primary server down time: %s", down_time);
+    }
+    log_info ("Current RADIUS server: %s%s", 
+        inet_ntoa(servers[server_index].sin_addr), 
+        (server_index == PRIMARY_SERVER_INDEX)? " (primary)": " (backup server)");
+    log_info ("Unfinished RADIUS requests: %u", unfinished_requests);
+
+    log_info("Hits counter lenght: %u", counter_end);
+    log_info("Hits counter size: %u", counter_size);
+	if(updating_arp_cache_perm)
+		log_info("ARP entry count: %d", arp_entry_count);
+    log_info ("#*************************************************************************#");
+    return;
+}
+
+/*
+	Функция шифрующая пароль для доступа к RADIUS-серверу. 
+	INCOMPLETE: Текущий вариант не соответствует требованиям безопасности предъявляемым
+	к RADIUS клиентам. В данной ситуации это не слишком опасно, т.к. протокол 
+	DHCP в своём чистом виде в принципе не поддерживает шифрование передаваемой информации.
+*/
+int encrypt_passwd(char * passwd, 
+                    uint8_t * encr_passwd, char *shared_key,
+                    uint8_t * auth)
+{
+    unsigned char   full_passwd[16];
+    uint8_t   md5_hash[16];
+    unsigned int    i = 0;
+	MD5_CTX		context;
+
+    /* Verify input data */
+    if(!passwd || (strlen(passwd) > 16))return 0;
+    if(!encr_passwd)                    return 1;
+    if(!shared_key)                     return 2;
+    if(!auth)                           return 3;
+    
+    memset(full_passwd, 0, sizeof(full_passwd));
+    memset(encr_passwd, 0, ENCR_PWD_LEN);
+   
+    memcpy(full_passwd, passwd, strlen(passwd));	/* Дополняем пароль до 16ти символов */
+
+    /* Generate MD5 sum for encrypting password */
+	MD5_Init(&context);
+	MD5_Update(&context, shared_key, strlen(shared_key));
+	MD5_Update(&context, auth, MD5_LEN);
+	MD5_Final(md5_hash, &context);
+    
+    /* Encrypting password */
+    for(i = 0; i < sizeof(full_passwd); i++) 
+		encr_passwd[i] = full_passwd[i] ^ md5_hash[i];
+    
+    return ENCR_PWD_LEN;
+}
+
+/*
+	Функция формирующая RADIUS-Access-Request 
+*/
+int assemble_auth_message( RAD_MESSAGE *access_req, 
+                            char *user_name, char *passwd, 
+                            uint32_t subnet)
+{
+    union
+    {
+        uint8_t auth[MD5_LEN];
+        uint32_t digits[4];
+    }auth_union;
+    
+    uint8_t encr_passwd[ENCR_PWD_LEN];
+    int32_t ret_val = 0;
+	uint32_t attr_ptr = 0;
+    int32_t i; 
+
+	memset(access_req, 0, sizeof(RAD_MESSAGE));
+	access_req->header.code = RAD_ACCESS_REQ;
+	access_req->header.id = (uint8_t) random();
+
+	for(i = 0; i < 4; i++)
+		auth_union.digits[i] = random(); /* Генерируем случайный идентификатор RADIUS запроса */
+    memcpy(access_req->header.auth, auth_union.auth, MD5_LEN);
+
+	ret_val = encrypt_passwd(passwd, encr_passwd, rad_secret, auth_union.auth);
+	if(ret_val != ENCR_PWD_LEN)
+    {
+        log_error ("ERROR encrypt_passwd() failed - return value %d . Exiting from assemble_auth_message()!", ret_val);
+        return 0;
+    }
+    
+	/* Формат информационного поля RADIUS сообщения.
+		1 байт - имя атрибута
+		1 байт - длина атрибута и значения формируется из длин полей: имя атрибута, длина, значение атрибута
+		X байт - значение атрибута
+	*/
+    access_req->message[attr_ptr++] = USER_NAME_ATTR;
+	access_req->message[attr_ptr++] = strlen(user_name) + 2; /* Длина атрибута включая длину типа атрибута и поле длины */
+	memcpy(access_req->message + attr_ptr, user_name, strlen(user_name));
+	attr_ptr += strlen(user_name);
+	
+	access_req->message[attr_ptr++] = USER_PASW_ATTR;
+	access_req->message[attr_ptr++] = ENCR_PWD_LEN + 2;
+	memcpy(access_req->message + attr_ptr, encr_passwd, ENCR_PWD_LEN);
+	attr_ptr += ENCR_PWD_LEN;
+
+	/* В качестве атрибута NAS_PORT используется IP адрес интерфейса на котором 
+		получен DHCP запрос представленный как число типа int32_t */
+	access_req->message[attr_ptr++] = NAS_PORT;
+	access_req->message[attr_ptr++] = sizeof (subnet) + 2;
+	memcpy(access_req->message + attr_ptr, &subnet, sizeof(subnet));
+	attr_ptr += sizeof(subnet);
+
+	int full_packet_len = sizeof(RAD_HEADER) + attr_ptr;
+	if(full_packet_len < RADIUS_MIN_PACK_LEN)full_packet_len = 20;
+
+	access_req->header.pack_len = htons(full_packet_len);
+
+    return full_packet_len;
+}
+
+/*
+	Функция проверяющая соответствие значения аутентификатора в ответе сервера 
+	значению аутентификатора в запросе сохранённом в списке кэша.
+*/
+static int calc_reply_auth(RAD_MESSAGE *packet, uint8_t *original_auth,
+			    const char *secret)
+{
+	uint8_t repl_checksumm[MD5_LEN];
+    uint8_t auth[MD5_LEN];
+	MD5_CTX		context;
+
+	if (!original_auth) 
+		return -1;
+
+    memcpy(auth, packet->header.auth, MD5_LEN);
+	memcpy(packet->header.auth, original_auth, MD5_LEN);
+
+	MD5_Init(&context);
+	MD5_Update(&context, (uint8_t *)packet, ntohs(packet->header.pack_len));
+	MD5_Update(&context, secret, strlen(secret));
+	MD5_Final(repl_checksumm, &context);
+
+	memcpy(packet->header.auth, auth, MD5_LEN);
 
+    return memcmp(auth, repl_checksumm, MD5_LEN);
+}
+
+/*
+	Функция удаляющая устаревшие элементы кэша DHCP
+*/
+int delete_old_xid(XID_NODE * start_xid_list)
+{
+    XID_NODE * xid_ptr = start_xid_list;
+    int deleted_xid_count = 0;
+    static time_t now;
+    if(time(&now) == (time_t)-1)
+    {
+        log_error("time() failed! Adding xid aborted.");
+        return 0;
+    }
+    while(xid_ptr)
+    {
+        if((now - xid_ptr->timestamp) > xid_timeout)
+        {
+            xid_ptr = delete_xid(xid_ptr);
+            deleted_xid_count++;
+        } else xid_ptr = xid_ptr->next;
+    }
+    return deleted_xid_count;
+}
+
+/* 
+	Функция добавляющая новый элемент в DHCP кэш.
+*/
+XID_NODE * add_xid(struct dhcp_packet *packet, uint8_t dhcp_type, 
+                        DHCP_REQ_INFO *request_info, uint32_t if_index)
+{
+    static time_t now;
+    if(dhcp_type <= 0)
+    {
+        log_info ("INFO Invalid DHCP message type %d for xid: %d hw addr: %s - add_xid aborted.",
+                        dhcp_type, ntohl( packet -> xid), print_hw_addr(1, MAC_ADDR_LEN, packet -> chaddr));
+        return 0;
+    }    
+    if((int)search_xid(packet -> chaddr, packet -> xid, if_index))
+                                            return 0;  /* Если подобный клиент уже есть в кэше, 
+                                                        то не добавляем новый узел */
+    del_duplicate_xid(packet -> xid, packet -> chaddr, if_index);
+    if( time(&now) == (time_t) -1 )
+    {
+        log_error("time() failed! Adding xid aborted.");
+        return 0;
+    }
+    if(!xid_list_top)	/* Если в кэше нет ни одного элемента */
+    {
+        xid_list_top = malloc(sizeof(XID_NODE));
+        if(!xid_list_top)
+        {
+            log_error("Creating DHCP cache failed!");
+            return 0;
+        }
+        bzero(xid_list_top, sizeof(XID_NODE));
+		xid_list_top -> client_msg_type = dhcp_type;
+        if(request_info) 
+            xid_list_top -> request_info = *request_info;
+        xid_list_top -> timestamp = now;
+        xid_list_top ->if_index = if_index;
+        xid_list_top ->next = 0;
+        xid_list_top ->prev = 0;
+        xid_list_top -> xid = packet -> xid;
+        xid_list_top -> dhcp_flags = packet -> flags;
+        memcpy(xid_list_top -> chaddr, packet -> chaddr, MAC_ADDR_LEN);
+        xid_list_ptr = xid_list_top;
+        ++dhcp_cache_len;
+        return xid_list_top;
+    }
+
+    xid_list_ptr->next = malloc(sizeof(XID_NODE));
+    if(!xid_list_ptr->next)
+    {
+        log_error("malloc(sizeof(XID_NODE)) failed! Adding xid aborted.");
+        return 0;
+    }
+    bzero(xid_list_ptr -> next, sizeof(XID_NODE));
+    xid_list_ptr -> next -> prev = xid_list_ptr;
+    xid_list_ptr = xid_list_ptr -> next;
+    xid_list_ptr -> if_index = if_index;
+    xid_list_ptr -> client_msg_type = dhcp_type;
+    if(request_info) 
+        xid_list_ptr -> request_info = *request_info;
+    xid_list_ptr -> timestamp = now;
+    xid_list_ptr -> xid = packet -> xid;
+    xid_list_ptr -> dhcp_flags = packet -> flags;
+    memcpy(xid_list_ptr -> chaddr, packet -> chaddr, MAC_ADDR_LEN);
+    xid_list_ptr->next = 0;
+    ++ dhcp_cache_len;
+    
+    return xid_list_ptr;
+}
+
+/* 
+	Устанавливает указанный тип DHCP сообщения, возвращает предыдущее значение типа 
+*/
+uint16_t set_dhcp_type(struct dhcp_packet *request, uint16_t new_type)
+{
+    uint8_t *option = (uint8_t *)request + sizeof (struct dhcp_packet) - DHCP_OPTION_LEN;
+    const uint8_t * opt_end = (const uint8_t *)request + sizeof(struct dhcp_packet);
+    uint8_t opt_len, old_type;
+    if(memcmp(option, magic_cookie, sizeof(magic_cookie)))  /* Выходим если не найдено magic_cookie - */
+            return -1;                                      /* начало поля опций */
+    option += sizeof(magic_cookie);
+    while((option < opt_end) && (*option != 255))
+    {
+        if(*option == DHO_DHCP_MESSAGE_TYPE)
+        {
+            old_type = *(option + 2);
+            *(option + 2) = new_type;
+            return old_type;
+        }
+        else option += *(option + 1) + 2;
+    }
+    return 0;
+}
+
+/*
+	Функция получающая значение заданной опции из DHCP пакета
+*/
+int get_dhcp_option(struct dhcp_packet *request, uint16_t packet_len, int req_option, void * option_value)
+{
+    /* Calculate start address for field "options" in DHCP packet */
+    uint8_t *option = (uint8_t *)request + sizeof (struct dhcp_packet) - DHCP_OPTION_LEN;
+    /* End options equal end packet */
+    const uint8_t * opt_end = (const uint8_t *)request + packet_len;
+    if(memcmp(option, magic_cookie, sizeof(magic_cookie)))/* Check "Magic cookie" in first 4 bytes options-field */
+                                                        return -1;
+    option += sizeof(magic_cookie);
+
+    while((option < opt_end) && (*option != DHO_END))
+    {
+        if((*(option + 1) + option) > opt_end)
+        {
+            log_error("WARN: Invalid value in DHCP-option length. Attempting DoS? \n");
+            return -1;
+        }
+
+        if(*option == req_option)
+        {
+            memcpy(option_value, option + 2, *(option + 1));
+            return *(option + 1);
+        }
+        else option += *(option + 1) + 2;
+    }
+    return 0;
+}
+
+/*
+	Функция возвращающая тип DHCP сообщения, а так же строковую информацию о нём через указатель str_dhcp_type
+*/
+uint16_t get_dhcp_type(struct dhcp_packet *request, char * str_dhcp_type)
+{
+    uint8_t *option = (uint8_t *)request + sizeof (struct dhcp_packet) - DHCP_OPTION_LEN;
+    const uint8_t * opt_end = (const uint8_t *)request + sizeof(struct dhcp_packet);
+    uint8_t opt_len, dhcp_type;
+    if(memcmp(option, magic_cookie, sizeof(magic_cookie)))return -1;
+    option += sizeof(magic_cookie);
+    while((option < opt_end) && (*option != 255))
+    {
+        if(*option == DHO_DHCP_MESSAGE_TYPE)
+        {
+            if(str_dhcp_type)
+            {								/*Возвращаем строку указывающую тип*/
+                switch(*(option + 2))
+                {
+                    case DHCPDISCOVER: memcpy(str_dhcp_type, "DHCPDISCOVER", strlen("DHCPDISCOVER")); break;
+                    case DHCPOFFER: memcpy(str_dhcp_type, "DHCPOFFER", strlen("DHCPOFFER")); break;
+                    case DHCPREQUEST: memcpy(str_dhcp_type, "DHCPREQUEST", strlen("DHCPREQUEST")); break;
+                    case DHCPDECLINE: memcpy(str_dhcp_type, "DHCPDECLINE", strlen("DHCPDECLINE")); break;
+                    case DHCPACK: memcpy(str_dhcp_type, "DHCPACK", strlen("DHCPACK")); break;
+                    case DHCPNAK: memcpy(str_dhcp_type, "DHCPNAK", strlen("DHCPNAK")); break;
+                    case DHCPRELEASE: memcpy(str_dhcp_type, "DHCPRELEASE", strlen("DHCPRELEASE")); break;
+                    case DHCPINFORM: memcpy(str_dhcp_type, "DHCPINFORM", strlen("DHCPINFORM")); break;
+                }
+            }
+            return *(option + 2);
+        }
+        else option += *(option + 1) + 2;
+    }
+    return 0;
+}
+
+/*
+	Функция получающая основную запрашиваемую клиентом информацию из DHCP сообщения
+*/
+int get_dhcp_request_info(struct dhcp_packet *request, DHCP_REQ_INFO * request_info)
+{
+    uint8_t *option = (uint8_t*)request->options;
+    const uint8_t * opt_end = (const uint8_t *)request + sizeof(struct dhcp_packet);
+    uint8_t opt_len, dhcp_type;
+    if(memcmp(option, magic_cookie, sizeof(magic_cookie)))
+		return 0; /* Выходим, если не найдена последовательность magic_cookie */
+    option += sizeof(magic_cookie);
+    
+    short options_found = 0;
+    while((option < opt_end) && (options_found < 2) && (*option != 255))
+    {
+        switch((uint8_t)*option)
+        {
+            case DHO_DHCP_SERVER_IDENTIFIER:
+                memcpy(&request_info->server_id, option + 2, *(option + 1));
+                options_found++;
+            break;
+            case DHO_DHCP_REQUESTED_ADDRESS:
+                memcpy(&request_info->req_addr, option + 2, *(option + 1));
+                options_found++;
+            break;
+        }
+        if(options_found == 2) 
+        {
+            return 1;
+        }
+        else option += *(option + 1) + 2;
+    }
+    if(request_info->client_addr.s_addr == 0) 
+        request_info->client_addr = request->ciaddr;
+    return 1;
+}
+
+/*
+	Функция удаляющая элемент кэша на который ссылается указатель xid_n
+*/
+inline XID_NODE * delete_xid(XID_NODE *xid_n)
+{
+    if(xid_n == xid_list_top)
+    {
+        if(xid_list_ptr == xid_list_top) 
+        {                               
+            xid_list_top = 0;
+            xid_list_ptr = 0;
+        }    
+        else xid_list_top = xid_n->next;
+    }
+    else if(!xid_n->next) 
+    {
+        xid_n->prev->next = 0;
+        xid_list_ptr = xid_n->prev;
+    }
+    else                 
+    {
+        xid_n->prev->next = xid_n->next;
+        xid_n->next->prev = xid_n->prev;
+    }    
+    XID_NODE * next_xid = xid_n->next;
+    free(xid_n);
+    -- dhcp_cache_len;
+    return next_xid;
+}
+
+/*
+	Функция удаляющая дублирующиеся по MAC адресу клиента и значению xid элементы кэша.
+	Такое бывает необходимо в случае повторной инициализации клиентом процесса получения
+	IP адреса - в последующих пакетах поле xid будет иметь другое значение. 
+	В таком случае все предыдущие запросы являются устаревшими и будут удалены.
+	Кроме MAC адреса сверяется индекс интерфейса на котором получен запрос от клиента.
+	Индексом интерфейса является его основной (не алиасный!) IP адрес 
+	представленный как число типа uint32_t
+*/
+int del_duplicate_xid(uint32_t xid, uint8_t * hw_addr, uint32_t if_index)
+{
+    XID_NODE * xid_ptr = xid_list_top;/*, *dup_xid = 0;*/
+    int ret_code = 0;
+    while(xid_ptr)
+    {
+        if( !memcmp(xid_ptr -> chaddr, hw_addr, MAC_ADDR_LEN) && 
+			(xid_ptr -> xid != xid) &&
+            (xid_ptr -> if_index == if_index)
+		  )
+        {   
+            xid_ptr = delete_xid(xid_ptr);
+            ++ret_code;
+        }
+        else xid_ptr = xid_ptr -> next;
+    }
+    return ret_code;
+}
+
+/*
+	Функция производящая поиск нужного узла кэша и возвращающая в случае успеха указатель на него.
+*/
+XID_NODE * search_xid(uint8_t * hw_addr, uint32_t xid, uint32_t if_index)
+{
+    XID_NODE * tmp_ptr = xid_list_top;
+    while(tmp_ptr)
+    {
+        if(xid) /* Производим поиск по xid-полю транзакции и MAC-адресу клиента */
+        {
+            if(!memcmp(tmp_ptr -> chaddr, hw_addr, MAC_ADDR_LEN) && 
+                (tmp_ptr -> xid == xid)) 
+            {
+                return tmp_ptr;
+            }    
+        }else /* Иначе производим поиск по MAC-адресу клиента и ID интерфейса на котором получен запрос */
+        {
+            if(!memcmp(tmp_ptr-> chaddr, hw_addr, MAC_ADDR_LEN) && 
+                (tmp_ptr->if_index == if_index))
+            {
+                return tmp_ptr;
+            }    
+        }
+        tmp_ptr = tmp_ptr->next;
+    }
+    return 0;
+}
+
+/*
+	Функция преобразующая информацию о статических бесклассовых маршрутах в
+	DHCP опцию с кодом 249 (Microsoft Classless Routes) из строкового
+	в двоичное представление.
+
+	NOTE! в данной версии RADIUS сервер передаёт информацию в следующем формате:
+	первые 4 байта - IP адрес шлюза через который должны маршрутизироваться
+						приведённые далее сети
+	1 байт - длина маски сети
+	1 - 4 байта - адрес сети
+	... - вышеприведённая последовательность (длина маски, адрес сети) до конца строки.
+	Пример:
+	длина значения:	|	    4    |1|   1      |1 |	   2     |1 |    2     |
+	значение:		|192.168.1.1 |8|   10     |16|	196.168  |12|  172.16  |
+	коментарий:		|    шлюз    | 10.0.0.0/8 |192.168.0.0/16|172.16.0.0/12|
+	Строка описывающая эти подсети будет такой:
+	c0a80101080a10c0a80cac10
+	Данный вариант описания информации о маршрутах значительно компактней 
+	стандартного варианта, вида: длина маски + адрес сети + IP адрес шлюза - 
+	адрес шлюза обычно всегда бывает одинаков и дублируется в каждом последующем 
+	маршруте порождая большую избыточность передаваемых данных. 
+	Необходимость в экономии вызвана ограниченностью максимально допустимой 
+	длины поля значения RADIUS атрибута (253 байта).
+*/	
+int translate_249(unsigned char * routes, int out_len, uint8_t * out)
+{
+    unsigned char gw_ip[4];
+    unsigned char network[4];
+    unsigned char *end_data = out;
+    unsigned char netmask_len;
+    int scaned;
+    int parsed_len = 0;
+    unsigned int routes_len = strlen(routes);
+    bzero(out, out_len);  
+
+    /* Scan default gateway */
+    scaned = sscanf(routes, "%02hhx%02hhx%02hhx%02hhx", &gw_ip[0], &gw_ip[1], &gw_ip[2], &gw_ip[3]);
+    if(scaned != 4)
+    {
+        log_error("translate_249: sscanf() error - gateway not found.");
+        return 0;
+    }
+
+    parsed_len += scaned * 2;
+    while(parsed_len < routes_len)
+    {
+        /* Scan netmask length */
+        scaned = sscanf(routes + parsed_len, "%02hhx", &netmask_len);
+        if(scaned != 1)
+        {
+            log_error("translate_249: sscanf() error - netmask length not found.");
+            return 0;
+        }
+        parsed_len += scaned * 2 ;
+
+        bzero(&network, sizeof(network));
+
+        if(netmask_len >= 1 && netmask_len <= 8)
+        {
+            scaned = sscanf(routes + parsed_len, "%02hhx", &network[0]);
+            if(scaned != 1)
+            {
+                log_error("translate_249: sscanf() error, scaned = %d. (mask 8)", scaned);
+                return 0;
+            }
+        }else if(netmask_len >=9 && netmask_len <= 16)
+        {
+            scaned = sscanf(routes + parsed_len, "%02hhx%02hhx", &network[0], &network[1]);
+            if(scaned != 2)
+            {
+                log_error("translate_249: sscanf() error, scaned = %d. (mask 16)", scaned);
+                return 0;
+            }
+        }else if(netmask_len >= 17 && netmask_len <=24)
+        {
+            scaned = sscanf(routes + parsed_len, "%02hhx%02hhx%02hhx", &network[0], &network[1], &network[2]);
+            if(scaned != 3)
+            {
+                log_error("translate_249: sscanf() error, scaned = %d. (mask 24)", scaned);
+                return 0;
+            }
+        }else if(netmask_len >= 25 && netmask_len <=32)
+        {
+            scaned = sscanf(routes + parsed_len, "%02hhx%02hhx%02hhx%02hhx", &network[0], &network[1], &network[2], &network[3]);
+            if(scaned != 4)
+            {
+                log_error("translate_249: sscanf() error, scaned = %d. (mask 32)", scaned);
+                return 0;
+            }
+        }else 
+        {
+            log_error("translate_249: invalid netmask length (%u)", netmask_len);
+            return 0;
+        }
+
+        parsed_len += scaned * 2 ;
+
+        if(end_data + sizeof(netmask_len) + scaned + sizeof(gw_ip) > out + out_len)
+        {
+            log_error("translate_249: error! Out bufer overflow (%d bytes)! Maximum lenght is 253. Aborting!\n",
+                (end_data + sizeof(netmask_len) + scaned + sizeof(gw_ip)) - out);
+            return 0;
+        }
+
+        memcpy(end_data, &netmask_len, sizeof(netmask_len));
+        end_data += sizeof(netmask_len);
+        memcpy(end_data, network, scaned);
+        end_data += scaned;
+        memcpy(end_data, gw_ip, sizeof(gw_ip));
+        end_data += sizeof(gw_ip);
+    }
+ 
+    return end_data - out;
+}
+
+/*
+	Функция преобразующая полученный от сервера RADIUS-Access-Accept в DHCP-сообщение клиенту
+	запросившему IP адрес
+*/
+int translate_rad_to_dhcp(RAD_MESSAGE *rad_pack, struct dhcp_packet * out_packet, uint16_t received_length,
+                           uint32_t * user_interface ) /* Параметр необходим для корректной обработки */
+{                                                     /* запросов с xid == 0 */
+    if(!out_packet)
+    {
+        log_error("NULL pointer *out_packet in function translate_rad_to_dhcp(). Exiting whith error code 0.");
+        return 0;
+    }
+    
+    *user_interface = 0;
+    int out_pack_len = 0;
+    XID_NODE * cur_xid;
+    struct in_addr ip;
+    unsigned int len = 0;
+    uint8_t rad_attr = 0;
+    uint8_t attr_len = 0;
+    
+    static uint8_t buf[4096];
+    uint8_t option_249[4096];
+    uint8_t hw_addr[MAC_ADDR_LEN];
+    uint8_t dns_servers[8];
+    uint16_t dns_num = 0;
+    uint32_t server_id = 0;
+    uint32_t subnet_mask = 0;
+    uint8_t msg_type_nak = 0;
+    uint32_t if_index = 0;
+    uint8_t allow_cache = 1;
+	int ret_code;
+    
+    memset(buf, 0, 4096);
+    memset (out_packet, 0, sizeof(struct dhcp_packet));
+    memset(dns_servers, 0, 8);
+    
+    uint8_t * attr_ptr = (uint8_t *)rad_pack + sizeof(RAD_HEADER);  /* Указатель на начало 
+                                                                    обрабатываемого RADIUS атрибута */
+    uint8_t * attr_cont = 0;	/* Указатель на начало значения атрибута */
+    unsigned int opt_len = 0;
+    /* Вставляем "magic cookie" в начало DHCP опций: 99.130.83.99 */
+    memcpy(out_packet->options, magic_cookie, sizeof(magic_cookie));
+    opt_len += sizeof(magic_cookie) + /* Задаём начальную длину пакета равной длине */
+                                        3;/* "magic cookie" + длина DHCP-Message-type */
+    /* Цикл пока не достигнем конца пакета, либо не обнаружим несоотвествие запрашиваемого адреса
+    предлагаемому, что породит msg_type_nak == true */
+    if( received_length != ntohs(rad_pack->header.pack_len))
+    {
+        log_error("ERROR: Invalid length in RADIUS header."
+                    "Received %u bytes, but length = %u in RADIUS header.",
+                    received_length, ntohs(rad_pack->header.pack_len));
+        return 0;
+    }
+	/* Начало разбора переданных атрибутов */
+    while( ( attr_ptr < (uint8_t *) rad_pack + ntohs(rad_pack->header.pack_len) ) 
+            && !msg_type_nak)
+    {
+        rad_attr = *attr_ptr;
+        attr_len = *(attr_ptr + 1);
+        attr_cont = attr_ptr + 2;
+    
+        /* Проверка на неправильную длину атрибута */
+        if( (uint8_t *)(rad_attr + attr_len) > (uint8_t *)(rad_pack + received_length))
+        {
+            log_error("ERROR: Invalid RADIUS attribute length: %u. "
+                        "End of option exceed the bounds of packet.",
+                        attr_len);
+            return 0;
+        }
+        /* Проверка на переполнение поля опций DHCP пакета */
+        if(opt_len + attr_len > DHCP_OPTION_LEN)
+        {
+            log_error("ERROR: DHCP_OPTION owerflow! Exiting from translate_rad_to_dhcp() with error code 0.");
+            return 0;
+        }
+
+        switch(rad_attr)
+        {
+            case RAD_YADDR: 
+                memcpy(&out_packet->yiaddr.s_addr, attr_cont, attr_len - 2);
+            break;
+            case RAD_GIADDR:
+                memcpy(&out_packet->giaddr.s_addr, attr_cont, attr_len - 2);
+                memcpy(&out_packet->siaddr.s_addr, attr_cont, attr_len - 2);
+            break;
+            case RAD_SID:
+                *(out_packet->options + opt_len++) = DHO_DHCP_SERVER_IDENTIFIER;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                memcpy(&server_id, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+            break;
+            case RAD_DNS1:
+                memcpy(dns_servers + 4 * dns_num++, attr_cont, attr_len - 2);
+            break;
+            case RAD_DNS2:
+                memcpy(dns_servers + 4 * dns_num++, attr_cont, attr_len - 2);
+            break;
+            case RAD_GATEWAY:
+                *(out_packet->options + opt_len++) = DHO_ROUTERS;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+            break;
+            case RAD_LEASE:
+                *(out_packet->options + opt_len++) = DHO_DHCP_LEASE_TIME;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+            break;
+			case RAD_RENEWING_TIME:
+                *(out_packet->options + opt_len++) = DHO_DHCP_RENEWAL_TIME;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+			break;
+			case RAD_REBINDING_TIME:
+                *(out_packet->options + opt_len++) = DHO_DHCP_REBINDING_TIME;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+			break;
+            case RAD_NETMASK:
+                *(out_packet->options + opt_len++) = DHO_SUBNET_MASK;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                memcpy(&subnet_mask, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+            break;
+            case RAD_DOMAIN_NAME:
+                *(out_packet->options + opt_len++) = DHO_DOMAIN_NAME;
+                *(out_packet->options + opt_len++) = attr_len - 2;
+                memcpy(out_packet->options + opt_len, attr_cont, attr_len - 2);
+                opt_len += attr_len - 2;
+            break;
+            case RAD_ROUTE:
+                if(attr_len - 2 > sizeof(option_249))
+                {
+                    log_error("Option 249 has too big len: %d. Aborted processing this packet.", attr_len - 2);
+                    return 0;
+                }
+                memset(option_249, 0, sizeof(option_249));
+                memcpy(option_249, attr_cont, attr_len - 2);
+                len = translate_249(option_249, sizeof(buf), buf);
+                *(out_packet->options + opt_len++) = DHO_STATIC_ROUTE;
+                *(out_packet->options + opt_len++) = len;//attr_len - 2;
+                memcpy(out_packet->options + opt_len, buf, len);
+                opt_len += len;
+            break;
+            case RAD_CLIENT_MAC:
+                memset(buf, 0, 4096);
+                memcpy(buf, attr_cont, attr_len - 2);
+                sscanf(buf,"%x:%x:%x:%x:%x:%x", 
+                        &hw_addr[0], &hw_addr[1], &hw_addr[2], &hw_addr[3], &hw_addr[4], &hw_addr[5]);
+            break;
+            case RAD_IFINDEX:
+                memcpy(&if_index, attr_cont, attr_len - 2);
+                *user_interface = if_index;	/* Копируем значение во внешнюю переменную для возможности */
+											/* воспользоваться им после завершения работы функции */
+            break;
+            case RAD_NO_CACHE:
+                allow_cache = 0;
+            break;
+            default: log_info ("WARN: Unknown rad-attr: %u, len: %u\n",rad_attr, attr_len);
+        } /* End switch (rad_attr) */
+        attr_ptr += attr_len;
+    } /* End while(...)*/
+
+                
+/****************************************************************/                
+    cur_xid = search_xid(hw_addr, 0, if_index);
+    if(!cur_xid)
+    {
+        ip.s_addr = if_index;
+        log_info("WARN: Can't find request info for client %s from interface %s", 
+                    print_hw_addr ( 1, MAC_ADDR_LEN, hw_addr), inet_ntoa(ip));
+        return 0;
+    }
+    cur_xid -> allow_cache = allow_cache;
+    out_packet -> flags = cur_xid -> dhcp_flags;
+    
+    /* Выбираем тип сообщения который будет отправлен в DHCP пакете к клиенту */
+    switch (cur_xid -> client_msg_type)
+    {
+        /*case DHCPDISCOVER:*/
+        case DHCPDISCOVER:
+#ifdef DEBUG_TR_RAD_TO_DHCP
+    log_info("cur_xid->server_msg_type == DHCPDISCOVER");
+#endif
+            *(out_packet->options + sizeof(magic_cookie)) = DHO_DHCP_MESSAGE_TYPE;
+            *(out_packet->options + sizeof(magic_cookie) + 1) = 1;
+            *(out_packet->options + sizeof(magic_cookie) + 2) = DHCPOFFER;
+            cur_xid -> server_msg_type = DHCPOFFER;
+            /* Записываем в XID предлагаемый пользователю адрес */
+            memcpy(&cur_xid -> request_info.client_addr, &out_packet->yiaddr, sizeof(struct in_addr));
+#ifdef DEBUG_TR_RAD_TO_DHCP
+log_info("End cur_xid->server_msg_type == DHCPDISCOVER");
+#endif
+        break;
+		case DHCPREQUEST:
+#ifdef DEBUG_TR_RAD_TO_DHCP
+log_info("cur_xid->server_msg_type == XID_REQUEST || cur_xid->server_msg_type == DHCPOFFER || cur_xid->server_msg_type == DHCPACK)");
+#endif
+            *(out_packet->options + sizeof(magic_cookie)) = DHO_DHCP_MESSAGE_TYPE;
+            *(out_packet->options + sizeof(magic_cookie) + 1) = 1;
+            
+            if(((cur_xid->request_info.client_addr.s_addr & subnet_mask) !=
+                (out_packet->yiaddr.s_addr & subnet_mask)) &&
+                cur_xid->request_info.client_addr.s_addr != 0 ) 
+                                                        return -1;
+            if(cur_xid->request_info.client_addr.s_addr != 0)
+            {
+                if(out_packet->yiaddr.s_addr != cur_xid->request_info.client_addr.s_addr)
+                {
+                    *(out_packet->options + sizeof(magic_cookie) + 2) = DHCPNAK;
+                    out_packet->flags = htons (BOOTP_BROADCAST);/* Флаг broadcast */
+                    out_packet->ciaddr.s_addr = 0;
+                    out_packet->yiaddr.s_addr = 0;
+                    out_packet -> siaddr.s_addr = server_id;
+
+                    msg_type_nak = DHCPNAK;
+                    *(out_packet->options + sizeof(magic_cookie) + 3) = DHO_DHCP_SERVER_IDENTIFIER;
+                    *(out_packet->options + sizeof(magic_cookie) + 4) = sizeof(server_id);
+                    memcpy(out_packet->options + sizeof(magic_cookie) + 5, &server_id, sizeof(server_id));
+                    cur_xid -> server_msg_type = DHCPNAK;
+                }else 
+                {
+                    *(out_packet->options + sizeof(magic_cookie) + 2) = DHCPACK;
+                    cur_xid -> server_msg_type = DHCPACK;
+                }
+            }
+            else if(cur_xid->request_info.req_addr != 0)
+            {
+                if(cur_xid->request_info.req_addr != out_packet->yiaddr.s_addr)
+                {
+                    *(out_packet->options + sizeof(magic_cookie) + 2) = DHCPNAK;
+                    out_packet->ciaddr.s_addr = 0;
+                    out_packet->yiaddr.s_addr = 0;
+                    out_packet -> siaddr.s_addr = server_id;
+                    out_packet->flags = htons (BOOTP_BROADCAST);/* Флаг broadcast */
+                    msg_type_nak = DHCPNAK;
+                    *(out_packet->options + sizeof(magic_cookie) + 3) = DHO_DHCP_SERVER_IDENTIFIER;
+                    *(out_packet->options + sizeof(magic_cookie) + 4) = sizeof(server_id);
+                    memcpy(out_packet->options + sizeof(magic_cookie) + 5, &server_id, sizeof(server_id));
+                    cur_xid -> server_msg_type = DHCPNAK;
+                }
+                else 
+                {
+                    *(out_packet->options + sizeof(magic_cookie) + 2) = DHCPACK;
+                    cur_xid -> server_msg_type = DHCPACK;
+                }    
+            }else
+            {
+                ip.s_addr = cur_xid->request_info.req_addr;
+                log_error("WARN: Found invalid DHCP-request \"required address\":%s \"client address\" %s (hw: %s), state: %d",
+                                            inet_ntoa(cur_xid->request_info.client_addr),
+                                            inet_ntoa(ip),
+                                            print_hw_addr ( 1, MAC_ADDR_LEN, cur_xid -> chaddr),
+                                            cur_xid -> server_msg_type);
+                return 0;
+            }
+        break;
+    }    /* End switch () */
+
+#ifdef DEBUG_TR_RAD_TO_DHCP
+    log_info("End cur_xid->server_msg_type == XID_REQUEST || cur_xid->server_msg_type == DHCPOFFER || cur_xid->server_msg_type == DHCPACK)");
+#endif
+/**********************************************************************/    
+    if(dns_num && !msg_type_nak)
+    {
+        *(out_packet->options + opt_len++) = DHO_DOMAIN_NAME_SERVERS;
+        *(out_packet->options + opt_len++) = 4 * dns_num;
+        memcpy(out_packet->options + opt_len, dns_servers, 4 * dns_num);
+        opt_len += 4 * dns_num;
+    }
+
+    if(msg_type_nak)
+    {
+        *(out_packet->options + sizeof(magic_cookie) + 5 + sizeof(server_id)) = DHO_END;
+        out_pack_len = sizeof(struct dhcp_packet) - DHCP_OPTION_LEN +	/* Общая длина пакета складывается из: */
+                        sizeof(magic_cookie) +							/* размера обязательного поля magic_cookie, */
+                        3 +												/* размера поля указывающего тип DHCP сообщения 
+																			включая длину полей указыающих имя и длину 
+																			DHCP атрибута (1 + 1 + 1 = 3), */
+                        2 + sizeof(server_id) + 						/* размера идентификатора сервера, так же 
+																			включая длину служебных полей */
+						1;												/* и длины опции-завершителя DHO_END */
+    }
+    else 
+    {
+        *(out_packet->options + opt_len++) = DHO_END;
+        out_pack_len = sizeof(struct dhcp_packet) - DHCP_OPTION_LEN + opt_len;
+    }
+
+    out_packet -> op = BOOTREPLY;
+    out_packet -> htype = HTYPE_ETHER;
+    out_packet -> hlen = MAC_ADDR_LEN;
+    memcpy(out_packet -> chaddr, hw_addr, MAC_ADDR_LEN);
+    out_packet -> xid = cur_xid -> xid;
+	/* Обновляем, если это необходимо статическую ARP таблицу ОС */
+    if(updating_arp_cache_perm && allow_cache && out_packet -> yiaddr.s_addr)
+	{
+		ret_code = update_arp_cache_perm(out_packet -> yiaddr.s_addr, out_packet -> chaddr);
+		if(ret_code < 0)
+		{
+			log_error("ERROR: Can't updating ARP cache for %s (hw: %s)",
+						inet_ntoa(out_packet -> yiaddr), 
+						print_hw_addr(1, MAC_ADDR_LEN, out_packet -> chaddr));
+		}
+		if(ret_code > 0)
+			log_info("INFO: Updating ARP entry for %s (new hw address: %s)",
+						inet_ntoa(out_packet -> yiaddr),
+						print_hw_addr(1, MAC_ADDR_LEN, out_packet -> chaddr));
+	}
+    return out_pack_len;
+}
+
+uint16_t update_xid_node(XID_NODE * xid, struct dhcp_packet * packet, unsigned pack_len)
+{
+    uint16_t msg_type = DHCPNAK;
+    struct in_addr requested_addr;
+    if( !get_dhcp_option(packet, pack_len, 
+            DHO_DHCP_REQUESTED_ADDRESS, &requested_addr.s_addr) )
+                                                        requested_addr.s_addr = 0;
+    /* Сюда попадают пакеты обязательно имеющие тип DHCPREQUEST,
+        следовательно у них обязательно должно быть 
+        заполнено одно из ниже указанных полей */
+    if(requested_addr.s_addr)
+    {
+        if(requested_addr.s_addr != xid -> out_packet.yiaddr.s_addr)
+        {
+            xid -> out_packet.yiaddr.s_addr = 0;
+            xid -> out_packet.ciaddr.s_addr = 0;
+        }
+        else msg_type = DHCPACK;
+    }
+    else if(packet -> ciaddr.s_addr)
+    {
+        if(packet -> ciaddr.s_addr != xid -> out_packet.yiaddr.s_addr) 
+        {
+            xid -> out_packet.yiaddr.s_addr = 0;
+            xid -> out_packet.ciaddr.s_addr = 0;
+        }
+        else msg_type = DHCPACK;
+    }
+    else 
+    {
+        log_error("INFO: Invalid DHCPREQUEST for xid: %d hw addr: %s.",
+                        ntohl( packet -> xid), print_hw_addr(1, MAC_ADDR_LEN, packet -> chaddr));
+        return 0;
+    }
+
+    xid -> out_packet.xid = packet -> xid;
+    xid -> xid = packet -> xid;
+
+    set_dhcp_type( &xid -> out_packet, msg_type);
+	xid -> server_msg_type = msg_type;
+    return msg_type;
+}
+
+/* 
+	Функция удаляющая самый старый запрос в DHCP кэше 
+*/
+inline int delete_oldest_cache_node(XID_NODE * cache_node)
+{
+    delete_xid(xid_list_top);
+    return dhcp_cache_len;
+}
+
+/* 
+	Функция проверяющая не превышено-ли максимально допустимое число запросов на которые не 
+	получены ответы от сервера. Применяется для переключения на резервный сервер в случае 
+	падения основного.
+*/
+inline int is_unfinished_requests_overflow(int * count, int inc, int max_count, int zeroing)
+{
+    static counter = 0;
+    if(++counter == zeroing)
+        *count = counter = 0;
+    if(!inc && *count > 0)
+        -- *count;
+    else 
+    if(inc)
+        ++ *count;
+    if(*count > max_count) 
+        return *count;
+    return 0;
+}
+
+/*
+	Функция анализирующая RADIUS-Response
+*/
+int dispatch_radius_packet(struct dhcp_packet * packet, unsigned length, struct interface_info *ip)
+{
+    RAD_MESSAGE *rad_ptr;
+    rad_ptr = (RAD_MESSAGE *) packet;
+    struct dhcp_packet tmp_dhcp_pack;
+    XID_NODE * xid;
+    uint32_t user_interface;
+
+    is_unfinished_requests_overflow(&unfinished_requests, DEC, MAX_UNFINISHED_REQ, ZEROING_UNFIN_REQ);
+
+    if(rad_ptr->header.code != RAD_ACCESS_ACK)
+    {
+        if(rad_ptr -> header.code == RAD_ACCESS_NAK)    /* Access Reject приходит в случае отсутствия */
+        {                                               /* пула адресов для данного сегмента */
+            log_info ("INFO: Recv RADIUS ACCESS_NAK - no free leases for subnet. Drop this packet.");
+        }
+        else log_info ("INFO: Recv RADIUS, code: %u - this is not Request-Accept. Drop this packet.",
+                        rad_ptr -> header.code);
+        return 0;
+    }
+    length = translate_rad_to_dhcp((RAD_MESSAGE *)packet, &tmp_dhcp_pack, length, &user_interface);
+    if(!length)
+        return 0;
+    if((signed int)length < 0)
+    {
+        log_debug("WARN: invalid client IP address. BOOTREPLY aborted.");
+        return 0;
+    }
+    if(!tmp_dhcp_pack.xid)
+    {
+        log_info("INFO Get DHCP-packet with zero xid.");
+        xid = search_xid(tmp_dhcp_pack.chaddr, tmp_dhcp_pack.xid, user_interface);
+    }
+    else
+        xid = search_xid(tmp_dhcp_pack.chaddr, tmp_dhcp_pack.xid, ip->primary_address.s_addr);
+    if(!xid)
+    {
+        log_info ("INFO: Get unknown xid %d with unknown user interface: %u in RADIUS. BOOTREPLY aborted.", 
+                            tmp_dhcp_pack.xid, user_interface);
+        return 0;
+    }
+    if(calc_reply_auth(rad_ptr, xid->req_header.auth, rad_secret)!=0)
+    {
+        log_info ("WARN: Invalid autentificator in RADIUS. BOOTREPLY aborted.");   
+        return 0;
+    }
+    
+    if(rad_ptr->header.id != xid->req_header.id)
+    {
+        log_info ("INFO: Invalid RADIUS-id in RADIUS-reply. BOOTREPLY aborted.");
+        return 0;
+    }
+    memset(packet, 0, sizeof(struct dhcp_packet));
+    memcpy(packet, &tmp_dhcp_pack, sizeof(struct dhcp_packet));
+    if(xid -> allow_cache)
+    {   /* Если разрешено кэширование то */
+        /* копируем DHCPREPLY в поле out_packet для хранения в кэше */
+        memcpy(&xid -> out_packet, packet, length);
+        xid -> out_pack_len = length;
+    }
+	delete_xid_if_mac_not_equal(packet -> yiaddr.s_addr, packet -> chaddr);
+    return length;
+}
+
+/*
+	Функция анализирующая полученный от клиента DHCP запрос
+*/
+int dispatch_dhcp_packet(struct dhcp_packet * packet, 
+                            unsigned * length, 
+                            struct interface_info *ip,
+                            int * rad_msg_len,
+                            uint8_t * from_cache,
+                            RAD_MESSAGE * auth_pack)
+{
+    static DHCP_REQ_INFO new_req_info;
+    XID_NODE * xid_ptr;
+    uint16_t dhcp_type = 0;
+    char str_dhcp_type[20] = "";
+    float qps = 0;                      /* Общее усреднённое число запросов в секунду */
+    float qps_host = 0;                 /* Усреднённое число запросов от конкретного хоста */
+
+    /* Определяем тип DHCP запроса */
+    bzero(str_dhcp_type, sizeof(str_dhcp_type));
+    dhcp_type = get_dhcp_type(packet, str_dhcp_type);
+
+    if(dhcp_type == DHCPDISCOVER || dhcp_type == DHCPREQUEST)
+    {
+        qps = count_queries_per_sec();
+
+        /* Начинаем вычисления числа запросов для каждого отдельно взятого хоста в секунду */
+        qps_host = qps_hosts(packet -> chaddr, 0);
+        if(qps_host > max_qps_from_host)
+        {
+            log_info("ERROR: Exceed maximum DHCP queries per second (%g/sec) for host %s/%s. Maximum: %g/sec. Drop request.", 
+                        qps_host,
+                        print_hw_addr (packet -> htype, packet -> hlen, packet -> chaddr),
+                        ip -> name,
+                        max_qps_from_host);
+            return -1;
+        }
+        /* Если клиент не блокирован по числу запросов в секунду, 
+        то добавляем временной штамп в кольцевой буфер */
+        if(ring_ptr >= RING_MAX)
+                        ring_ptr = 0;
+        normal_clients_counter[ring_ptr] = time(0);
+        ++ring_ptr;
+        
+        bzero(&new_req_info, sizeof(new_req_info));
+        delete_old_xid(xid_list_top);   /* Удаляем старые узлы dhcp-транзакций */
+        if(dhcp_type == DHCPREQUEST)    /* Если тип запроса DHCPREQUEST то возможно мы уже имеем */
+        {                               /* кэшированный ответ */
+            /* Возвращает структуру содержащую адрес клиента, запрашиваемый адрес */
+            get_dhcp_request_info(packet, &new_req_info); /* и идентификатор сервера */
+            xid_ptr = search_xid(packet -> chaddr, 0, ip->primary_address.s_addr);
+            if( xid_ptr /* Если соответствующий узел найден */
+                && xid_ptr -> allow_cache /* и разрешено его кэширование */
+                && xid_ptr -> out_packet.op) /* и в нём уже имеется ответ сервера */
+            {   /* Информация для этого адреса уже есть в кэше */
+                /* Отправляем клиенту кэшированный ответ заменив поле типа */
+                switch(xid_ptr -> server_msg_type)
+                {   /* В этих случаях XID содержит ответный пакет */
+                    case DHCPOFFER:
+                    case DHCPACK:
+                    case DHCPNAK:
+                        if(!( dhcp_type = update_xid_node(xid_ptr, packet, *length)) )
+                                                                    return -1;
+                        /* Отдаём клиенту кэшированный пакет */
+                        memcpy(packet, &xid_ptr -> out_packet, sizeof(struct dhcp_packet));
+                        *length = xid_ptr -> out_pack_len;
+                        log_info ("Received %s (%s/%s) found in dhcrelay cache.", str_dhcp_type,
+                                    print_hw_addr (packet -> htype, MAC_ADDR_LEN , packet -> chaddr),
+                                    ip -> name);
+                        *from_cache = 1;
+                        if(qps > max_qps)
+                                    log_error("WARN: Exceed maximum DHCP queries per second - %g/sec (%g/sec max)."
+                                                " But DHCP-response found in cache. Sending packet...",
+                                                qps, max_qps);
+						xid_ptr -> client_msg_type = DHCPREQUEST;
+                        return 1;
+                    break;
+                }
+            }
+        }    
+        
+        /* Тип DHCP запроса - DHCPDISCOVER, либо запрещено кэширование для данного клиента,
+        значит необходима пересылка данных на RADIUS сервер. */
+        /* Проверяем не превышено ли пороговое значение числа запросов в секунду */
+        if(qps > max_qps)
+        {   /* Если превышен, то дропаем новый запрос */
+            log_error("ERROR: Exceed maximum DHCP queries per second - %g/sec (%g/sec)",
+                    qps, max_qps);
+            log_error("ERROR: Drop %s from %s/%s !", str_dhcp_type,
+                    print_hw_addr (packet -> htype, packet -> hlen, packet -> chaddr),
+					ip -> name);
+            return -1;
+        }
+
+        /* Если наш запрос не найден в кэше, то проверяем не достиг-ли кэш максимального размера */
+        if( dhcp_cache_len >= MAX_CACHE_LIST_LEN && 
+            !search_xid(packet -> chaddr, 0, ip->primary_address.s_addr)/* Если запрос найден в кэше */
+        )   /* то нет нужды удалять старый узел, либо дропать запрос, т.к. старый узел будет удалён в ходе */
+        {   /* добавления нового и размер кэша не увеличится */
+            if(delete_oldest_cache_node(xid_list_top) < 0)
+            {
+                log_error("ERROR: Can't delete oldest cache node! Drop %s from %s/%s", str_dhcp_type,
+                    print_hw_addr (packet -> htype, packet -> hlen, packet -> chaddr),
+					ip -> name);
+                return -1;
+            }
+            log_info("WARN: Cache overflow. The oldest cache node has been dropped. Cache length now is: %u", 
+                            dhcp_cache_len);
+        }
+        /* Добавляем новый узел в кэш */
+        xid_ptr = add_xid(packet, dhcp_type, &new_req_info, ip->primary_address.s_addr);
+        uint8_t mac_addr[STR_MAC_ADDR_LEN];
+        bzero(mac_addr, sizeof(mac_addr));
+        sprintf(mac_addr, "%02x:%02x:%02x:%02x:%02x:%02x", 
+                packet->chaddr[0], packet->chaddr[1], packet->chaddr[2], 
+                packet->chaddr[3], packet->chaddr[4], packet->chaddr[5]);
+        *rad_msg_len = assemble_auth_message(auth_pack, mac_addr, rad_users_passwd, 
+                                                ip->primary_address.s_addr);
+        if(!xid_ptr) /* Если не добавлен новый узел в список, значит ответ */
+        {            /* сервера уже есть в кэше */
+            xid_ptr = search_xid(packet->chaddr, packet->xid, ip->primary_address.s_addr);
+            if(!xid_ptr)
+            {
+                log_error("WARN: search_xid() in relay() return zero.");
+                return -1;
+            }
+            xid_ptr -> req_header.id = auth_pack -> header.id;
+			xid_ptr -> client_msg_type = dhcp_type;
+            if(new_req_info.client_addr.s_addr)
+                memcpy(&xid_ptr -> request_info, &new_req_info, sizeof(DHCP_REQ_INFO));
+            memcpy(xid_ptr -> req_header.auth, &auth_pack -> header.auth, MD5_LEN);
+        }                                              
+        else 
+        {
+            memcpy(&xid_ptr -> req_header, &auth_pack -> header, sizeof(RAD_HEADER));   
+            if(new_req_info.client_addr.s_addr)
+                memcpy(&xid_ptr -> request_info, &new_req_info, sizeof(DHCP_REQ_INFO));
+        }
+    }
+    else
+    {
+        log_info ("Ignored BOOTREQUEST type: %s from client %s (%s/%s)",
+                        (strlen(str_dhcp_type))? str_dhcp_type: "'unknown'",
+                        (strlen(str_dhcp_type))? inet_ntoa(packet -> ciaddr): "'unknown'",
+                        (strlen(str_dhcp_type))? 
+                            print_hw_addr (packet -> htype, packet -> hlen, packet -> chaddr):"'unknown'",
+                        ip -> name);
+        return 0; 
+    }
+    return 1;
+}
+
+/* END PATCH CODE */
+/******************************************************************/
+
+void relay (ip, packet, length, from_port, from, hfrom)
+    struct interface_info *ip;
+    struct dhcp_packet *packet;
+    unsigned length;
+    unsigned int from_port;
+    struct iaddr from;
+    struct hardware *hfrom;
+{
+/* START PATCH COMMENT */
+/*    struct server_list *sp;*/
+/* END PATCH COMMENT */
+    struct sockaddr_in to;
+    struct interface_info *out;
+    struct hardware hto, *htop;
+
+/* START PATCH CODE */
+    RAD_MESSAGE auth_pack;
+    int rad_msg_len = 0;
+    uint16_t dhcp_type = 0;
+    register int i = 0;
+    time_t now;
+    char str_dhcp_type[20] = "";
+    uint8_t from_cache = 0;
+    static char to_addr[50];            /* Используется для вывода в лог информации о том,
+                                        куда пересылается BOOTRESPONSE */
+    int ret = 0;
+
+    if(time(&now) == (time_t) -1)
+    {
+        log_error("time() failed! Processing packet aborted.");
+        return;
+    }
+
+    if(ntohs(from_port) == radius_port)/* Если данный порт, то это ответ радиус-сервера */
+    {
+        /* Обрабатываем полученный ответ RADIUS сервера */
+        if( !(length = dispatch_radius_packet(packet, length, ip)) )
+        {
+            return;
+        }
+    }
+    else /* Получен DHCP пакет */
+    {   
+        if(ntohs(from_port) != DHCP_CLIENT_PORT)
+        {
+            log_info("INFO: Ignored packet from port %u (if: %s)", ntohs(from_port), ip -> name);
+            return;
+        }
+        ret = dispatch_dhcp_packet(packet, &length, ip,
+                            &rad_msg_len, &from_cache, &auth_pack);
+        if(!ret)
+            return;
+        if(ret < 0)
+        {
+            log_info("INFO: Can't processed DHCP packet.");
+            return;
+        }
+    }
+    /* END PATCH CODE */    
 	if (packet -> hlen > sizeof packet -> chaddr) {
 		log_info ("Discarding packet with invalid hlen.");
 		return;
@@ -355,7 +2132,17 @@
 	}
 
 	/* If it's a bootreply, forward it to the client. */
-	if (packet -> op == BOOTREPLY) {
+	if ((packet -> op == BOOTREPLY)
+/* START PATCH CODE */
+        && 
+            (
+                ntohs(from_port) == radius_port
+                ||
+                from_cache
+            ) 
+        ) 
+/* END PATCH CODE */
+        {
 		if (!(packet -> flags & htons (BOOTP_BROADCAST)) &&
 			can_unicast_without_arp (out)) {
 			to.sin_addr = packet -> yiaddr;
@@ -394,16 +2181,36 @@
 			return;
 		}
 
+        bzero(to_addr, sizeof(to_addr));
+        strcat(to_addr, inet_ntoa (to.sin_addr));
+        if(packet -> flags & htons (BOOTP_BROADCAST))
+        {
+            strcat(to_addr, " (yiaddr: ");
+            strcat(to_addr, inet_ntoa(packet -> yiaddr));
+            strcat(to_addr, ")");
+        }
 		if (send_packet (out,
 				 (struct packet *)0,
 				 packet, length, out -> primary_address,
 				 &to, htop) < 0) {
 			++server_packet_errors;
 		} else {
-			log_debug ("forwarded BOOTREPLY for %s to %s",
-			       print_hw_addr (packet -> htype, packet -> hlen,
-					      packet -> chaddr),
-			       inet_ntoa (to.sin_addr));
+/* START PATCH CODE */
+                bzero(str_dhcp_type, sizeof(str_dhcp_type));
+                get_dhcp_type(packet, str_dhcp_type);
+                if(from_cache)
+                {
+                    log_info ("Creating %s for %s and forwarded to %s",
+                                str_dhcp_type,
+                                print_hw_addr (packet -> htype, packet -> hlen, packet -> chaddr), 
+                                to_addr);
+                }else
+                {
+                    log_info ("Convert RADIUS-reply for %s in %s and forwarded to %s",
+			                    print_hw_addr (packet -> htype, packet -> hlen, packet -> chaddr), 
+                                str_dhcp_type, to_addr);
+                }
+/* END PATCH CODE */       
 
 			++server_packets_relayed;
 		}
@@ -435,7 +2242,92 @@
 
 	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
 	   servers. */
-	for (sp = servers; sp; sp = sp -> next) {
+
+/* START PATCH CODE */    
+    static union 
+	{
+		uint8_t packbuf [4097]; /* Packet input buffer.
+					 	 Must be as large as largest
+						 possible MTU. */
+		struct dhcp_packet packet;
+	} u;
+    
+    memset(&u, 0, sizeof(u));
+    memcpy(&u.packbuf, &auth_pack, rad_msg_len);
+    
+    /* Если задано время через которое производится попытка 
+        тестирования упавшего первичного RADIUS сервера на 
+        работоспособность, то пробуем переключиться на него */
+    if(time_to_restore_primary_server_index
+        &&
+        (server_index != PRIMARY_SERVER_INDEX)
+    )
+    {
+        if( (now = time(0)) == ((time_t) -1))
+        {
+            log_fatal("FATAL: time() error! Abort execution. Quit.");
+            exit (-1);
+        }
+        if( (now - primary_server_down_time) > 
+                    time_to_restore_primary_server_index)
+        {
+            server_index = PRIMARY_SERVER_INDEX;
+            log_info("INFO: Primary server down %u seconds earlier. Attempting change RADIUS server to primary: %s", 
+                now - primary_server_down_time,
+                inet_ntoa(servers[server_index].sin_addr));
+            unfinished_requests = 0;
+        }
+    }
+
+    /* Проверяем - не ушёл ли RADIUS-сервер в down */
+    if(is_unfinished_requests_overflow(&unfinished_requests, INC, MAX_UNFINISHED_REQ, ZEROING_UNFIN_REQ))
+    {
+        log_info("ERROR: RADIUS server %s down!", inet_ntoa(servers[server_index].sin_addr));
+        /* Если упал первичный сервер, то отмечаем последнее время падения */
+        if(server_index == PRIMARY_SERVER_INDEX)
+        {
+            primary_server_down_time = time(0);
+            if(primary_server_down_time == ((time_t) -1))
+            {
+                log_fatal("FATAL: time() error! Abort execution. Quit.");
+                exit (-1);
+            }
+        }
+        /* Если есть дублирующие сервера, то переключаемся на следующий */
+        if(servers_count > 1)
+        {
+            if(server_index == servers_count - 1)
+                server_index = 0;
+            else ++server_index;
+            log_info("WARN: Changing RADIUS server to %s (#%d)", 
+                inet_ntoa(servers[server_index].sin_addr), server_index + 1 );
+            unfinished_requests = 0;
+        }
+        else log_info("ERROR: can't change the server, because there are no duplicating servers.");
+    }
+    servers[server_index].sin_port = htons(radius_port); /* Подменяем порт назначения на порт RADIUS-сервера */
+    if (send_packet ((fallback_interface
+              ? fallback_interface : interfaces),
+             (struct packet *)0,
+             &u.packet, rad_msg_len, ip -> primary_address,
+             &servers[server_index], (struct hardware *)0) < 0) {
+        ++client_packet_errors;
+    } else 
+    {
+        bzero(str_dhcp_type, sizeof(str_dhcp_type));
+        get_dhcp_type(packet, str_dhcp_type);
+        log_info ("Convert %s %s/%s to RADIUS and forwarded to %s",
+               str_dhcp_type,
+               print_hw_addr (packet -> htype, packet -> hlen,
+                      packet -> chaddr),
+                ip -> name, 
+               inet_ntoa (servers[server_index].sin_addr));
+        ++client_packets_relayed;
+    }
+/* END PATCH CODE */
+
+/* START PATCH COMMENT */
+	/*for (sp = servers; sp; sp = sp -> next) {
 		if (send_packet ((fallback_interface
 				  ? fallback_interface : interfaces),
 				 (struct packet *)0,
@@ -447,19 +2339,42 @@
 			       print_hw_addr (packet -> htype, packet -> hlen,
 					      packet -> chaddr),
 			       inet_ntoa (sp -> to.sin_addr));
-			++client_packets_relayed;
-		}
-	}
-				 
+			++client_packets_relayed;*/
+/* END PATCH COMMENT */
 }
 
+/* START PATCH CODE */
+static void patch_help (void)
+{
+	log_info ("Usage: dhcrelay [-p <port>] [-d] [-D] <-i interface> [-q] [-a] [-u] <-s shared-secret>\n"
+		"               [-c count] [-A length] "
+		"[-m append|replace|forward|discard] [-T time to recover primary server]\n"
+		"               <-P radius-users-password> [-R RADIUS-port] [-f max DHCP QPS from client]\n"
+        "               [-t cache-timeout] <server1 [... serverN]> [-Q max QPS from all clients]\n\n"
+"Patch version: " PATCH_VERSION);
+	log_info ("Patch keys:\n"
+				"\t-s <secret-string> - RADIUS shared secret.\n"
+				"\t-T <seconds> - delay before attempt switching to primary server if already switched to reserve server.\n"
+				"\t-P <password-string> - password to ALL RADIUS users.\n"
+				"\t-R <port> - RADIUS server port. Default: 1812.\n"
+				"\t-f <queries-frequency> - maximum count queries per seconds from DHCP client. Default: 4.\n"
+				"\t-t <seconds> - time during cache entry keep stored. Default: 86400 seconds.\n"
+				"\t-Q <queries-frequency> - maximum count queries per seconds from all DHCP clients. Default: 10\n"
+				"\t-u - updating ARP entries in kernel.\n"
+				"\t-h - print this help message."
+				);
+	exit(1);
+}
+/* END PATCH CODE */
+
 static void usage ()
 {
-	log_fatal ("Usage: dhcrelay [-p <port>] [-d] [-D] [-i %s%s%s%s",
-		"interface] [-q] [-a]\n                ",
-		"[-c count] [-A length] ",
-		"[-m append|replace|forward|discard]\n",
-		"                [server1 [... serverN]]");
+	log_fatal ("Usage: dhcrelay [-p <port>] [-d] [-D] <-i interface> [-q] [-a] [-u] <-s shared-secret>\n"
+		"               [-c count] [-A length] "
+		"[-m append|replace|forward|discard] [-T time to recover primary server]\n"
+		"               <-P radius-users-password> [-R RADIUS-port] [-f max DHCP QPS from client]\n"
+        "               [-t cache-timeout] <server1 [... serverN]> [-Q max QPS from all clients]\n\n"
+"Patch version: " PATCH_VERSION);
 }
 
 int write_lease (lease)
@@ -910,3 +2825,4 @@
 
 	return length;
 }
+
diff -ruN dhcp-3.0.5/relay/rad2dhcp.h dhcp-3.0.5.patched/relay/rad2dhcp.h
--- dhcp-3.0.5/relay/rad2dhcp.h	1970-01-01 03:00:00.000000000 +0300
+++ dhcp-3.0.5.patched/relay/rad2dhcp.h	2008-11-13 13:54:02.000000000 +0300
@@ -0,0 +1,205 @@
+#include <openssl/md5.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+/*
+#define FULL_DEBUG_OUT 
+#define DEBUG_TR_RAD_TO_DHCP 
+*/
+
+#define PATCH_VERSION       "0.5.3 (2008.11.11) (with ARP cache updating)"
+
+#define RADIUS_PORT         1812
+#define DHCP_CLIENT_PORT    68
+#define RADIUS_MIN_PACK_LEN 20
+
+/* Коды пакетов протокола Radius */
+#define RAD_ACCESS_REQ      1   /* Запрос доступа (отправка MAC адреса на сервер) */
+#define RAD_ACCESS_ACK      2   /* Доступ подтверждён (успешное получение конфигурационной информации от сервера) */
+#define RAD_ACCESS_NAK      3   /* Доступ запрещён (не удалось получить конфигурацию от сервера) */
+
+/* Значения атрибутов протокола Radius 
+используемые в пакете Access-Request */
+#define USER_NAME_ATTR		1
+#define	USER_PASW_ATTR		2
+#define NAS_PORT            5
+
+/* Значения атрибутов протокола Radius
+используемые в пакете Access-Accept */
+#define RAD_GATEWAY         0xE0
+#define RAD_YADDR           0xE1
+#define RAD_GIADDR          0xE2
+#define RAD_LEASE           0xE3
+#define RAD_CLIENT_MAC      0xE4
+#define RAD_DNS1            0xE5
+#define RAD_DNS2            0xE6
+#define RAD_NETMASK         0xE7
+#define RAD_SID             0xE8
+#define RAD_DOMAIN_NAME     0xE9
+#define RAD_ROUTE           0xEA
+#define RAD_IFINDEX         0xEB
+#define RAD_NO_CACHE        0xEC
+#define RAD_RENEWING_TIME	0xED
+#define RAD_REBINDING_TIME	0xEE
+
+#define SIGSTAT                     64      /* Сигнал при получении которого dhcrelay выдаёт статистику работы в лог */
+#define SIGNAL_SET_PRI_SERVER       63      /* Сигнал, получив который dhcrelay устанавливает server_index */
+                                            /* в значение по умолчанию, т.е. на основной сервер */
+#define SIGNAL_CHANGE_SERV          62      /* При получении этого сигнала dhcrelay устанавливает server_index */
+                                            /* на следующий элемент в массиве серверов если таковой имеется */
+
+/* Опция используемая в dhcp-пакете для 
+указания статических маршрутов в сети */
+#define DHO_STATIC_ROUTE    249
+
+/* Используется в функциях аутентификации RADIUS */
+#define MD5_LEN             16
+#define MAC_ADDR_LEN        6
+#define ENCR_PWD_LEN        16
+#define MAX_SECRET_LEN      128
+
+#define DEFAULT_XID_TIMEOUT         86400   /* Одни сутки */
+#define MAX_CACHE_LIST_LEN          3000    /* Максимальная длина списка кэша запросов */
+#define QUERY_TIME_RING_LEN         350     /* Длина массива служащего для подсчёта частоты запросов */
+#define RING_MAX                    QUERY_TIME_RING_LEN
+#define TIME_DELTA                  10.0    /* Время (сек.) за которое производится подсчёт */
+                                            /* среднего числа DHCP запросов в секунду */
+#define TIME_DELTA_HOST             5.0      /* То же что и TIME_DELTA, только для отдельно взятого хоста */
+#define MAX_DHCP_QPS                10.0    /* Суммарное максимально допустимое число DHCP запросов в секунду */
+#define MAX_QPS_FROM_HOST           4.0     /* Максимально допустимое число DHCP запросов в секунду от одного хоста */
+#define MIN_COUNTER_SIZE            100      /* Минимальная длина динамического массива счётчика запросов */
+
+#define DEFAULT_SERV_ARR_SIZE       10      /* Размер массива серверов по умолчанию */
+#define BASIC_SERVER_INDEX          0       /* Индекс массива серверов обозначающий основной сервер, */
+                                            /* запросы к которому отправляются в штатном режиме работы релея */
+#define PRIMARY_SERVER_INDEX        BASIC_SERVER_INDEX
+#define INC                         1
+#define DEC                         0
+#define MAX_UNFINISHED_REQ          30
+#define ZEROING_UNFIN_REQ           50
+
+#define DEF_ARP_CACHE_PERM_LEN		1024
+#define PROC_ARP					"/proc/net/arp"
+#define STR_MAC_ADDR_LEN			18
+/* Используется что бы определить начало поля dhcp-options */
+const uint8_t magic_cookie[] = {99, 130, 83, 99};
+
+unsigned char rad_secret[17];       /* Пароль для доступа на Radius-сервер */
+unsigned char rad_users_passwd[17]; /* Пароль пользователя, в качестве имени применяется MAC адрес клиента */
+
+/* Структура описывающая заголовок пакета Radius */
+typedef struct rad_header
+{
+	uint8_t code;
+	uint8_t id;
+	uint16_t pack_len;
+	uint8_t auth[16];
+}RAD_HEADER;
+
+/* Полный формат пакета Radius */
+typedef struct rad_message
+{
+	RAD_HEADER	header;
+	uint8_t		message[4096 - sizeof(RAD_HEADER)];
+}RAD_MESSAGE;
+
+/* Структура входящая в состав xid_list_node и описывающая 
+DHCP-запрос полученный от клиента */
+typedef struct dhcp_req_info
+{
+    struct in_addr client_addr;
+    uint32_t    server_id;    
+    uint32_t    req_addr;
+}DHCP_REQ_INFO;
+
+/* Структура узла служащего для обработки DHCP ответов - определения
+типа DHCP ответа отправляемого клиенту. Одновременно служит кэшем DHCP ответов. */
+typedef struct xid_list_node
+{
+    uint8_t     allow_cache;                /* Булева переменная, принимает значение TRUE если разрешено кэширование DHCP ответов */
+    struct dhcp_packet out_packet;          /* Структура для кэширования BOOTREPLY */
+    uint32_t    out_pack_len;               /* Хранит длину кэшированного пакета что бы не высчитывать её перед повторной 
+                                               отправкой при использовании кэша */
+    uint32_t    if_index;                   /* Индекс интерфейса на котором был получен запрос от клиента */
+	uint8_t		client_msg_type;
+    uint8_t     server_msg_type;			/* Возможные состояния транзакции: XID_DISCOVER|XID_OFFER|XID_REUQEST|XID_ACK */
+    uint32_t    xid;                        /* Копируется из dhcp-пакета запроса конфигурации */
+    uint8_t     chaddr[MAC_ADDR_LEN];       /* MAC адрес клиента запросившего конфигурацию */
+    uint16_t    dhcp_flags;                 /* Флаги из dhcp-пакета запроса конфигурации. Необходимо для учёта BROADCAST */
+    DHCP_REQ_INFO   request_info;           /* Служит для определения валидности запроса клиента, т.е. соотвествия 
+                                            запрашиваемого адреса предлагаемому */
+    RAD_HEADER  req_header;                 /* Заголовок запроса Radius отправленного на сервер для получения конфигурации */
+    time_t      timestamp;                  /* Используется для вычисления устаревших узлов кэша */ 
+    struct      xid_list_node *prev;        /* Указатель на предыдущий узел списка транзакций */
+    struct      xid_list_node *next;        /* Указатель на последующий узел списка транзакций */
+} XID_NODE;
+
+typedef struct counter_node
+{
+    uint8_t     mac_addr[MAC_ADDR_LEN];
+    time_t      timestamp;
+} COUNTER_NODE;
+
+/* Структура для хранения информации о статическом ARP кэше сервера */
+typedef struct arp_entry
+{
+	struct in_addr ip;
+	unsigned char mac[MAC_ADDR_LEN];
+} ARP_ENTRY;
+
+/* Функции применяемые для работы с Radius-сервером */
+
+/* Печатам текущий список узлов dhcp-транзакций */
+void print_stat(int sign);
+/* Шифруем пароль для передачи в radius-пакете */
+int encrypt_passwd(char * passwd, uint8_t * encr_passwd, 
+                    char *shared_key, uint8_t * auth);
+/* Сборка пакета radius-Access-Request для отправки на radius-сервер */
+int assemble_auth_message( RAD_MESSAGE *access_req, char *user_name, 
+                            char *passwd, uint32_t subnet);
+/* Подсчёт контрольной суммы ответа radius-Access-Accept */
+static int calc_reply_auth(RAD_MESSAGE *packet, uint8_t *original_auth, const char *secret);
+/* Добавляем новый узел в список узлов dhcp-транзакций */
+/*XID_NODE * add_xid(uint32_t xid, uint8_t * hw_addr, uint8_t dhcp_type, uint16_t dhcp_flags,  
+                                                DHCP_REQ_INFO *request_info, uint32_t if_index);*/
+XID_NODE * add_xid(struct dhcp_packet *packet, uint8_t dhcp_type,
+                        DHCP_REQ_INFO *request_info, uint32_t if_index);
+/* Определение dhcp-типа пакета (DHCPDISCOVER/DHCPREQUEST/etc...) */
+uint16_t get_dhcp_type(struct dhcp_packet *request, char * str_dhcp_type);
+/* Присваивает option_value значение запрошенной опции находящейся в DHCP пакете */
+int get_dhcp_option(struct dhcp_packet *request, uint16_t packet_len, int req_option, void * option_value);
+/* Удаление узла dhcp-транзакции */
+inline XID_NODE * delete_xid(XID_NODE *xid_n);
+/* Удаление старых узлов dhcp-транзакций, у которых поле истекло время ожидания */
+int delete_old_xid(XID_NODE * start_xid_list);
+/* Удаление дублирующихся по MAC-адресу узлов dhcp-транзакций */
+int del_duplicate_xid(uint32_t xid, uint8_t * hw_addr, uint32_t if_index);
+/* Поиск узла dhcp-транзакции либо только по MAC-адресу, либо по MAC-адресу и значению xid */
+XID_NODE * search_xid(uint8_t * hw_addr, uint32_t xid, uint32_t if_index);
+/* Преобразование dhcp-опции 249 (Static routes for OS Windows) 
+из строкового представления в побайтовое.*/
+int translate_249(unsigned char * inp_str, int out_len, uint8_t * out);
+/* Преобразование пакета radius в dhcp-пакет готовый для отправки клиенту запросившему конфигурационную информацию */
+int translate_rad_to_dhcp(RAD_MESSAGE *rad_pack, struct dhcp_packet * out_packet, uint16_t received_length,
+                           uint32_t * user_interface ); /* Параметр необходим для корректной обработки */
+/* Устанавливает указанный тип DHCP сообщения, возвращает предыдущее значение типа */
+uint16_t set_dhcp_type(struct dhcp_packet *request, uint16_t new_type);
+/* Обновляет поля DHCP пакета в кэше перед отправкой пакета клиенту */
+uint16_t update_xid_node(XID_NODE * xid, struct dhcp_packet * packet, unsigned pack_len);
+/* Функция удаляющая самый старый запрос в DHCP кэше */
+int delete_oldest_cache_node(XID_NODE * cache_node);
+/* Подсчитывает среднее число DHCP запросов в секунду за время TIME_DELTA секунд */
+float count_queries_per_sec(void);
+/* Устанавливаем обработчик для смены индекса массива RADIUS серверов на значение по умолчанию */
+void set_default_server(int sign);
+/* Устанавливаем обработчик для смены индекса массива  */
+void change_server(int sign);
+/* Берём ARP-кэш статически привязанных MAC адресов. ДАННЫЙ ВАРИАНТ ПОДХОДИТ ТОЛЬКО ДЛЯ LINUX!!! */
+int get_arp_cache_perm(void);
+/* Функция обновляющая статический ARP кэш на роутере, если для IP сменился MAC */
+int update_arp_cache_perm(uint32_t ip, unsigned char * mac);
+/* Функция удаляющая кэшированные xid узлы при изменении соответствия IP - MACклиента */
+int delete_xid_if_mac_not_equal(uint32_t ip, uint8_t * mac);
+/* Печтает help-info для ключей патча*/
+static void patch_help (void);
+
+
