Напишу полноценный DHCP сервер работающий с SQL СУБД.
Если не делать полноценный сервер, а расчитанный на работу с релеем, (не нужно лезть на канальный уровень), при этом если схема "один порт - один адрес" то тут делать нечего.
основной процесс типа такого:
82 опцию можно распарсить так (это длинк, по-хорошему нужно делать боле универсально):
нужно тока парсить опции например так (магик-куку луше конешно вынести):
сама структура пакета берется из isc:
В общем-то это практически всё
- осталось тока генерить пакет из sql. Полностью свою реализацию не показываю - больно она кривая... Года полтара назад забросил так и не закончив.
основной процесс типа такого:
Код: Выделить всё
#define SERVADDR "10.255.255.254"
#define SERVPORT 67
. . .
bzero ( &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
inet_pton (AF_INET, text_addr, &serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons ( SERVPORT );
bzero ( &clientadr, sizeof(clientadr));
if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0){
perror(NULL);
return -1;
}
if ( bind (sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
perror(NULL);
return -1;
}
. . .
for (;;) {
printf ("ожидание\n");
n = recvfrom (sockfd, (void *)&mesg, DHCP_OPTION_LEN, 0, (struct sockaddr *)&clientadr, &len);
printf ("пакет получен\n");
switch ( check_options_id (&mesg, n) ) {
case DHCPDISCOVER :
prepare_dhcp_msg (&mesg, DHCPOFFER);
break;
case DHCPREQUEST :
prepare_dhcp_msg (&mesg, DHCPACK);
break;
case DHCPINFORM :
prepare_dhcp_msg (&mesg, DHCPACK);
break;
case DHCPRELEASE :
printf ("не реализовано DHCPRELEASE\n");
break;
case DHCPDECLINE :
printf ("не реализовано DHCPDECLINE\n");
break;
default:
continue;
}
printf ("оправка сообщения\n");
inet_ntop (clientadr.sin_family, &clientadr.sin_addr.s_addr, addr_temp, sizeof (addr_temp));
printf ("Адрес релая %s\n", addr_temp);
clientadr.sin_port = htons ( SERVPORT );
if( sendto (sockfd, (void *)&mesg, DHCP_OPTION_LEN, 0, (struct sockaddr *)&clientadr, sizeof(clientadr)) < 0){
perror(NULL);
return (1);
}
}
Код: Выделить всё
int
op_82_parse ( uint8_t *op_82, int max_lent)
{
int i = 0 ,j = 0;
uint8_t op_82_lent = *op_82, sub_op, sub_op_lent, *circ_id, circ_id_lent, *rem_id, rem_id_lent, port;
uint16_t vlan;
printf ("_________op_82_parse_____\n");
op_82++;
while (i < op_82_lent ) {
sub_op = op_82[i];
if ( ( sub_op_lent = op_82[++i] ) > (op_82_lent - i) ) {
printf ("неверная длинна подопции %d\n", sub_op);
return -1;
}
i++;
switch ( sub_op ) {
case 1 :
circ_id_lent = sub_op_lent;
circ_id = (op_82 + i);
break;
case 2 :
rem_id_lent = sub_op_lent;
rem_id = (op_82 + i);
break;
}
i += sub_op_lent;
}
vlan = ntohs (*((uint16_t*)(circ_id + 2)));
printf ("___unit:%d port:%d vlan:%d\n",circ_id[4], circ_id[5], vlan);
for (j=0; j < circ_id_lent; j++) {
printf ("%02x ",circ_id[j]);
}
printf ("\n");
for (j=0; j < rem_id_lent; j++) {
printf ("%02x ",rem_id[j]);
}
printf ("\n");
return 0;
}
Код: Выделить всё
unsigned char
check_options_id ( struct dhcp_packet *message, int msg_lent)
{
unsigned char *options = message->options, magic_cookie[] = {99, 130, 83, 99},
curr_op, o53;
int i = 0;
for (; options[i] == magic_cookie[i] && i < 4; i++ );
if ( i < 4 ){
printf ("Не DHCP пакетик\n");
return 0;
}
for (; options[i] != DHO_END && i < msg_lent; i++){
printf ("нашли опцию %d\n", options[i] );
if ( curr_op = options[i] ){
i++;
switch (curr_op) {
case DHO_DHCP_MESSAGE_TYPE:
o53 = options[i+1];
break;
case DHO_DHCP_AGENT_OPTIONS:
op_82_parse ( (options + i), (msg_lent - i));
break;
}
i += options[i];
}
else
i++;
}
print_options (message);
return o53;
}
Код: Выделить всё
struct dhcp_packet {
u_int8_t op; /* 0: Message opcode/type */
u_int8_t htype; /* 1: Hardware addr type (net/if_types.h) */
u_int8_t hlen; /* 2: Hardware addr length */
u_int8_t hops; /* 3: Number of relay agent hops from client */
u_int32_t xid; /* 4: Transaction ID */
u_int16_t secs; /* 8: Seconds since client started looking */
u_int16_t flags; /* 10: Flag bits */
struct in_addr ciaddr; /* 12: Client IP address (if already in use) */
struct in_addr yiaddr; /* 16: Client IP address */
struct in_addr siaddr; /* 18: IP address of next server to talk to */
struct in_addr giaddr; /* 20: DHCP relay agent IP address */
unsigned char chaddr [16]; /* 24: Client hardware address */
char sname [DHCP_SNAME_LEN]; /* 40: Server name */
char file [DHCP_FILE_LEN]; /* 104: Boot filename */
unsigned char options [DHCP_OPTION_LEN];
/* 212: Optional parameters
(actual length dependent on MTU). */
};

И так, я снова с вами.
Но сейчас плавненько восстанавливается и в общем-то намечаются некоторые успехи.
Спасибо всем кто выкладывает файлики с 82й опцией - в ближайшее время думаю пригодятся, хотя надеюсь сделать процесс парсинга достаточно универсальным для любой опции.
Кроме того, хочется мягко намекнуть/попросить кое о чём. Отчасти проект временно заглох в связи со сменой мною работы и вида деятельности по сути - теперь я ни как не связан с провайдингом и DHCP в принципе. Как следствие - весьма ощутимо утратилась мотивация развивать софт этого направления. И так, перейду от намёков к сути: "мотивация" в виде $$$ приветствуется и может весьма подогреть мой личный интерес к выпуску данного продукта в готовом виде.
Конечно это не значит что если вдруг все проигнорируют данную просьбу то я на всех обижусь, напишу что-то и буду продавать за деньги с закрытыми исходниками. Нет! Проект в любом случае будет открытый, и я думаю что в любом случае будет.
Просто действительно хотелось бы ощутить если возможно - материальную мотивацию писать хороший код. Ну и конечно отношение к просьбам/пожеланиям тех кто поможет мне будет несколько более тёплым чем к просьбам остальных
Разумеется я не прошу что бы это было прямо сейчас, пока я всех только обещаниями по факту кормлю. Когда будет что показать, PoC так сказать - тогда и будет не лишним.
И так, кому сколько не жалко (если такие найдутся) - озвучте в личку.
В предыдущие месяцы было катастрофически мало времени заниматься чем-то кроме обязательных дел и работы. Потому на время проект заглох.hellard писал(а):Ага, поддерживаю вопрос. что по dhcp серверу?
Но сейчас плавненько восстанавливается и в общем-то намечаются некоторые успехи.
Спасибо всем кто выкладывает файлики с 82й опцией - в ближайшее время думаю пригодятся, хотя надеюсь сделать процесс парсинга достаточно универсальным для любой опции.
Кроме того, хочется мягко намекнуть/попросить кое о чём. Отчасти проект временно заглох в связи со сменой мною работы и вида деятельности по сути - теперь я ни как не связан с провайдингом и DHCP в принципе. Как следствие - весьма ощутимо утратилась мотивация развивать софт этого направления. И так, перейду от намёков к сути: "мотивация" в виде $$$ приветствуется и может весьма подогреть мой личный интерес к выпуску данного продукта в готовом виде.
Конечно это не значит что если вдруг все проигнорируют данную просьбу то я на всех обижусь, напишу что-то и буду продавать за деньги с закрытыми исходниками. Нет! Проект в любом случае будет открытый, и я думаю что в любом случае будет.
Просто действительно хотелось бы ощутить если возможно - материальную мотивацию писать хороший код. Ну и конечно отношение к просьбам/пожеланиям тех кто поможет мне будет несколько более тёплым чем к просьбам остальных

Разумеется я не прошу что бы это было прямо сейчас, пока я всех только обещаниями по факту кормлю. Когда будет что показать, PoC так сказать - тогда и будет не лишним.
И так, кому сколько не жалко (если такие найдутся) - озвучте в личку.
И так, как по-моему (т.е. в моей реализации) это должно работать. Обращаю внимание что основной целью не является привязка сервера к netup'овскому биллингу и какому-либо оборудованию. Основная цель - возможность прикрутить сервер по возможности к большему числу СУБД с возможностью наиболее универсальной настройки.
В конфиге используется (пока) 3 sql запроса. Для dhcpdiscover, dhcprequest & dhcprelease. Покажу суть на примере первого:
QueryDiscover - название опции конфига. Далее сам запрос. Обязательно в запросе должно быть соблюдено только одно условие - select должен делать выборку по полям code & value именно в таком порядке. Которые соответственно обозначают код DHCP опции и её значение. Разумеется важны не сами названия полей, а суть возвращаемых базой значений.
Откуда будет делаться выборка (из таблицы, view, или какого-нибудь порождения набора данных через union, или вообще что угодно) - совершенно не важно.
Далее рассмотрим секцию where. В принципе в неё можно написать какое угодно условие, удовлетворяющее вашим задачам. Конечно самое интересное - использование переменной $CLI-ETHER-ADDR$. Очевидно что делать фиксированный набор переменных - недальновидно. Потому все необходимые переменные для условия можно описать в конфиге. Пример объявления переменной в конфиге:
h - значит что переменная берётся из заголовка DHCP сообщения (header) а не из поля опций.
28 - указывает смещение от начала заголовка. На 28м байте начинается client hardware address.
6 - длина опции. Понятно что для ethernet'а это будет 6.
Но, как можно заметить - сделать только такие переменные будет недостаточно, например для решения той же пресловутой проблемы с 82й опцией. Потому переменные из поля "опции" можно получать с различными условиями. Ниже дан пример абсолютно бессмысленный на практике, но показывающий принципы работы таких переменных:
Объявляется переменная CLI-NAME. Пояснения к формату:
o - обозначает что переменная получается из поля "опции" (options) dhcp запроса.
60 - DHCP обозначает код этой опции (client-name).
Далее идёт набор условий. Каждое условие представляет из себя подобие стандартного тернарного оператора многих языков программирования. Пояснение:
(0=00) - первое условие. Слева от = смещение в байтах от начала значения опции (опции 60 в данном случае). Справа - байтовая последовательность которая сравнивается со значением опции начиная с этого смещения. В данном случае это значит: "если начиная с байта 0 идёт последовательность байт 0x00".
0:2 - выполняется если условие верно. 0 - смещение в байтах от начала значения опции. 2 - сколько байт взять для получения значения переменной.
| - значит то же самое что else в операторе if(...) ... else ...
Далее должна идти либо такая же пара смещение:значение указывающая что нужно вернуть в случае не соблюдения условия, либо - следующий условный оператор. У нас второе.
(5=64726f70706572) - опять условие, читающееся как: "с 5го байта должна идти последовательность байт 0x64726f70706572".
5:2 - взять 2 байта для значения переменной если условие верно
| - else
0:0 - начиная с нулевого смещения взять все байты опции. Если длина равна нулю - возвращаются все байты начиная с байта смещения и заканчивая концом опции. В случае нулевого смещения это значит - все байты опции. Удобно для применения в качестве поведения "по умолчанию".
Другой пример - допустим вам не нужны условия и вы желаете использовать всё значение опции целиком. Тогда объявление переменной будет выглядеть проще:
Это вернёт в запрос всё значение переменной. В принципе тут так же можно указать смещение:длина и получить строго столько байт, сколько хочется, например: o:60:1:5 - взять 5 байт начиная с 1го из 60й опции.
На всякий случай - значения переменных приходят в запрос в виде строки шестнадцатиричных цифр. Смещение расчитывается начиная с нуля.
Я конечно понимаю что пока конфиг будет немного похож на какой-то "птичий язык" из-за объявлений переменных, но впоследствии, когда будут решены более важные задачи, сделаю встроенные именованные переменные дабы избежать ручного прописывания общеупотребимых опций.
По-моему такой подход должен решить назойливую проблему с распарсиванием 82й опции. Если у кого-то есть мнение почему что-либо может не получиться, и вообще, какие-либо ценные замечания - прошу изложить, и чем раньше тем лучше.
И - есть-ли какие-нибудь принципиальные невозможности стыковки сервера на подобной концепции с тем же netup'ом?
В конфиге используется (пока) 3 sql запроса. Для dhcpdiscover, dhcprequest & dhcprelease. Покажу суть на примере первого:
Код: Выделить всё
QueryDiscover = select code, value from dhcp_data where ether = '$CLI-ETHER-ADDR$'
Откуда будет делаться выборка (из таблицы, view, или какого-нибудь порождения набора данных через union, или вообще что угодно) - совершенно не важно.
Далее рассмотрим секцию where. В принципе в неё можно написать какое угодно условие, удовлетворяющее вашим задачам. Конечно самое интересное - использование переменной $CLI-ETHER-ADDR$. Очевидно что делать фиксированный набор переменных - недальновидно. Потому все необходимые переменные для условия можно описать в конфиге. Пример объявления переменной в конфиге:
По пунктам - Var собственно директива объявления переменной, далее идёт имя переменной используемое как $имя$ в sql запросах. Далее идёт описание формата данных выбираемых из пакета и подставляемых в запрос:Var = CLI-ETHER-ADDR h:28:6 # Ethernet address
h - значит что переменная берётся из заголовка DHCP сообщения (header) а не из поля опций.
28 - указывает смещение от начала заголовка. На 28м байте начинается client hardware address.
6 - длина опции. Понятно что для ethernet'а это будет 6.
Но, как можно заметить - сделать только такие переменные будет недостаточно, например для решения той же пресловутой проблемы с 82й опцией. Потому переменные из поля "опции" можно получать с различными условиями. Ниже дан пример абсолютно бессмысленный на практике, но показывающий принципы работы таких переменных:
Код: Выделить всё
Var = CLI-NAME o:60:(0=00)0:2|(5=64726f70706572)5:2|0:0
o - обозначает что переменная получается из поля "опции" (options) dhcp запроса.
60 - DHCP обозначает код этой опции (client-name).
Далее идёт набор условий. Каждое условие представляет из себя подобие стандартного тернарного оператора многих языков программирования. Пояснение:
(0=00) - первое условие. Слева от = смещение в байтах от начала значения опции (опции 60 в данном случае). Справа - байтовая последовательность которая сравнивается со значением опции начиная с этого смещения. В данном случае это значит: "если начиная с байта 0 идёт последовательность байт 0x00".
0:2 - выполняется если условие верно. 0 - смещение в байтах от начала значения опции. 2 - сколько байт взять для получения значения переменной.
| - значит то же самое что else в операторе if(...) ... else ...
Далее должна идти либо такая же пара смещение:значение указывающая что нужно вернуть в случае не соблюдения условия, либо - следующий условный оператор. У нас второе.
(5=64726f70706572) - опять условие, читающееся как: "с 5го байта должна идти последовательность байт 0x64726f70706572".
5:2 - взять 2 байта для значения переменной если условие верно
| - else
0:0 - начиная с нулевого смещения взять все байты опции. Если длина равна нулю - возвращаются все байты начиная с байта смещения и заканчивая концом опции. В случае нулевого смещения это значит - все байты опции. Удобно для применения в качестве поведения "по умолчанию".
Другой пример - допустим вам не нужны условия и вы желаете использовать всё значение опции целиком. Тогда объявление переменной будет выглядеть проще:
Код: Выделить всё
Var = CLI-NAME o:60
На всякий случай - значения переменных приходят в запрос в виде строки шестнадцатиричных цифр. Смещение расчитывается начиная с нуля.
Я конечно понимаю что пока конфиг будет немного похож на какой-то "птичий язык" из-за объявлений переменных, но впоследствии, когда будут решены более важные задачи, сделаю встроенные именованные переменные дабы избежать ручного прописывания общеупотребимых опций.
По-моему такой подход должен решить назойливую проблему с распарсиванием 82й опции. Если у кого-то есть мнение почему что-либо может не получиться, и вообще, какие-либо ценные замечания - прошу изложить, и чем раньше тем лучше.
И - есть-ли какие-нибудь принципиальные невозможности стыковки сервера на подобной концепции с тем же netup'ом?
В принципе можно поступить так.RomanCh писал(а):Хм, кажется я сам уже понял чего точно будет не хватать. Нужно реализовать возможность использования внутри условия других переменных - что бы например вычислять длины всяких подопций внутри 82й опции.
Набить "стандартных" параметров - например порт, номер влана, MAC. По большому счёту этого достаточно, идентифицировать сам релей можно по IP с которого пришёл запрос.
В конфиге можно посто указывать смещения влана и порта, а SQL в запрос создаваемый пользователем добавлять соотвественно переменные типа _RELAY_ _PORT_ _VLAN_ _MAC_. Примерно так, как это сделано во FreeRadius.
Хотя конешно на вкус на цвет....
Думал о чём-то подобном. Но это помоему несколько напоминает брутфорсArti писал(а):В принципе можно поступить так.
Набить "стандартных" параметров - например порт, номер влана, MAC. По большому счёту этого достаточно, идентифицировать сам релей можно по IP с которого пришёл запрос.
