Команда для вывода назначенных DHCP-адресов

Вопрос или проблема

Существует ли команда, с помощью которой я могу узнать, какие IP-адреса были назначены сервером dhcpd?

Пакет isc-dhcpd версии 4.3.1 содержит эту команду для отображения аренды:

dhcp-lease-list --lease PATH_TO_LEASE_FILE

Это простой Perl-скрипт, который также поддерживает более старые версии DHCP. Вы можете увидеть копию в исходном коде Debian или в официальной дистрибуции DHCPcontrib/).

Вывод выглядит следующим образом:

$ perl contrib/dhcp-lease-list.pl --lease /var/db/dhcpd/dhcpd.leases
Чтобы получить названия производителей, пожалуйста, загрузите http://standards.ieee.org/regauth/oui/oui.txt в /usr/local/etc/oui.txt
MAC                IP              hostname       действителен до         производитель
===============================================================================================
90:27:e4:f9:9d:d7  192.168.0.182   iMac-de-mac    2015-12-12 01:37:06 -NA-
d8:a2:5e:94:40:81  192.168.0.178   foo-2          2015-12-12 01:04:56 -NA-
e8:9a:8f:6e:0f:60  192.168.0.127   angela         2015-12-11 23:55:32 -NA-
ec:55:f9:c5:f2:55  192.168.0.179   angela         2015-12-11 23:54:56 -NA-
f0:4f:7c:3f:9e:dc  192.168.0.183   kindle-1234567 2015-12-11 23:54:31 -NA-
f4:ec:38:e2:f9:67  192.168.0.185   -NA-           2015-12-11 23:55:40 -NA-
f8:d1:11:b7:5a:62  192.168.0.184   -NA-           2015-12-11 23:57:34 -NA-

Это выглядит лучше, если вы загрузите файл oui.txt, как предложено, но тогда вывод может испортиться, если вы не примените следующий патч:

--- dhcp-lease-list.pl.orig     2015-12-12 12:30:00.000000000 -0500
+++ dhcp-lease-list.pl  2015-12-12 12:54:31.000000000 -0500
@@ -41,7 +41,7 @@
     if (defined $oui) {
        $manu = join('-', ($_[0] =~ /^(..):(..):(..):/));
        $manu = `grep -i '$manu' $oui | cut -f3`;
-       chomp($manu);
+       $manu =~ s/^\s+|\s+$//g;
     }

     return $manu;
