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 ] [-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] [-Q max QPS from all clients]\n\n" +"Patch version: " PATCH_VERSION); + log_info ("Patch keys:\n" + "\t-s - RADIUS shared secret.\n" + "\t-T - delay before attempt switching to primary server if already switched to reserve server.\n" + "\t-P - password to ALL RADIUS users.\n" + "\t-R - RADIUS server port. Default: 1812.\n" + "\t-f - maximum count queries per seconds from DHCP client. Default: 4.\n" + "\t-t - time during cache entry keep stored. Default: 86400 seconds.\n" + "\t-Q - 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 ] [-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 ] [-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] [-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 +#include +#include +/* +#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); + +