Учет работы dialup-пользователей в системе FreeBSD
Опубликована в журнале "Системный администратор", декабрь 2003
Если Вы вознамерились стать провайдером услуг Интернета, имея сервер
под управлением FreeBSD и несколько модемов на входе, то одной из
первых задач, которая встанет перед Вами, будет проблема учета времени
работы Ваших пользователей.
Безусловно, существует целый ряд готовых программных продуктов, в той
или иной степени решающих задачу учета (и тарификации) работы
пользователей, выходящих в Интернет через модемный пул сервера. Однако
для "частного" использования вряд ли целесообразно "прикручивать" к
Вашей системе RADIUS или что-то подобное. В данной статье мы
попытаемся рассмотреть общие принципы решения задачи учета работы
"дайлапников", скажем так, подручными средствами. Конкретную
реализацию оставим за рамками данной статьи.
Итак, для того, чтобы посчитать время работы пользователя, мы должны
решить три задачи: определение момента входа пользователя в систему,
определение момента выхода из системы и сохранение информации о данном
соединении для последующего использования. Если мы изберем авансовую
систему оплаты доступа (когда "утром деньги - вечером стулья"), то
потребуется также контролировать состояние счета абонента в процессе
его работы, и отключать его, если аванс на его счету будет исчерпан.
Также может потребоваться и учет потребленного трафика.
Поскольку мы не ставим своей задачей разработку серьезной и
универсальной системы, то будем стремиться максимально использовать
то, что у нас уже есть. Также мы не будем связываться с
программированием, чтобы основная идея не затерялась в дебрях
алгоритмизации, а ограничимся скромными скриптами на Shell и Perl.
Посмотрим, какие "встроенные" средства учета нам доступны.
Прежде всего, это wtmp - файл, в котором хранится информация о
последних соединениях. Просмотреть содержащуюся в нем информацию можно
командой last. Как видно, используя эту команду, можно получить
подробную информацию о моментах входа-выхода пользователя в систему и
о продолжительности соединения (last с ключом -s (last -s) отображает
продолжительность соединения в секундах) за период с момента последней
ротации (перезаписи) файла wtmp. Период ротации выбирается в
зависимости от нагрузки на систему и задается с таким расчетом, чтобы
его размер не достигал катастрофических значений. Учитывая, что мы
рассматриваем небольшую систему, обновлять файл wtmp достаточно
ежемесячно (cкрипт, производящий обновление, в этом случае скорее
всего будет располагаться в /etc/periodic/monthly).
Второй важный инструмент, который нам понадобится - команда who,
которая выводит информацию о пользователях, подключенных к системе в
данный момент. Любители языка С могут воспользоваться файлом utmp, из
которого информация о текущих подключениях и черпается.
С остальными вспомогательными средствами познакомимся в процессе
рассмотрения конкретных методов учета.
Итак, для начала рассмотрим способы, которыми можно определить момент
входа пользователя в систему. В простейшем случае, когда информация о
работе пользователей нужна нам "задним числом", вполне достаточно
будет анализа данных, возвращаемых командой last. Например, в конце
месяца мы можем просто просуммировать продолжительности соединений для
каждого пользователя, и выставить счета на основе этой информации
(простейший скрипт lastreader.pl, выполняющий данную функцию,
представлен на вкладке ниже). Или обрабатывать эту информацию
ежедневно, сохраняя результаты в свои файлы. Однако такой способ учета
не позволяет контролировать текущее состояние счета абонента с тем,
чтобы не допустить превышения установленного лимита (или округляет
такой контроль до суток, например). Так же если нам нужно блокировать
работу определенных пользователей без изменения информации в системных
файлах, то определять вход пользователя нужно в режиме реального
времени.
lastreader.pl:
#!/usr/bin/perl -w
open(LAST, 'last -s|') || die 'Error'; # открываем last на чтение
while() {
chomp;
if($_) {
($user, $tty) = split(/s/); # выделяем имя пользователя и устройство
if($tty =~ /ttyd/) { # Далее обрабатываем только модемные
# соединения ttydX
$_ =~ s/(s*(d+))/$1/g; # выделяем значение в скобках -
# продолжительность соединения в секундах
if(($user)&&($1) {
$totals{$user} += $1; # суммируем продолжительности по пользователям
}
}
}
}
close(LAST);
@res = sort keys %totals; # сортируем по алфавиту пользователей
foreach $item (@res) {
print "$item - $totals{$item}n"; # выводим результат на экран
}
exit(1);
Одним из самых очевидных способов является просмотр списка находящихся
в данный момент в системе пользователей. Например, если запускать
команду who каждые пять секунд, то мы сможем определить появление в
системе пользователя с точностью до этого значения. Присутствие в
системе пользователя можно определить и по команде last - подключенные
в данный момент абоненты отмечены как "still logged in".
Однако рассмотренный выше способ чреват нерациональным расходом
ресурсов (постоянные вызовы "левых" программ даром не проходят) и
точностью, ограниченной периодом вызова вспомогательных команд. Чтобы
определить более элегантный способ, рассмотрим процесс входа в систему
с модемного пула.
В данной статье остановимся на рассмотрении двух способов входа в
систему - pap- и login-аутентификации. В первом случае
последовательность такова:
* после того, как модемы установят соединение, конкретное
ttyd-устройство сопоставляется с интерфейсом pppX;
* tty-device - устройство tty, через которое осуществляется соединение,
* speed - скорость на порту (на tty-устройстве),
* local-IP, remote-IP - соответственно локальный и удаленный IP-адреса,
* ipparam - дополнительные параметры.
Для нас интерес представляют первые два параметра, передаваемые в
скрипт. К сожалению, имя пользователя в ip-up не передается, и мы
можем узнать только о том, что какой-то пользователь выполнил
подключение.
* peer-name - имя пользователя, установившего соединение,
* user-name - пользователь, с чьими правами запущен pppd (как правило, root),
* tty-device - устройство tty, через которое осуществлено соединение,
* speed - скорость на порту (на tty-устройстве).
В данном случае, как видно, в скрипт передается еще один полезный
параметр - имя авторизованного пользователя. Таким образом, мы можем
сразу установить факт входа в систему конкретного абонента.
Небольшая неприятность появляется в случае login-аутентификации
(например, при входе в систему через стандартный скрипт). Если в
качестве стандартной оболочки пользователю задать pppd, то процесс
входа в систему будет следующим:
* после того, как модемы установят соединение, конкретное
ttyd-устройство сопоставляется с интерфейсом pppX;
* проводится login-аутентификация;
* запускается программа, указанная в /etc/passwd как стандартная
оболочка (в нашем случае это демон pppd);
* отрабатывается скрипт ip-up (если есть), как правило,
располагающийся в /etc/ppp;
Поскольку аутентификация уже выполнена, то средствами pppd она не
осуществляется, и потому скрипт auth-up не отрабатывается. Таким
образом, если мы попытаемся использовать для регистрации входа
пользователя в систему именно его, то при login-аутентификации
пользователь зарегистрирован не будет. Поэтому, если мы хотим оставить
пользователю возможность устанавливать соединение и через стандартный
скрипт (с помощью login-аутентификации), то для определения его входа
в систему нам остается только скрипт ip-up, который отрабатывается в
любом случае. Однако теперь мы знаем только устройство, через которое
пользователь подключен, и нам придется сопоставить устройство с
конкретным пользователем самим. Для этого нам как раз и пригодится
команда who (например, имя пользователя возвратит следующая команда:
who | grep "ttydX" | awk '{print $1;}', где "Х" - номер нужного нам
ttyd-устройства).
Вторая задача - определение момента, когда пользователь покидает
систему, - решается аналогично. Это может быть либо проверка
подключенных пользователей периодическим вызовом who, либо
использование скриптов ip-down и auth-down. Первый отрабатывается при
выходе из системы пользователя, вошедшего через pap-аутентификацию
(для которого отрабатывался скрипт auth-up), второй - при завершении
сеанса связи. Однако нужно иметь в виду, что в некоторых случаях
"down-скрипты" могут не отрабатываться, например, при аварийной
перезагрузке системы.
Особо нужно отметить, что вышеописанные ppp-скрипты исполняются с
правами суперпользователя (root). С одной стороны, это хорошо,
поскольку позволяет нам совершать любые действия, но с другой -
требует особой осторожности и аккуратности. Также не забывайте
поддерживать права доступа к данным файлам как r-x------ (на стадии
отладки можно поставить rwx------, но потом возможность записи
необходимо снять).
Сохранение статистической информации проблем вызвать не должно. Если
мы знаем, когда пользователь вошел в систему и когда ее покинул, то
достаточно просто сохранить эту информацию в файл или базу данных.
Выбор конкретного формата (база MySQL, единый текстовый файл,
индивидуальные файлы для хранения статистики по каждому пользователю)
оставим на совести администратора.
Теперь несколько слов о том, как можно определить обнуление счета
абонента в процессе работы и соответственно дальнейшую работу оного
пресечь самым жестоким образом. Основных способов здесь тоже два -
постоянный (периодический) контроль остатка, и предварительное
вычисление остатка. В первом случае специальный скрипт должен время от
времени определять остаток на счете пользователя (который можно
хранить в БД или текстовом файле), вычитать из него стоимость
наработки на текущий момент, и в случае отрицательной разности давать
команду на отключение пользователя. Суть второго способа заключается в
следующем: при входе пользователя в систему определяется остаток на
его счете и вычисляется время, на которое абоненту данной суммы хватит
при действующем тарифе. Затем дается задание планировщику (это может
быть как специально разработанный скромный скриптик, так и системная
команда at) отключить данного пользователя через данное время.
Естественно, нужно предусмотреть ситуацию, когда пользователь
отключится добровольно: в этом случае нужно давать отмену
планировщику, как только будет обнаружено отсутствие пользователя в
системе, а также программа-киллер (которая будет заниматься черным
делом отключения пользователя) должна уметь определять, нуждаются ли
еще в ее услугах. Первое нужно, чтобы избежать накапливания сотен
заданий на отключение пользователя через дни и месяцы, в то время как
он уже давно пьет пиво вдали от родного Интернета, второе - для
исключения банальных ошибок.
Для принудительного завершения работы пользователя рекомендуется
использовать утилиту killpppd (для FreeBSD ее можно найти в коллекции
портов). Принимая в качестве параметров peer-name и tty-device,
утилита осуществляет корректное завершение работы всех процессов,
связанных с данным устройством.
Если нам нужно еще и учитывать трафик, потребляемый пользователем, то
самый простой вариант - использовать для этих целей ipfw (брандмауэр,
входящий в состав FreeBSD). При входе пользователя в систему запускаем
подсчет трафика на конкретном устройстве (если точнее, то подсчет
трафика запускается на IP-адрес, однако определить адрес, присвоенный
устройству, как правило, проблемой не является):
ipfw add 10000 count ip from any to 100.100.100.5 out.
Приведенная выше строка заставит брандмауэр считать входящий
Интернет-трафик ("out" говорит о том, что будет учитываться трафик,
исходящий из FreeBSD по направлению к клиенту, для клиента этот трафик
будет, соответственно, входящим), приходящий на адрес 100.100.100.5 с
любого источника. 10000 - номер правила, которое назначается для
данной операции. Естественно, для каждого соединения нужно будет
назначать свободный в данный момент номер правила (правила с
одинаковым номером также будут корректно работать, но при удалении
одного из них возникнут проблемы).
Затем, когда пользователь отключается, считываем его наработку (ipfw
show 10000) и удаляем правило (ipfw delete 10000). А наработку,
соответственно, пишем в файлик для последующего предъявления.
Обобщая сказанное, можно сделать вывод, что встроенных средств FreeBSD
вполне достаточно, чтобы за ночь "на коленке" написать биллинговую
систему для обслуживания нескольких десятков пользователей.
Если нам нужны только сведения о том, сколько пользователь провел
времени в Сети за месяц, оптимальным будет обработка результата,
выдаваемого по last -s.
Если мы предоставляем доступ в Интернет только с использованием
ppp-аутентификации (например, pap), то для определения входа
пользователя в систему вполне может служить скрипт auth-up. Если наш
сервер будет поддерживать и login-аутентификацию, то придется
использовать скрипт ip-up. В этом случае имя пользователя придется
определять вручную, основываясь на сведениях, предоставляемых командой
who. Эту же команду, видимо, придется использовать и для определения
момента отключения пользователя, поскольку метод, связанный со
скриптами ip-down и auth-down, слишком ненадежен. Хотя в целях
экономии ресурсов можно использовать комбинацию этих методов:
отключение производить по команде соответствующего скрипта, но
выполнять контроль по команде who через относительно большой
промежуток времени (например, раз в 5 минут).
Конечно, для построения эффективной, точной и надежной системы учета
придется прибегнуть к программированию на С, однако в большинстве
случаев концепция остается прежней - контроль файлов wtmp (last) и
utmp (who).
Дополнительные материалы:
man last(1) - команда last;
man who(1) - команда who;
man grep(1), man awk(1) - команды обработки текстовых строк;
man at(1) - отложенное выполнение команды;
man pppd(8) - информация по auth-up, ip-up, auth-down, ip-down;
man ipfw(8) - информация по ipfw, в том числе по подсчету трафика;
man periodic(8) - дополнительные сведения по ротации wtmp;
man utmp(5) - обработка utmp на С;
man wtmp(5) - обработка wtmp на С.