Вопрос или проблема
Существует ли команда, с помощью которой я могу узнать, какие IP-адреса были назначены сервером dhcpd?
Пакет isc-dhcpd
версии 4.3.1
содержит эту команду для отображения аренды:
dhcp-lease-list --lease PATH_TO_LEASE_FILE
Это простой Perl-скрипт, который также поддерживает более старые версии DHCP. Вы можете увидеть копию в исходном коде Debian или в официальной дистрибуции DHCP (в contrib/
).
Вывод выглядит следующим образом:
$ 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 на вашем сервере.