Вступление
В этой статье я хотел бы, раскрыть некоторые аспекты использования isc dhcpd, поскольку, даже вполне тривиальные задачи, в иных статьях описаны либо неправильно, либо не до конца.
Этим грешат не только рандомные страницы в интернете, но и даже вполне себе официальная документация. Взять хотя бы документацию от восьмой центоси: https://docs.centos.org/en-US/8-docs/advanced-install/assembly_preparing-for-a-network-install/#configuring-a-tftp-server-for-bios-based-clients_preparing-for-a-network-install
Работать то это конечно будет, но в их примере есть лишний код.
И да, наверное в 2020 году уже нужно бы потихоньку двигаться в сторону kea. Однако, это у нас с вами ещё впереди. Сейчас разберёмся над ошибками в dhcpd.
Фабула
Начнём с весьма тривиальной задачи — разделим клиентов по разным адресным пространствам при помощи классов.
Я не знаю почему, но многие не понимают как это делается.
Вот типичный пример https://voxlink.ru/kb/voip-devices-configuration/dhcp-vendor-class/
на который можно наткнуться, если искать информацию об этом в интернетах.
Давайте сначала немного разберёмся в механизме деления на классы, а потом посмотрим что не так сделал автор.
Итак, в dhcpd есть такая вещь как «опции».
Опции могут иметь различный формат, и от этого, принимать различные значения (например опция может быть булева, численная, строковая, или ip адрес)
Некоторые опции задаются сервером, и передаются в последствии клиентам. Некоторые опции передаются от клиента к серверу.
С такими (от клиента к серверу) опциями, очень просто выделить клиента в класс.
Рассмотрим простой пример:
Код: Выделить всё
class "yealink" {
match if substring (option vendor-class-identifier, 0, 7) = "yealink";
}
class "panasonic" {
match if substring (option vendor-class-identifier, 0, 9) = "Panasonic";
}
Тут для выделения клиента в класс, мы используем сравнение по выражению:
match if substring. И мы сравниваем vendor class identifier со строкой yealink.
Что же означают цифры? Substring это оператор, который вырезает часть строки:
substring ( строка, отступ, длина ) где строка — то что мы будем вырезать.
Отступ — сколько бит пропустить от начала строки. Длина — сколько бит отрезать.
Стало понятнее?
Клиент посылает нам опцию vendor-class-identifier (она будет разная от разных клиентов)
мы вырезаем из неё часть нужной длины, в первом случае 7 байт, во втором случае 9 байт. Таким образом, можем выделить слово yealink или panasonic, которые и имеют длину 7 байт и 9 байт.
Где же можно посмотреть присылает ли клиент опцию vendor-class-identifier?
В файле dhcpd.leases. Вот типичный пример записи клиента:
Код: Выделить всё
lease 172.16.7.5 {
starts 5 2020/08/07 08:15:02;
ends 5 2020/08/07 20:15:02;
tstp 5 2020/08/07 20:15:02;
cltt 5 2020/08/07 08:15:02;
binding state free;
hardware ethernet bc:c3:42:f8:6e:a0;
set vendor-class-identifier = "Panasonic";
}
Так же обратите внимание что не все клиенты посылают эту опцию.
Давайте теперь посмотрим на пример посложнее:
Код: Выделить всё
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
}
if substring (option vendor-class-identifier, 15, 5) = "00000" {
filename "bios/lpxelinux.0";
} elsif substring (option vendor-class-identifier, 15, 5) = "00007" {
filename "EFI/grubx64.efi";
} else {
filename "pxelinux.0";
}
Код: Выделить всё
set vendor-class-identifier = "PXEClient:Arch:00007:UNDI:003016";
Почему я так сделал? Я хотел показать вам, что для передачи разных опций клиенту, вовсе не обязательно создавать отдельный класс. Если выделять клиенту отдельный диапазон адресов не нужно, не нужно и объявлять для этого отдельный класс.
Тут у нас выделен класс pxeclients, куда попадут без исключения все клиенты pxe, и их можно выделить в свою подсеть.
А потом выполнена конструкция из операторов if, которая никак не связана с классами клиентов, а уже служит просто для того, чтобы каждому клиенту был передан его загрузчик.
Теперь, когда мы познакомились с базовыми знаниями по поводу классов, давайте рассмотрим как же их применить на практике.
Тут нам помогут директивы allow и deny.
Запомните простую логику:
- Если для части адресов указана хотя бы одна директива allow, то всё что не разрешено — будет запрещено
- Если для части адреса указана хотя бы одна директива deny — то всё что не запрещено — будет разрешено.
Код: Выделить всё
subnet 172.16.0.0 netmask 255.240.0.0 {
pool { range 172.16.7.0 172.16.7.20; allow members of "panasonic"; }
pool { range 172.16.6.0 172.16.6.150; allow members of "yealink"; }
pool { range 172.16.10.0 172.16.15.255; deny members of "yealink"; deny members of "panasonic"; }
}
Первый промежуток адресов. Разрешено панасоникам. По логике, всем остальным запрещено.
Второй промежуток — разрешено еалинкам. По логике, всем остальным, запрещено.
Третий промежуток — запрещено панасоникам и еалинкам. По логике — всем остальным клиентам использование этого пула будет разрешено!
То есть, все кто не относится к панасоникам и еалинкам — попадёт в третий пул адресов.
Ну вроде же не сложно, и всё по логике, верно?
Давайте теперь рассмотрим какие же ошибки совершают некоторые авторы, и почему нельзя так делать.
Вот по той же ссылочке, куда я уже указывал https://voxlink.ru/kb/voip-devices-configuration/dhcp-vendor-class/
Автор пытается сделать такое сравнение:
Код: Выделить всё
class «yealink» {
match if
substring (option vendor-class-identifier, 0, 4) = «A580» or
substring (option vendor-class-identifier, 0, 5) = «udhcp» or
substring (option vendor-class-identifier, 0, 10) = «yealink» or
substring (option vendor-class-identifier, 0, 4) = «00006=»;
}
Что мы видим дальше:
Код: Выделить всё
#Yealink-Phones
pool {
range 192.168.180.31 192.168.180.250;
allow members of «yealink»;
}
#Not-grouped-clients
pool {
range 192.168.180.205 192.168.180.215;
allow unknown-clients;
}
С этим конфигом у наc еалинки свободно попадут в сеть «not-grouped-clients».
Почему? Потому что неправильно используется определение unknown-clients.
Запомните!!! unknown-clients — это клиенты, которые не имеют явного объявления хоста (host declaration!)
Занести клиента в класс — это не значит объявить его! Он всё ещё останется unknown!
В данном случае, все телефоны в классе еалинк, точно так же являются и членами класса unknown-clients!
Объявить клиента — значит создать запись host для него. Вы могли видеть такие записи когда нужно статично присвоить ip адрес клиенту. Давайте возьмём пример, как должны выглядеть записи, чтобы этот конфиг работал:
Код: Выделить всё
host ncd1 { hardware ethernet 0:c0:c3:49:2b:57; }
host ncd4 { hardware ethernet 0:c0:c3:80:fc:32; }
host ncd8 { hardware ethernet 0:c0:c3:22:46:81; }
pool {
range 192.168.180.205 192.168.180.215;
deny unknown-clients;
}
Именно так это работает.
Кстати, если вы вдруг задавались вопросом, зачем вообще нужны группы в dhcp, у меня есть ответ. Именно для таких случаев.
Код: Выделить всё
group {
filename "loader1";
host ncd1 { hardware ethernet 0:c0:c3:49:2b:57; }
host ncd4 { hardware ethernet 0:c0:c3:80:fc:32; }
}
group {
filename "loader2";
host ncd2 { hardware ethernet 0:c0:c3:88:2d:81; }
host ncd3 { hardware ethernet 0:c0:c3:00:14:11; }
}
Я не знаю чем это может быть полезно в реальной жизни, но работает это именно так.
Теперь, когда мы уже получше разобрались в классификации клиентов, и уже умеем выделять для них разный пул адресов, давайте усложним задачу, и попытаемся соединить клиентов, которые НЕ посылают vendor class но при этом обладают общими свойствами (три первых части мак адреса).
Для этого нам потребуется вот такое очень хитрое сравнение:
Код: Выделить всё
match if binary-to-ascii(16, 8, ":", substring(hardware, 1, 3)) = «0:c1:a0»;
Для начала давайте разбираться с binary-to-ascii:
binary-to-ascii ( число1, число2, строка1, строка2 )
этот оператор, берёт данные из «строка2», после чего, откусывает от него по кусочку бит, указанных в «число2», преобразует это в систему счисления, указанную в «число1», после чего записывает результат, разделённый между собой через «строка1».
Да, я знаю, звучит дико, но сейчас всё станет понятно.
Что такое substring (hardware, 1, 3) — это сетевой идентификатор клиента (hardware) от которого откушен первый байт (для ethernet этот байт всегда 1 (единица) а всё остальное — сетевой адрес клиента (link-layer address). Мы откусили ещё три байта, и теперь преобразуем это в удобоваримый формат.
Посмотрите, binary-to-ascii берёт вот эти три байта от substring, берёт первые 8 бит (число 2) преобразует их в 16ричный формат (число1), ставит после всего этого «:» (строка1) и переходит к следующим 8 битам. На выходе мы получим три первых части mac адреса.
Обратите внимание, ноль, это 0 а не 00. И 1 это 1 а не 01. Поэтому если часть адреса содержит числа, начинающиеся с нуля, их нужно обрезать. Например: "0:c1:a0" вместо "00:c1:a0" или "0:fe:a" вместо "00:fe:0a".
Вроде и не сложно, правда?
Последнее, о чём бы я хотел поговорить, это опции.
Как только не издеваются разнообразные авторы над этими опциями.
Я уже говорил, что некоторые опции сервер отправляет клиенту. А некоторые опции клиент отправляет серверу. В любом случае, у опций dhcp есть код, числовой. Людям удобнее работать с символьными именами, нежели с числами. Поэтому, опции dhcp можно объявить. Давайте вернёмся к моему примеру с документацией к centos. Вот пример оттуда:
Код: Выделить всё
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
subnet 10.0.0.0 netmask 255.255.255.0 {
option routers 10.0.0.254;
range 10.0.0.2 10.0.0.253;
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
next-server 10.0.0.1;
if option architecture-type = 00:07 {
filename "uefi/shim.efi";
} else {
filename "pxelinux/pxelinux.0";
}
}
}
Код: Выделить всё
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
Тут авторы объявляют целое пространство опций pxelinux, после чего указывают код опции, и через знак «=» объявляют тип данных.
Например architecture-type имеет код 93 и тип — целое число без знака, размером 16 бит.
Теперь хотелось бы указать что меня смущает в этом примере.
Первое — опции эти добры молодцы объявили. А используют из них только одну — architecture-type. Зачем объявлять опции и не использовать их? Ещё и включать это в документацию.
Второе — это сама опция.
Вот тут уже нужно сделать отступление и кинуть камень в огород к тем, кто писал документацию на сам dhcpd. К сожалению, они забыли указать в своей документации список опций, которые они сами же включили в dhcpd! Но!!! Они включили этот список в документацию к kea!
Ура! https://kea.readthedocs.io/en/latest/arm/dhcp4-srv.html#id2
Для dhcpd есть разве что опции без привязки к кодам:
https://kb.isc.org/docs/en/isc-dhcp-44-manual-pages-dhcp-options#STANDARD%20DHCPV4%20OPTIONS
По большей части они совпадают, но кое-что, к сожалению может не совпадать.
Вот например та же опция 93 согласно документации называется pxe-system-type
Так что вышеуказанный пример от centos, можно сократить до этого:
Код: Выделить всё
subnet 10.0.0.0 netmask 255.255.255.0 {
option routers 10.0.0.254;
range 10.0.0.2 10.0.0.253;
next-server 10.0.0.1;
if option pxe-system-type = 00:07 {
filename "uefi/shim.efi";
} else {
filename "pxelinux/pxelinux.0";
}
}
Меня кроме этого весьма забавляет попытка сравнивать целочисленную опцию с "00:07" но злые языки говорят что якобы это работает.
Кроме того, внимательный читатель мог заметить, что в начале статьи, я выполнил всю туже самую процедуру распределения, не используя переменную с кодом 93 вообще. Ну, у кого получилось красивше, это конечно вопрос с дополнительным обсуждением. Моя задача была показать вам, как не совершать ошибок.
У dhcpd ещё много возможностей в плане классификации клиентов, однако, в этом руководстве я хотел затронуть те случаи, которые чаще могут понадобится в реальной жизни, и выполнение таких задач тривиально, но почему-то не для всех.
Если где-то нашли ошибку, или информацию трудно читать, если что-то непонятно, пишите об этом в коментариях. Спасибо за внимание!