А АMonday 25 May 2020

Диагностика приложений протокола DHCP в Scapy.

Всем привет.

Вторая часть практики со  Scapy от Бражука Андрея. Сегодня будет проведена активная диагностика приложений протокола DHCP. Первую часть читаем здесь.

Оригинал статьи вы найдете в журнале "Системный администратор" №12 (109) 2011 года.

Введение

Протокол динамической конфигурации узла DHCP (Dynamic Host Configuration Protocol) повсеместно используется для автоматизации процесса получения настроек протокола IP на сетевых узлах. Средства, обеспечивающие функциональность DHCP-сервера, существуют для всех программных платформ, реализующих те или иные удаленные службы (серверные версии Windows, Linux/Unix, операционные системы маршрутизаторов, шлюзов, межсетевых экранов и т. д.). Клиент этого протокола обычно имеется в любом оборудовании, работающем в сети.

Не смотря на наличие хорошей документации и простоту настройки приложений DHCP, работоспособность их сложных конфигураций требует проверки. Обычно для активного тестирования используется рабочая станция, сетевой стек которой настроен на динамическое получение параметров IP. Для обнаружения существующих проблем исследуются журналы работы сервера и клиента DHCP, а также, если необходимо, с помощью соответствующего ПО перехватывается и анализируется трафик между ними.

В ряде случаев нестандартные задачи активной диагностики DHCP, а также автоматизация их решения требуют привлечение технологий программирования. Учитывая то, что в практике системного администрирования обычно основная цель программирования - быстрый результат, решать данный класс задач лучше с помощью специальных инструментов. Рассмотренный в статье генератор сетевых пакетов Scapy собственно и есть такое средство. Данная утилита одновременно является расширяемой библиотекой языка программирования Python и ориентирована на протоколы без установления соединения, к которым относится и DHCP, использующий протокол UDP в качестве транспорта.

Разведка боем: имитируем DHCP-клиент

Ввиду того, что принципы функционирования DHCP общеизвестны и описаны в литературе, теоретические выкладки пропустим и сразу перейдем к программированию DHCP-клиента в Scapy.

Тестовый стенд, использовавшийся для написания данной статьи, построен на основе ПО с открытым исходным кодом: виртуальная машина (ВМ) VirtualBox на основе Debian GNU/Linux с установленным ISC DHCP. Утилита Scapy запускалась с хостовой системы, объединенной с ВМ с помощью виртуального адаптера «Виртуальная сеть хоста». При этом, DHCP-сервер VirtualBox для этого режима был отключен. Разумеется, описанные в статье упражнения можно повторить в «физической» сети с использованием службы DHCP, встроенной в маршрутизатор, реальный сервер или модем.

Для получения минимальной работоспособной конфигурации сервера ISC DHCP достаточно в файле /etc/dhcp/dhcpd.conf описать подсеть (subnet):
subnet 192.168.56.0 netmask 255.255.255.0 {
  range 192.168.56.10 192.168.56.30;
  option domain-name-servers 192.168.56.1;
  option domain-name "local.test";
  option routers 192.168.56.1;
  option broadcast-address 192.168.56.255;
  default-lease-time 600;
  max-lease-time 7200;
}



В общем виде, процесс получения настроек протокола IP состоит из четырех сообщений: два отправляет клиент (discovery и request), два — сервер (offer и ack). Структура и содержание сообщений определяется форматом протоколов BOOTP (Bootstrap Protocol) и DHCP:

