Главная » Хабрахабр » Собственный DHCP-сервер силами bash

Собственный DHCP-сервер силами bash


Я люблю автоматизировать процесс и писать собственные велосипеды для изучения того или иного материала. Моей новой целью стал DHCP-сервер, который будет выдавать адрес в маленьких сетях, чтобы можно было производить первоначальную настройку оборудования.
В данной статье я расскажу немного про протокол DHCP и некоторые тонкости из bash'а.
Начнём с конца, чтобы было понятно, за что сражаемся.
Демонстрация работы:

Репозиторий со скриптом: firemoon777/bash-dhcp-server

Необходимая мне настройка производится так: подключаемся напрямую по витой паре к оборудованию, выдаём временный адрес по DHCP, производим настройку уже созданным скриптом. И так десять-двадцать раз подряд.
Многим известный isc-dhcp-server прекрасно справляется со своими обязанностями, но, увы, никак не уведомляет мой скрипт о том, что адрес выдан, поэтому нужно как-то блокировать выполение, пока адрес не выдан.
Решение, вроде бы, на поверхности: пинговать до посинения, пока оборудование не отзовётся:

while ! ping -c1 -W1 "$DHCP" | grep -q "time="
do echo "Waiting for $DHCP..."
done

Но в этом решении, определённо, не хватает авантюризма.

Получение адреса при одном DHCP-сервере

Протокол DHCP работает поверх UDP на портах 67 и 68. Сервер работает всегда только на 67, а клиент только на 68. Так как клиент не имеет адреса (имеет адрес 0.0.0.0), рассылка DHCP-пакетов производится шировещательным образом. Т.е. клиент всегда отправляет пакеты на адрес 255.255.255.255:67 с адреса 0.0.0.0:68, а сервер отправляет со своего адреса :67 на адрес 255.255.255.255:68.
Получение адреса клиентом происходит в четыре пакета (DORA):

  1. Клиент выясняет где здесь DHCP-сервер (Discover)
  2. Сервер отзывается и предлагает свой адрес (Offer)
  3. Клиент запрашивает предложенный адрес у конкретного сервера (Request)
  4. Сервер соглашается и выдаёт адрес (Ack)

Визуально схему можно представить так:

Получение адреса при нескольких DHCP-серверах

Но клиент должен выбрать кого-то одного. Когда клиент отправляет Discover, все сервера, которые могут слышать, присылают свой Offer клиенту. Хотя Request отправляется так же всем в сети, реагирует только тот DHCP-сервер, чей IP указан в опции 54. Выбор клиента оглашается в сообщении Request опцией 54 (DHCP-сервер), которая содержит IP-адрес предпочтённого DHCP-сервера.

Содержимое DHCP-пакета

DHCP-пакет состоит из двух частей: постоянной, размером в 236 байт и переменной, которая несёт в себе опции (DHCP Option).

Таблица со всеми полями пакета DHCP из Википедии

Поле

Описание

Длина (в байтах)

op

Тип сообщения. Например может принимать значения: BOOTREQUEST (0x01, запрос от клиента к серверу) и BOOTREPLY (0x02, ответ от сервера к клиенту).

1

htype

Тип аппаратного адреса. Допустимые значения этого поля определены в RFC 1700 «Assigned Numbers». Например, для MAC-адреса Ethernet это поле принимает значение 0x01.

1

hlen

Длина аппаратного адреса в байтах. Для MAC-адреса Ethernet —0x06.

1

hops

Количество промежуточных маршрутизаторов (так называемых агентов ретрансляции DHCP), через которые прошло сообщение. Клиент устанавливает это поле в 0x00.

1

xid

Уникальный идентификатор транзакции в 4 байта, генерируемый клиентом в начале процесса получения адреса.

4

secs

Время в секундах с момента начала процесса получения адреса. Может не использоваться (в этом случае оно устанавливается в 0x0000).

2

flags

Поле для флагов — специальных параметров протокола DHCP.

2

ciaddr

IP-адрес клиента. Заполняется только в том случае, если клиент уже имеет собственный IP-адрес и способен отвечать на запросы ARP (это возможно, если клиент выполняет процедуру обновления адреса по истечении срока аренды).

4

yiaddr

Новый IP-адрес клиента, предложенный сервером.

4

siaddr

IP-адрес сервера. Возвращается в предложении DHCP (см. ниже).

4

giaddr

IP-адрес агента ретрансляции, если таковой участвовал в процессе доставки сообщения DHCP до сервера.

4

chaddr

Аппаратный адрес (обычно MAC-адрес) клиента.

16

sname

Необязательное имя сервера в виде нуль-терминированной строки.

64

file

Необязательное имя файла на сервере, используемое бездисковыми рабочими станциями при удалённой загрузке. Как и sname, представлено в виде нуль-терминированной строки.

128

options

Поле опций DHCP. Здесь указываются различные дополнительные параметры конфигурации. В начале этого поля указываются четыре особых байта со значениями 99, 130, 83, 99 («волшебные числа»), позволяющие серверу определить наличие этого поля. Поле имеет переменную длину, однако DHCP-клиент должен быть готов принять DHCP-сообщение длиной в 576 байт (в этом сообщении поле options имеет длину 340 байт).

