Как себя чувствует RADIUS?

в 10:45, , рубрики: perl, radius, tcpdump, системное администрирование, Телекомы, метки: , ,

Сегодняшний день начался с неожиданного. Один из наших Заказчиков сообщил, что он считает, что RADIUS сервер не справляется с работой, поскольку NAS отсылает большое количество запросов повторно. Так как это достаточно серьезное обвинение, его следовало немедленно проверить. Описанию того как это сделать, посвящена эта статья (материал ориентирован на начинающих разработчиков и системных администраторов).

Первый шаг довольно очевиден. Необходимо получить дамп сетевой активности во время максимальной нагрузки на RADIUS-сервер. Данных 10-15 минутного интервала вполне достаточно для анализа. Для Unix-like систем получить эти данные можно при помощи tcpdump, но поскольку, в нашем случае, RADIUS-сервер работал на Windows-системе, пришлось прибегнуть к помощи Wireshark. Выполнив захват пакетов, Wireshark показал эту замечательную картинку:

image

Хотя этот интерфейс очень удобен для интерактивной работы, для получения статистики, требуется перевести данные в форму более удобную для автоматизированной обработки. К счастью, Wireshark умеет сохранять данные в текстовый формат:

image

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

+---------+---------------+----------+
10:03:59,092,544   ETHER
|0   |ff|ff|ff|ff|ff|ff|00|0c|29|6b|5a|a3|08|00|45|00|00|e5|b6|15|00|00|80|11|04|d4|c0|a8|7d|ce|c0|a8|7f|ff|00|8a|00|8a|00|d1|ee|a9|11|0e|d1|fb|c0|a8|7d|ce|00|8a|00|bb|00|00|20|46|44|46|41|44|44|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|00|20|45|4a|45|4f|46|45|45|46|46|43|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|43|41|42|4e|00|ff|53|4d|42|25|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|11|00|00|21|00|00|00|00|00|00|00|00|00|e8|03|00|00|00|00|00|00|00|00|21|00|56|00|03|00|01|00|00|00|02|00|32|00|5c|4d|41|49|4c|53|4c|4f|54|5c|42|52|4f|57|53|45|00|01|00|80|fc|0a|00|53|50|33|00|25|03|00|00|00|00|fc|5e|20|03|02|00|05|01|03|12|01|00|0f|01|55|aa|00|

+---------+---------------+----------+
10:03:59,184,049   ETHER
|0   |ff|ff|ff|ff|ff|ff|00|1d|7d|3a|f9|3a|08|06|00|01|08|00|06|04|00|01|00|1d|7d|3a|f9|3a|0a|15|52|44|00|00|00|00|00|00|0a|15|52|41|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|00|
...

С этим уже можно работать. Из спецификаций RFC 768, 2865, 2866 нам известны смещения интересующих нас полей (смещения представлены в десятеричной системе счисления, значения — в шестнадцатеричной):

+23 = 11 (UDP)
+36 = 07 14 (RADIUS Destination Port = 1812)
    +42 - (01 = Access Request)
    +43 - ID
+34 = 07 14 (RADIUS Source Port = 1812)
    +42 - (02 = Access Accept; 03 = Access Reject)
    +43 - ID
+36 = 07 15 (RADIUS Destination Port = 1813)
    +42 - (04 = Accounting Request)
    +43 - ID
+34 = 07 14 (RADIUS Source Port = 1812)
    +42 - (05 = Accounting Response)
    +43 - ID

Обработать данные нам поможет Perl:

Скрипт

use Time::Local;

my $tm;
my $ln;

my %auth;
my %acco;

my %discarded;
my %accepted;