@@ -142,7 +142,7 @@
     }
     foreach (@leases) {
        if ($opt_format eq 'human') {
-          printf("%-19s%-16s%-15s%-20s%-20s\n",
+          printf("%-19s%-16s%-14.14s %-20s%-20s\n",
                  $_->{'mac'},       # MAC
                  $_->{'ip'},        # IP адрес
                  $_->{'hostname'},  # имя хоста

Этот патч был отправлен в основное хранилище как ISC-Bugs #41288 и ожидает рассмотрения.

Нет, вы можете получить эту информацию только со стороны сервера от DHCP-сервера. Эта информация содержится в .lease файле DHCP-сервера: /var/lib/dhcpd/dhcpd.leases, если вы используете DHCP-сервер ISC.

Пример

$ more /var/lib/dhcpd/dhcpd.leases
# Все времена в этом файле указаны в UTC (GMT), а не в вашей местной зоне.   Это
# не баг, поэтому не спрашивайте об этом.   Нет переносимого способа
# хранить арендные соглашения в местной временной зоне, поэтому не запрашивайте это как
# функция.   Если это неудобно или сбивает с толку, мы искренне
# приносим извинения.   Серьезно, не спрашивайте.
# Формат этого файла задокументирован в man-странице dhcpd.leases(5).
# Этот файл аренды был записан isc-dhcp-V3.0.5-RedHat

lease 192.168.1.100 {
  starts 4 2011/09/22 20:27:28;
  ends 1 2011/09/26 20:27:28;
  tstp 1 2011/09/26 20:27:28;
  binding state free;
  hardware ethernet 00:1b:77:93:a1:69;
  uid "\001\000\033w\223\241i";
}
...
...

Команду egrep можно использовать для получения вывода:

egrep "lease|hostname|hardware|\}" /var/lib/dhcpd/dhcpd.leases

Вывод:

lease 192.168.11.10 {
  hardware ethernet 20:6a:8a:55:19:0a;
  client-hostname "Maryam-PC";
}
lease 192.168.11.7 {
  hardware ethernet 00:16:ea:51:d3:12;
  client-hostname "parsoon";
}
lease 192.168.11.3 {
  hardware ethernet 00:17:c4:3f:84:e3;
  client-hostname "zahra-ubuntu";
}
lease 192.168.11.5 {
  hardware ethernet 58:b0:35:f1:31:2f;
}

Большинство ответов выше являются частичными. И если быть честным, то простого решения нет.
1) Вы можете разобрать файл базы данных dhcpd.leases и получить информацию о действующих арендах, но вы не получите информацию о любых ФИКСИРОВАННЫХ адресах (назначенных строкой, подобной:

host switch1      { hardware ethernet a1:b2:c3:d7:2f:bc ; fixed-address switch1.mydomain.com; }

И это также не дает действительно информации о том, когда в последний раз был отправлен dhcp ack вашему компьютеру.

2) С другой стороны, вы можете разобрать файл dhcpd.log, чтобы искать строки ack (они выглядят так):

2017-03-12T08:44:52.421114+01:00, Linuxx, info, dhcpd: DHCPREQUEST for 10.0.0.63 from 68:ab:35:59:9c:a1 via 10.0.0.1 
2017-03-12T08:44:52.421174+01:00, Linuxx, info, dhcpd: DHCPACK on 10.0.0.63 to 68:ab:35:59:9c:a1 via 10.0.0.1

Но на самом деле вам нужно сделать И ТО, и ДРУГОЕ. Сначала разберите файл журнала, а затем обновите файл информацией, полученной из файла dhcpd.leases, для недостающей информации, такой как начало и конец аренды и т. д.

Теперь: я потратил примерно 2 полных рабочих дня, чтобы создать решение, которое создает HTML-таблицу со всеми активными арендами, как фиксированными, так и динамическими. Вот код, который вы можете разместить в своей папке cgi-bin или где угодно.

#!/usr/bin/perl
#####################################################################################
# список активных арендуемых адресов dhcpd 
#   - как "фиксированные" адреса, которые обычно не помещаются в базу данных аренды
#   - и динамически назначаемые аренды, которые присутствуют в базе данных аренды
# работает для службы isc-dhcpd-server, но также должен работать для других совместимых
# dhcpd серверов. 
# производит HTML или CSV список аренды
#
# написано Марчином Госевским, BV Grupa s.c. Польша <[email protected]>
# основано на частях кода Джейсона Антмана <[email protected]> 
#
# чтобы это работало, измените $logfilename и $leasedbname ниже и измените
# регулярное выражение во второй части кода (см. ниже), чтобы оно соответствовало вашему формату строк журнала
# кроме того, вы также можете отключить обратный DNS-запрос (см. ниже), что ускоряет процесс 
# создания таблицы и бесполезно, если у вас нет обратного DNS для 
# ваших фиксированных или динамических арендуемых адресов
#
# CHANGELOG:
#     2017-03-13: начальная версия
use Socket;
use strict;
use warnings;
no warnings 'uninitialized';

# настройте это, чтобы соответствовать местоположению ваших файлов: как файла журнала, так и базы данных аренды.
# Мы используем 2 последних файла журнала из logrotate, но вы можете добавить столько, сколько хотите
my @logfilenames = ( "/var/log/LOCALAPP.dhcpd.log.1", "/var/log/LOCALAPP.dhcpd.log" );
my $leasedbname = "/var/lib/dhcp/dhcpd.leases";
my %data = ();
# необязательно, можно изменить для получения местного времени
use Time::Local;
use POSIX 'strftime';
my $now = time();
# локальные переменные, информация об аренде хранится здесь
my $ip=""; 
my $status=""; 
my $interface=""; 
my $sdate="";         # начало аренды
my $stime=""; 
my $edate="";         # конец аренды
my $etime=""; 
my $adate="";         # последнее обновление (ACK), отправленное запрашивающему серверу
my $atime="";
my $mac=""; 
my $hostname="";
my $dnsname="";       # обратный DNS-запрос для хоста

#######################################################################
# сначала соберите данные из файла журнала для всех действий ACK
#######################################################################

# собрать все строки из файлов журнала в память...
my @lines = (); my @loglines=(); 
foreach my $logfilename (@logfilenames)
{
  open LOGFILE, '<', $logfilename;
  chomp(@loglines = <LOGFILE>);
  #printf "LINES1: " . scalar @loglines . " в " .$logfilename . "\n";
  push(@lines, @loglines);
  close(LOGFILE);
}
@loglines=();
#printf "TOTAL LINES: " . scalar @lines . "\n";
foreach my $line (@lines)
{
  if ( $line !~ m/dhcpd: DHCPACK/) { next;}
  #printf "LINE: $line\n";

  ###############################
  # Измените следующую строку, чтобы регулярное выражение захватывало 6 групп из строки журнала:
  # 1 - дата
  # 2 - время
  # 3 - ip 
  # 4 - mac
  # 5 - имя хоста, если доступно
  # 6 - интерфейс
  #$line =~ m/(^.{10})T(.{8}).+,\ dhcpd: DHCPACK on (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}.*) via (.+)/;
  $line =~m/(^.{10})T(.{8}).+,\ dhcpd: DHCPACK on (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}) (.*)via (.+)/;
  # обрабатываем ввод
  $adate="$1";
  $atime="$2";
  $ip="$3";
  $mac="$4";
  $hostname="$5";
  $interface="$6";
  # добавляем некоторые 'известные' факты:
  $status="ACK";
  $sdate="";    #"FOREVER";
  $stime="";
  $edate="";
  $etime="";

  #создать/обновить запись для этого mac_addr
  #вы можете добавить дополнительную проверку здесь, если IP-адрес не дублируется в
  #истории ack и выбирать только более новый. 

  $data{"$mac"}->{'ip'} = "$ip";
  $data{"$mac"}->{'status'} = "$status";
  $data{"$mac"}->{'interface'} = "$interface";
  $data{"$mac"}->{'adate'} = "$adate";
  $data{"$mac"}->{'atime'} = "$atime";
  $data{"$mac"}->{'sdate'} = "$sdate";
  $data{"$mac"}->{'stime'} = "$stime";
  $data{"$mac"}->{'edate'} = "$edate";
  $data{"$mac"}->{'etime'} = "$etime";
  $data{"$mac"}->{'mac'} = "$mac";
  $data{"$mac"}->{'hostname'} = "$hostname";
}
#close(LOGFILE);

#######################################################################
# собираем данные из базы данных аренды для динамических адресов
# обновляем записи (для существующих) или добавляем новые записи
#######################################################################

my $isdata = 0;
my $type = "";

# эта информация отсутствует в базе данных аренды, поэтому мы просто устанавливаем
# его на значения по умолчанию
$interface="dhcpd";
$status="ACTIVE";
$adate="-";
$atime="";

open LEASEDB, $leasedbname or die $!;
foreach my $line (<LEASEDB>) 
{
  chomp($line);
  $isdata = 1 if $line =~ /^lease /;
  $isdata = 0 if $line =~ /^}/;

  if ($isdata) 
  {
    if ($line =~ /^lease/) 
    {
      $ip = (split(" ", $line))[1];
    } 
    elsif ($line =~ /^  starts/) 
    {
      ($sdate, $stime) = (split(" ", $line))[2,3];
      $sdate =~ s/\//-/g;
      $stime =~ s/;//;
    } 
    elsif ($line =~ /^  ends/) 
    {
      ($type, $edate, $etime) = (split(" ", $line))[1,2,3];
      if($type eq "never;")
      {
        $edate="forever";
        $etime=" ";
      }
      else
      {
        $edate =~ s/\//-/g;
        $etime =~ s/;//;
      }
    } 
    elsif ($line =~ /^  hardware ethernet/) 
    {
            $mac = (split(" ", $line))[2];
            $mac =~ s/;//;
    } 
    elsif ($line =~ /^  client-hostname/) 
    {
            $hostname = (split(/\"/, $line))[1];
    }
    elsif($mac ne "") 
    {
        #мы разобрали всю запись, больше нет совпадающих записей
        #данные собраны в переменные. теперь отправьте запись.

        #теперь давайте решим, обновляем ли мы запись или создаем
        #новую запись

        # проверьте дату аренды, не добавляйте истекшие аренды
        # преобразуйте время окончания аренды в местное время/дату и сравните с $now
        my $y=0; my $m=0; my $d=0; my $H=0; my $M=0; my $S=0;
        my $edatetime = $now;
        ($y, $m, $d) = split("-", $edate);
        ($H, $M, $S) = split(":", $etime);
        $edatetime = timelocal($S,$M,$H,$d,$m-1,$y);
        if($edatetime >= $now)
        {
          # теперь проверьте, существует ли запись
          if(!defined($data{"$mac"}->{'mac'}))
          {
            #данных не существует, заполняем данные по умолчанию
            $data{"$mac"}->{'mac'} = "$mac";
            $data{"$mac"}->{'interface'} = "$interface";
            $data{"$mac"}->{'ip'} = "$ip";
            $data{"$mac"}->{'hostname'} = "$hostname";
          }
          # запись существует, давайте проверим, нужно ли обновлять
          $data{"$mac"}->{'status'} = "$status";
          $data{"$mac"}->{'sdate'} = "$sdate";
          $data{"$mac"}->{'stime'} = "$stime";
          $data{"$mac"}->{'edate'} = "$edate";
          $data{"$mac"}->{'etime'} = "$etime";
          $data{"$mac"}->{'hostname'} = "$hostname";
          #мы не обновляем время ACK, потому что не имеем его
          #не раскомментируйте ниже
          #$data{"$mac"}->{'adate'} = "$adate";
          #$data{"$mac"}->{'atime'} = "$atime";

        }
    }
  }
}
close(LEASEDB);

#######################################################################
# сортировка данных
#######################################################################

# мы сортируем по IP, но вы можете сортировать по чему угодно.
my @sorted = sort { ($data{$a}{'ip'}) cmp ($data{$b}{'ip'}) } %data;

#######################################################################
# Вывод всего в HTML-таблицу
#######################################################################

my $hostnamelong="";

printf "Content-type: text/html\n\n";
printf "<html><head><title>Активные арендные адреса DHCP</title></head>\n";
printf "<style> table, th, td { border: 1px solid lightgray; border-collapse: collapse; padding: 3px; } ";
printf "tr:nth-child(even) { background-color: #dddddd; } ";
printf "</style>\n";
printf "<body>\n";
printf "<table border="1" cellpadding='6'>\n";
printf "<tr><th>IP</th><th>Статус</th><th>Интерфейс</th><th>Время аренды</th><th>Время ACK</th><th>Mac</th><th>Хост</th></tr>\n";
foreach my $key (@sorted) {
    if($data{$key}{'mac'} eq "") { next ; }

    # НАЧАЛО обратного поиска dns
    # вы можете по желанию отключить обратный DNS-запрос (закомментируйте ниже строки), что ускоряет процесс 
    # создания таблицы и бесполезно, если у вас нет обратного DNS для 
    # ваших фиксированных или динамических арендуемых адресов, раскомментируйте единственную строку ниже вместо:
    #
    # версия без обратного поиска dns:
    # $hostnamelong = $data{$key}{'hostname'};
    #
    # версия с обратным поиском DNS: 
    # НАЧАЛО
    $dnsname = gethostbyaddr(inet_aton($data{$key}{'ip'}), AF_INET);
    if($data{$key}{'hostname'} ne "")
    {
      $hostnamelong = $data{$key}{'hostname'} . " | " . $dnsname;
    }
    else
    {
      $hostnamelong = $dnsname;
    }
    $dnsname = "";
    # КОНЕЦ

    printf "<tr>";
    printf "<td>" . $data{$key}{'ip'} ."</td>";
    printf "<td>" . $data{$key}{'status'} ."</td>";
    printf "<td>" . $data{$key}{'interface'} ."</td>";
    printf "<td>" . $data{$key}{'sdate'} . " " . $data{$key}{'stime'} ." - ";
    printf $data{$key}{'edate'} . " " . $data{$key}{'etime'} ."</td>";
    printf "<td>" . $data{$key}{'adate'} . " " . $data{$key}{'atime'} . "</td>";
    printf "<td>" . $data{$key}{'mac'} ."</td>";
    printf "<td>" . $hostnamelong ."</td>";
    printf "</tr>\n";
}

printf "</table>\n";
printf "</body></html>\n";

# КОНЕЦ программы

Обратите внимание, что:
1) вышеприведенный скрипт требует небольшого изменения перед запуском в ВАШЕЙ среде, вы должны изменить местоположения файлов и одно регулярное выражение в зависимости от формата вашего файла журнала. См. комментарий в скрипте.
2) вышеприведенный скрипт не проверяет, не повторяется ли IP в таблице ACK, если 2 разных машины получили один и тот же адрес за последние дни. Это сделано по замыслу (то, что мне нужно было увидеть каждый mac-адрес, который находился в моей сети в последние дни) – вы можете легко изменить это, в коде уже есть готовый раздел для этого, просто добавьте одно условие.

Надеюсь, вам это понравится.

Формат файлов аренды изменился, или, по крайней мере, он отличается при использовании dhcpcd5. Чтобы просмотреть аренду, которую вы имеете на wlan0 для WiFi-сети MyNetwork, вам нужно будет взглянуть на этот файл (или что-то подобное): /var/lib/dhcpcd5/dhcpcd-wlan0-MyNetwork.lease.

Этот файл является двоичным файлом. (Почему? Я не знаю. Возможно, чтобы сэкономить несколько драгоценных циклов процессора на его анализе? Брр.) Чтобы просмотреть его, используйте dhcpcd --dumplease, который анализирует двоичные данные из STDIN и выводит их в читаемом виде:

cat /var/lib/dhcpcd5/dhcpcd-wlan0-MyNetwork.lease | dhcpcd --dumplease

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

dhcpcd --dumplease wlan0

На самом деле я написал что-то на bash, чтобы попытаться это сделать. Он записывает каждый IP-адрес в файл с одним и тем же именем, поэтому, если другой появится снова, он перезапишет предыдущий файл, таким образом дублирующих записей не будет.
Он также будет использовать oui.txt, чтобы узнать производителя интересующего MAC-адреса.

Посмотрите, можете ли вы это использовать.

#!/bin/bash

pid=$$

while read i
do
        echo $i | egrep -qi '^lease|hardware|starts|ends|hostname' || continue
        if [[ $i =~ [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} ]]; then
                ip=$(echo $i | awk '{print $2}')
                printf "IP адрес: " > /root/$ip.$pid
                echo $ip >> /root/$ip.$pid
        elif [[ $i =~ starts ]]; then
                printf "\tНачало аренды: " >> /root/$ip.$pid
                echo $i | awk '{print $3,$4}' | tr -d ';' >> /root/$ip.$pid
        elif [[ $i =~ ends ]]; then
                printf "\tАренда заканчивается: " >> /root/$ip.$pid
                echo $i | awk '{print $3,$4}' | tr -d ';' >> /root/$ip.$pid
        elif [[ $i =~ ethernet ]]; then
                mac=$(echo -n $i | awk '{print $3}' | tr -d ';')
                oui=$(echo -n $mac | tr ':' '-' | cut -c1-8)

                printf "\tMAC адрес: " >> /root/$ip.$pid
                echo $mac >> /root/$ip.$pid
                printf "\tПроизводитель: " >> /root/$ip.$pid
                grep -i $oui /usr/share/hwdata/oui.txt | awk '{print $3,$4,$5,$6}' >> /root/$ip.$pid
                printf "\n" >> /root/$ip.$pid
        elif [[ $i =~ hostname ]]; then
                printf "\tИмя хоста: " >> /root/$ip.$pid
                echo $i | awk '{print $2}' | tr -d ';' >> /root/$ip.$pid
        fi

done < /var/lib/dhcpd/dhcpd.leases

cat /root/*.$pid
echo "Всего аренд: $(ls -l /root/*.$pid | wc -l)"
rm -rf /root/*.$pid

Обычно я получаю быстрый снимок аренды DHCP через:

arp -a

На Debian иногда этот кэш требует некоторое время для обновления – например, только отражает новые значения через 30 минут после изменения IP.

Вывод данных выглядит так:

Хост                                 Ethernet Address    Netif Expire    Flags
101.10.12.115                        a4:bb:6d:e3:da:fc   em0 17m5s     
101.10.12.120                        8a:6b:f2:8f:1a:43   em0 12m48s   

Ответ или решение

Для получения информации о назначенных DHCP-адресах с сервера DHCP, работающего на базе пакета isc-dhcpd, существует несколько способов.

Основной способ: использование dhcp-lease-list

Вы можете воспользоваться утилитой dhcp-lease-list, которая является простым скриптом на Perl и поддерживает старые версии DHCP. Для работы этой утилиты вам понадобится указать путь к файлу аренды. В вашем случае команда будет выглядеть так:

dhcp-lease-list --lease PATH_TO_LEASE_FILE

Где PATH_TO_LEASE_FILE — это путь к файлу аренды, который обычно находится по адресу /var/lib/dhcp/dhcpd.leases:

dhcp-lease-list --lease /var/lib/dhcp/dhcpd.leases

Эта команда выведет информацию о назначенных IP-адресах, а также информацию о MAC-адресах, именах хостов и сроках действия аренды.

Альтернативный способ: ручной анализ файла аренды

Если у вас нет возможности использовать dhcp-lease-list, вы можете вручную проанализировать файл аренды. Можно использовать команду egrep, чтобы извлечь только интересующие строки из файла аренды:

egrep "lease|hostname|hardware|\}" /var/lib/dhcp/dhcpd.leases

Эта команда выдаст информацию о всех арендах в понятном формате.

Обработка фиксированных адресов

Следует отметить, что предоставленная информация касается только динамически назначенных арендуемых адресов. Фиксированные адреса (например, адреса, назначенные в конфигурации DHCP через блоки host) не попадают в файл аренды. Чтобы получить информацию о фиксированных адресах, можно проанализировать конфигурационные файлы DHCP.

Дополнительный метод: использование логов DHCP

Для получения подробной информации о резервированных адресах и аутентификационных подтверждениях (DHCP ACK) можно проанализировать логи сервера DHCP. Например:

grep 'DHCPACK' /var/log/dhcpd.log

Эта команда отобразит все сообщения ACK, что может помочь определить, когда и как были назначены адреса.

Использование альтернативных утилит

Если вы используете dhcpcd, команду для отображения lease можно выполнить так:

dhcpcd --dumplease

Или для получения конкретной lease, привязанной к интерфейсу:

dhcpcd --dumplease wlan0

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...