переменная

Список всех опций DHCP в RFC 2132

Опции DHCP кодируются следующим образом:

Номер

Длина

Данные

Например, параметр 3 (предлагаемый шлюз) со значением 10.0.0.1:

3

4

10

0

0

1

В случае, если нужно передать несколько параметров, длина параметра увеличивается.
Например, в параметре 6 (DNS-сервер) передадим два адреса (1.1.1.1 и 8.8.4.4):

6

8

1

1

1

1

8

8

4

4

Признаком конца поля опций является параметр с номером 255 (0xFF) и длиной 0.

Чаще всего клиент вкладывает параметр 55 (список параметров, которые он хочет получить в ответ) в DHCP Discover, однако, мы имеем право выдать ему не всё, что он запросил.

Изначально планировалось написать сервер на каком-то более подоходящем для этого языке (Си), однако, это было бы буднично и просто. То ли дело написать скрипт, который будет брать на себя функции dhcp-сервера.

Упрощения

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

  • гарантируется, что один клиент в сети;
  • гарантируется, что больше нет dhcp-серверов в сети
  • запускающий сам решает, какой адрес выдать
  • DHCP Release и DHCP Decline игнорируются

Слушатель

В первую очередь нужно научиться принимать пакеты. Для этого необходим дипломированный сочувственный слушатель, например, nc. Но не всякий nc подойдёт для этих целей. OpenBSD netcat 1.130 с дебиана подходит, а вот 1.105 с Ubuntu уже нет. Запустим nc слушать все UDP-пакеты, прилетающшие на порт 67.

nc -l 0.0.0.0 -up 67 -w0

OpenBSD netcat нужен в том числе из-за ключа -w со значением 0. После получения одного пакета (UDP Broadcast) традиционный nc не принимает больше пакетов, но и не завершается.

Работа с сырыми байтами

В командном интерпретаторе весьма сложно работать с непечатными символами, например с нуль-символом: он его просто игнорирует. А DHCP-пакет содержит множество байт 0x00 (например, поле file). Решение проблемы приходит в виде hex-dump'а:

nc -l 0.0.0.0 -up 67 -w0 | stdbuf -o0 od -v -w1 -t x1 -An

По одному байту на строку, без вывода адреса, не пропуская повторяющиеся байты. Можно так же приправить stdbuf -o0, чтобы вывод не буферизировался.

Получение, хранение и обработка пакетов

Из stdout команды od байты забираются командой read и складываются в массив.

msg=()
for i in ; do read -r tmp msg[$i]=$tmp
done

Хотя все значения передаются в шестнадцатеричной системе счисления, номер DHCP Option и длину опции лучше всего выводить на экран/в логи в привычном десятичном виде. Для этого можно воспользоваться краткой записью bash'a:

$ op=AC
$ echo $((16#$op))
172

Принятый пакет редактируется в соответствии с типом запроса (Discover или Request) и отправляется обратно.

Отправка ответа

Впрочем, отправка пакета не такая уж и простая задача. Сначала нужно конвертировать байты из дампа в сырые байты и отправить сразу всё одним пакетом.
Конвертацию можно сделать утилитой printf с помощью escape-последовательностей. А чтобы ничего не потерялось, записывать байты сразу в файл.

# Зачищаем файл
>/tmp/dhcp.payload
# Записываем начало фиксированной длины
for i in ${msg[*]}; do printf "\x$i" >> /tmp/dhcp.payload done

Для отправки так же используется OpenBSD netcat. Однако, если в качестве слушателя версия 1.105 с Ubuntu подходит, то вот для рассылки широковещательных UDP-сообщений не годится: получаем ошибку protocol not available.

cat /tmp/dhcp.payload | nc -ub 255.255.255.255 68 -s $SERVER -p 67 -w0

Ключ -b разрешает отправку широковещательных сообщений и это вторая причина, по которой сервер необходимо запускать из-под суперпользователя.
Данный DHCP-сервер разрабатывался при упрощениях типа одного клиента в сети. Однако, он будет работать и при нескольких клиентах. Просто адрес получит быстрейший.

Хотя bash-скрипты сложно назвать полноценным языком программирования, тем не менее, при должном желании можно решить даже такие задачи, как выдача IP-адреса в сети без использования специально предназначенного для этого ПО. А решение специфичных задач не только приносит радость, но и новые знания, которые открылись в момент решения.

  1. DHCP — Википедия
  2. DHCP and BOOTP Parameters — IANA

Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

MIDI-router на Raspberry Pi

Хочу рассказать о том, как решить проблему, которая наверняка знакома любителям аппаратных синтезаторов. Причем, по понятным причинам хочется все это сделать не используя компьютер. Что делать, если хочется состыковать MIDI-контроллер и синтезатор, но у одного из них есть только USB ...

DynamicData: Изменяющиеся коллекции, шаблон проектирования MVVM и реактивные расширения

В феврале 2019 года состоялся релиз ReactiveUI 9  —  кроссплатформенного фреймворка для построения приложений с графическим пользовательским интерфейсом на платформе Microsoft .NET. ReactiveUI  — это инструмент для тесной интеграции реактивных расширений с шаблоном проектирования MVVM. Знакомство с фреймворком можно начать с ...