Оба сообщения клиента идентичны на уровнях Ethernet, IP, UDP, BOOTP и различаются только опциями DHCP, поэтому предварительно сделаем пакет-заготовку, который назовем pktboot:
>>> fam,hw = get_if_raw_hwaddr("vboxnet0")
>>> pktboot = Ether(src=hw, dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0", dst="255.255.255.255")/UDP(sport=68, dport=67)/BOOTP(chaddr=hw)

В поле адреса получателя кадра Ethernet указываем широковещательный MAC-адрес, а в поле адреса отправителя — MAC-адрес соответствующего сетевого адаптера локального компьютера, Получить последний можно с помощью функции get_if_raw_hwaddr, использующей в качестве аргумента имя интерфейса и возвращающей искомое значение вторым параметром. В заголовке IP-слоя описываем исходный адрес как неопределенный (0.0.0.0), а адрес назначения — как широковещательный (255.255.255.255). Порт источника в UDP-датаграмме — 68, назначения — 67. В параметре chaddr в сообщении протокола BOOTP также нужно указать локальный физический адрес.

Перед тем как отправлять первое сообщение, отключим проверку IP-адресов отправленных и полученных пакетов в Scapy (так как стимул-пакет будет отправляться на широковещательный IP-адрес, а пакет-реакция будет приходить с определенного IP-адреса DHCP-сервера):
>>> conf.checkIPaddr = False

Сформируем и отправим первое сообщение, целью которого является обнаружить существующий в сети DHCP-сервер. Сообщение будет состоять из объекта pktboot и опций DHCP; в данном случае опции «message-type», указывающей на тип сообщения (discovery) и ключевого слова «end», обозначающего завершение списка:
>>> pktoff = srp1(pktboot/DHCP(options=[("message-type","discover"),"end"]), timeout=2,iface="vboxnet0")

Функция srp1 «поймает» ответ первого доступного DHCP-сервера и запишет его в переменную pktoff. «Правильный» ответ сервера будет содержать предлагаемый клиенту IP-адрес (поле «yiaddr») и необходимые для настройки сетевого стека опции. Отобразим интересующую нас информацию с помощью функции print:
>>> print "op =", pktoff[BOOTP].op, ", yiaddr =", pktoff[BOOTP].yiaddr, ", options=", pktoff[DHCP].options
op = 2 , yiaddr = 192.168.56.10 , options= [('message-type', 2), ('server_id', '192.168.56.2'), ('lease_time', 600), ('subnet_mask', '255.255.255.0'), ('router', '192.168.56.1'), ('name_server', '192.168.56.1'), ('renewal_time', 300), ('rebinding_time', 525), ('broadcast_address', '192.168.56.255'), ('domain', 'local.test'), 'end']

Теперь клиенту нужно отправить второе сообщение — запрос на получение IP-адреса, указав в опциях некоторые данные полученные с офертой: идентификатор сервера (server_id), запрашиваемый адрес (requested_addr), а также тип сообщения (request):
>>> pktask = srp1(pktboot/DHCP(options=[("message-type","request"),("server_id","192.168.56.2"),("requested_addr","192.168.56.10"),"end"]),timeout=2,iface="vboxnet0")

Если в полученном ответе опция «message-type» установлена в значение 5 (ack), то Ваш сервер работает:
>>> print pktask[DHCP].options
[('message-type', 5), ('server_id', '192.168.56.2'), ('lease_time', 600), ('subnet_mask', '255.255.255.0'), ('router', '192.168.56.1'), ('name_server', '192.168.56.1'), ('renewal_time', 300), ('rebinding_time', 525), ('broadcast_address', '192.168.56.255'), ('domain', 'local.test'), 'end']

Следует отметить, что кроме сообщений, используемых для получения IP-параметров, в протоколе DHCP предусмотрено несколько дополнительных типов оповещений. Клиент может послать широковещательный отказ (decline), если обнаружит конфликт IP-адресов с другой системой в сети. Сервер может отменить аренду IP-адреса, отправив соответствующее широковещательное сообщение (nak). Клиент также может прекратить использование адреса, известив об этом сервер сообщением об освобождении (release) и т.д.

Подключаем Python

Теперь остается лишь оформить результаты описанного эксперимента в виде скрипта на Python (если Вы имеете опыт в программировании, то очевидно сможете написать более элегантный и функциональный код, чем представлен здесь):
#!/usr/bin/python

from scapy.all import *

# search in lst val for fld
def getval(lst,fld):
    for p in lst:
 if type(p) is tuple:
     if p[0]==fld: return p[1]
    return None

# err and exit
def exterr(msg):
    print msg
    sys.exit(1)

#main
import sys
#check args
if len(sys.argv) != 2:
    exterr("Usage: "+sys.argv[0]+" ")
myif=sys.argv[1]

#mk pktboot
conf.checkIPaddr = False
fam,hw = get_if_raw_hwaddr(myif)
pktboot = Ether(src=hw, dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0", dst="255.255.255.255")/UDP(sport=68, dport=67)/BOOTP(chaddr=hw)

#mk discovery
dhdisc = DHCP(options=[("message-type","discover"),"end"])
pktoff = srp1(pktboot/dhdisc, timeout=2,iface=myif)
if pktoff != None:
    if getval(pktoff[DHCP].options,'message-type')==2 and getval(pktoff[DHCP].options,'server_id')!=None and pktoff[BOOTP].yiaddr!=None:
 print "got OFFER from ",pktoff[IP].src,"(",pktoff[Ether].src,") addr: ", pktoff[BOOTP].yiaddr
 print pktoff[DHCP].options,"\n"
 #mk request
 dhreq = DHCP(options=[("message-type","request"),("server_id",getval(pktoff[DHCP].options,'server_id')),("requested_addr",pktoff[BOOTP].yiaddr),"end"])
 pktask = srp1(pktboot/dhreq, timeout=2,iface=myif)
 if pktask != None:
     if getval(pktask[DHCP].options,'message-type')==5:
          print "got ASK from ",pktask[IP].src,"(",pktask[Ether].src,") addr: ", pktask[BOOTP].yiaddr
  print pktask[DHCP].options
  # exit ok
          sys.exit(0)
     exterr("invalid ACK from server")
 exterr("no ACK from server")
    exterr("invalid offer")
exterr("no OFFER from server")

Пользовательская функция getval предназначена для получения значения опции по ее имени из формата списка, в котором объект DHCP хранит их (составной список из единичных элементов и пар «имя»-«значение»). Функция exterr используется для аварийного завершения программы в случае отклонений в работе алгоритма. Соответствующие проверки реализованы с помощью стандартной конструкции «if».

У этого «самопального» скрипта один параметр: имя сетевого интерфейса, на котором нужно тестировать DHCP-сервер; запускать же его нужно от имени суперпользователя.

В поисках нелегитимного DHCP-сервера

Проблема появления в сети нелегитимного сервера DHCP в большинстве случаев связана с неаккуратностью пользователей и халатностью системных администраторов, допустивших подключение к сети устройств с включенной функцией удаленной выдачи IP-адресов. Если параметры протокола IP, предлагаемые таким устройством, не соответствуют конфигурации сети в данном сегменте, то пользователи не смогут получить доступ к сетевым сервисам; если же соответствуют — будут возникать конфликты IP-адресов между клиентами легитимного и нелегитимного DHCP-серверов.

Современные технологии контроля доступа в сеть и оборудование позволяют существенно снизить вероятность подобных проблем. Однако, такие технологии не всем доступны и не во всех случаях применимы, поэтому тем, кому не повезло, остается настраивать мониторинг активных DHCP-серверов и систему оповещений о подозрительной активности.
Одним из способов диагностики является периодическая отправка сообщения обнаружения и анализа ответов от DHCP-серверов на него. Простейший вариант такой проверки можно организовать с помощью Python и Scapy (приведен фрагмент скрипта):
ans, uans= srp(pktboot/dhdisc, timeout=5,iface=myif, multi=True)
for p in ans:
       print p[1][Ether].src, p[1][IP].src

Здесь, pktboot — пакет-заготовка и dhdisc — DHCP-сообщение обнаружения. В отличие от предыдущего скрипта предполагается, что на один стимул может несколько реакций. Поэтому, во-первых, используется функция srp, которая позволяет сформировать массив объектов-ответов. Во-вторых, параметр «multi» установлен в истину, чтобы srp не прекращала свою работу после первого полученного ответа на запрос.

Если, например, в тестовой конфигурации на виртуальном интерфейсе включить встроенный DHCP-сервер VirtualBox, то результат работы подобного скрипта будет примерно таким:


Используя возможности Python, оповещения можно реализовать в любой удобной форме: запись в файл журнала, по электронной почте, посредством SMS и т. д. Следует также отметить, что для обнаружения нелегитимных DHCP-серверов также существуют и готовые решения.

Опция номер восемьдесят два

Кроме сервера и клиента в процессе автоматической настройки параметров IP может участвовать еще DHCP-ретранслятор (relay), играющий роль посредника между ними. Часто ретранслятор применяется тогда, когда сервер и клиент расположены в разных широковещательных доменах.

Также релеи встраивают в коммутаторы локальных сетей, что позволяет привязать диапазон IP-адресов к данному устройству либо к отдельным его портам. Достигается этот эффект использованием DHCP-опции 82 (Agent Information Option), включающей два идентификатора: собственно релея (Agent Remote ID) и порта (Agent Circuit ID).

Фрагмент конфигурационного файла сервера ISC DHCP с поддержкой данной опции для тестовой конфигурации приведен в листинге. В данном примере на запросы, содержащие в полях идентификатора релея MAC-адрес коммутатора (11:22:33:44:55:66) и порт 5, будут предлагаться IP-адреса из диапазона 192.168.56.65-126, а для остальных клиентов — 192.168.56.10-30.
class "sw1p5" {
    match if (
      (binary-to-ascii (16, 8, ":", option agent.remote-id)
       = "11:22:33:44:55:66")
      and
      (binary-to-ascii (10, 8, "",
       suffix( option agent.circuit-id, 1)) = "5")
    );
}
pool {
    allow members of "sw1p5";
    range 192.168.56.65 192.168.56.126;
}
pool {
    deny members of "sw1p5";
    range 192.168.56.10 192.168.56.30;
}
При тестировании приведенных настроек с помощью Scapy возникла небольшая заминка, связанная с тем, что внутренняя структура опции 82 (т. е. ее подпараметров) на данный момент не реализована в Scapy. Но как оказалось, формат информации об агенте достаточно прост и аналогичен другим полям DHCP-сообщения [8]: в первом байте записан номер поля (82), во втором — длина данных в байтах (N), а далее непосредственно данные (рис. 3). Подпараметры имеют точно такой же формат: номер идентификатора порта равен 1, ретранслятора — 2; длины — K и L соответственно.


Поэтому, в коде симулятора DHCP-ретранслятора на Python не составляет труда сформировать содержимое поля «relay_agent_information» вручную (переменная p):
p="\x01\x01\x05\x02\x06\x11\x22\x34\x44\x55\x66"
dhdisc = DHCP(options=[("message-type","discover"),("relay_agent_Information",p),"end"])

Изготовив такой «самопальный» имитатор релея, можно существенно облегчить проверку настроек DHCP-сервера. Правда, следует иметь ввиду, что информация, отсылаемая серверу зависит от типа ретранслятора: вид данных, отправляемых последним, можно исследовать с помощью анализатора протоколов.

Заключение

Практическое программирование с использованием средств, приближенных к прикладной области, естественно предполагает исследование и изучение непосредственно предмета (в данном случае конкретного протокола). Данная статья — не исключение: в представленном материале больше внимания уделено сетевым технологиям и меньше непосредственно программированию. Отчасти в этом есть и заслуга Scapy, многофункционального и простого инструмента, позволяющего сконцентрироваться на главном и не заостряться по мелочам.

No comments:

Post a Comment

А что вы думаете по этому поводу?

Версия на печать

Популярное