# Обрабатываем все поступающие строки
while (<>) {
  # Убираем перевод строки
  chomp;
  s/|0s+|//;
  # Регулярным выражением извлекаем время
  if (/^(dd):(dd):(dd),(ddd),ddd/) {
      # Приводим значение времени к количеству миллисекунд с начала эпохи
      # Поскольку дата нас не интересует, берем 01/01/1970
      $tm = timelocal($3, $2, $1, 01, 01, 1970) * 1000 + $4;
  }
  if (/|/) {
      $ln .= $_;
  } else {
      if ($ln) {
          # Разбиваем строку на байты
          my @packet = split /|/, $ln;
          # Нас интересуют только UDP-пакеты
          if ($packet[23] eq '11') {
              my $code = $packet[42];
              my $id   = $packet[43];
              # RADIUS Request
              if ($packet[36] eq '07' and 
                 ($packet[37] eq '14' or $packet[37] eq '15')) {
                  # Аккаутинг
                  if ($code eq '04') {
                      # Повтор ID запроса означает потерю соответствующего ответа
                      if (exists($acco{%id})) {
                          $discarded{$code}++;
                      }
                      # Сохраняем время получения запроса
                      $acco{%id} = $tm;
                  # Авторизация
                  } else {
                      if (exists($auth{%id})) {
                          $discarded{$code}++;
                      }
                      $auth{%id} = $tm;
                  }
              }
              # RADIUS Response
              if ($packet[34] eq '07' and 
                 ($packet[35] eq '14' or $packet[35] eq '15')) {
                  # Аккаутинг
                  if ($code eq '05') {
                      if (exists($acco{%id})) {
                          if ($tm - $acco{%id} > 1000) {
                              # Если прошло более 1 секунды, считаем пакет отброшенным
                              $discarded{$code}++;
                          } else {
                              # Иначе увеличиваем количество успешно обработанных пакетов
                              $accepted{$code}++;
                          }
                          delete($acco{%id});
                      }
                  # Авторизация
                  } else {
                      if (exists($auth{%id})) {
                          if ($tm - $auth{%id} > 1000) {
                              $discarded{$code}++;
                          } else {
                              $accepted{$code}++;
                          }
                          delete($auth{%id});
                      }
                  }
              }
          }                      
          $ln = '';
      }
  }
}

# На всякий случай, учитываем ID, для которых не сформирован ответ
while (($id, $tm) = each(%auth)) {
  $discarded{'01'}++;
}

while (($id, $tm) = each(%acco)) {
  $discarded{'04'}++;
}

#Выводим результаты обработки
print "Auth  Accepted: $accepted{'02'}n";
print "Auth  Rejected: $accepted{'03'}n";
print "Acco Responsed: $accepted{'05'}nn";

print "Auth Request  Discarded: $discarded{'01'}n";
print "Auth Accept   Discarded: $discarded{'02'}n";
print "Auth Reject   Discarded: $discarded{'03'}n";
print "Acco Request  Discarded: $discarded{'04'}n";
print "Acco Response Discarded: $discarded{'05'}n";

Поскольку мы все еще работаем в Windows, bang-комментарий в исходном коде не обязателен (кстати, Perl для Windows можно взять здесь). Для работы со временем, не забываем подключить Time::Local. Скрипт запускаем на выполнение следующей командой:

perl stat.pl dump.txt > dump

Скрипт работает довольно долго (в основном из-за split-а большого объема данных), но один раз можно и потерпеть. В конечном итоге, формируется следующий результат:

Auth  Accepted: 195
Auth  Rejected: 907
Acco Responsed: 6058

Auth Request  Discarded: 13
Auth Accept   Discarded: 
Auth Reject   Discarded: 
Acco Request  Discarded: 1476
Acco Response Discarded: 

Здесь мы видим, что за 10 минут было успешно обработано 6058 пакетов аккаунтинга и 1102 пакетов авторизации (из них на 907 запросов был сформирован Reject). Все запросы были обработаны сервером не более чем за 1 секунду. По непонятной причине, NAS отослал 13 повторных запросов авторизации и 1476 запросов аккаунтинга. Возможно это связано с потерями UDP-пакетов при передаче их по сети.

Приведенный выше скрипт можно улучшить, для получения более детальной статистики по времени отклика, но в целом задачу можно считать выполненной. RADIUS-сервер работает нормально.

Автор: GlukKazan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js