ДЕНИС ПЕПЛИН
Скрипты для подсчета трафика
Пример реализации в FreeBSD
Среди прочих задач системного администратора «написание скриптов» занимает немало места. Но существует эта задача не сама по себе, а в качестве средства автоматизации, призванного свести объем рутинной работы к минимуму. Вместе с тем сам язык, на котором пишутся скрипты (а в данной статье пойдет речь о Bourne shell), достаточно развит, чтобы перевести скрипты из разряда «набора команд» для оптимизации в разряд небольших программ, выполняющих в системе полезные функции. Более того, во многих случаях применение именно этого языка наиболее оправданно, поскольку позволяет упростить до предела изначально сложные задачи.
Довольно часто с помощью скриптов решается задача учета трафика. Они могут применяться не только для обработки выходных данных, но и для настройки параметров учета. В качестве примера можно привести программу ipa, которая считает трафик по правилам ipfw или ipf. Если количество правил невелико и меняется редко, то и сами правила и параметры настройки ipa несложно добавить вручную. Другое дело, если количество правил достигает нескольких десятков или больше, и они часто меняются.
Крайний случай такой ситуации – динамическое распределение IP-адресов. Причём это не обязательно dhcp: в условиях офиса не всегда за каждым пользователем закреплено отдельное рабочее место, и распределение адресов по пользователям приобретает некоторую динамичность.
В таких условиях в большинстве случаев наиболее целесообразным выглядит установка прокси-сервера (например, Squid+ntlm) и учет проходящего через него трафика. С точки зрения иерархии сети трафик проходит через прикладной уровень стека TCP/IP и подсчитывается именно там. Действительно, это самый очевидный способ: такое понятие, как «пользователь», появляется именно на прикладном уровне. На сетевом уровне никаких пользователей нет, есть лишь IP-адреса.
Именно поэтому попытки посчитать трафик по пользователям на сетевом уровне приносят лишь проблемы. Ведь адрес нельзя закрепить именно за пользователем, можно лишь закрепить его за отдельным компьютером и надеяться, что пользователь не пересядет на другое место, а на его место не сядет другой. Применение dhcp в этом случае также нужно исключить.
Не совсем стандартный путь – применение информации, полученной с прикладного уровня, для учета трафика на сетевом уровне. Объем этой информации должен быть минимален: необходимо лишь связать имя пользователя с IP-адресом компьютера, на котором он работает. Для первоначального конфигурирования потребуется список вида:
user1 192.168.0.11
user2 192.168.0.123
...
Для последующей динамической перенастройки достаточно зафиксировать факт входа в сеть (выхода из сети) и все те же имя и адрес. Если обозначить вход/выход знаками +/-, получится список вида:
+ user3 192.168.0.98
- user2 192.168.0.123
...
Его можно обобщить и на случай первоначального конфигурирования, тогда перед каждой строкой будет знак “+”.
Сам процесс динамического переконфигурирования системы учета трафика таким образом разделяется на два этапа: сбор информации согласно приведенному выше протоколу и собственно настройка системы учета. Наличие минимального одностороннего протокола не только позволяет разделить скрипт на две части, но даже в случае необходимости разнести эти части по разным серверам в случае, если программное обеспечение, регистрирующее вход пользователей, и шлюз во внешнюю сеть располагаются на разных серверах.
Разделение скрипта на части полезно еще и по соображениям сохранения простоты каждой из частей. Несмотря на то что скриптовый язык довольно развит, при написании скриптов рекомендуется придерживаться так называемого принципа KISS (Keep It Simple Stupid). Чем примитивнее скрипт, тем меньше вероятность того, что в нем допущены ошибки. Сложные конструкции не приветствуются.
В отличие от языков более низкого уровня (например, C), скрипты идеально подходят для организации взаимодействия программ. Именно это их качество помогает решать сложные задачи простыми способами – несравнимо проще, чем это можно было бы сделать на том же C.
Хорошей иллюстрацией этому утверждению может послужить, к примеру, задача сбора информации на прикладном уровне.
Зачастую в качестве контроллера домена NT-сети применяется пакет Samba. Как известно, он помогает вообще избавиться от серверной ОС Windows во многих случаях. В составе пакета Samba есть программа smbstatus. С ее помощью можно получить список подключений к сетевым ресурсам и информацию о блокировках файлов. Формат данных, выдаваемых этой программой, примерно такой:
Data store user group 76905 buhgalter (192.168.0.3) Fri Apr 11 15:25:38 2003
76905 DENY_WRITE 0x20089 RDONLY EXCLUSIVE+BATCH /pub/file.txt Thu Apr 11 17:30:46 2003
|
Перевести эти данные в список подключенных пользователей можно с помощью следующего скрипта (назовем его count_samba):
#!/bin/sh
smbstatus | grep -E "([0-9]{1,3}.){3}[0-9]{1,3}" | colrm 1 13 | grep -v -E "^nobody|^root"
| awk "{printf(" %s %s ",$1,$5)}" | sort | uniq | sed "s/[()]//g"
Данные от smbstatus преобразуются последовательностью утилит: сначала выбираются строки с IP-адресами, затем удаляется информация об именах ресурсов, удаляются строки с пользователями nobody и root, выводится информация о пользователе и адресе, которая затем сортируется, повторяющиеся строки удаляются и, наконец, удаляются скобки вокруг IP-адреса.
Символ «|» означает связывание программ с помощью канала (pipe), то есть стандартный вывод предыдущей программы связывается со стандартным вводом последующей. Это простейшее средство однонаправленного межпроцессного взаимодействия довольно широко применяется в UNIX именно из-за своей простоты. Как известно, чем проще инструмент, тем он надежнее – каналы как раз и есть этот простой и надежный инструмент. Одной из основ идеологии UNIX является использование нескольких простых утилит, связываемых через такие каналы, вместо одной большой и сложной программы (разумеется, только в тех случаях, когда этого достаточно для решения поставленной задачи).
Символ «» в конце строки указывает sh считать следующую строчку продолжением предыдущей. Можно, конечно, написать все в одну строчку, но в таком случае читабельность скрипта уменьшится.
В примере выше каждая из используемых утилит обрабатывает входной поток построчно. Посмотреть, как изменяются данные от одной программы к другой, можно последовательно подсоединяя к выходному потоку по одной программе, выстраивая их в цепочку.
Программа grep выбирает линии, соответствующие указанному регулярному выражению. Ключ -E используется вместе с расширенными регулярными выражениями. Выражение «([0-9]{1,3}.){3}[0-9]{1,3}» соответствует любому IP-адресу, а «^nobody|^root» – строкам, начинающимся с «nobody» или «root» (символ «|» здесь означает ИЛИ). Ключ -v указывает выбирать строки, не соответствующие заданному регулярному выражению. Подробнее о регулярных выражениях можно почитать на странице руководства grep(1).
Awk представляет собой язык программирования, предназначенный для работы с таблицами. Несмотря на то что язык этот не самый простой, в нашем случае awk вполне подходит под определение простой утилиты. Все дело в том, как awk обрабатывает входной поток: каждая строка разбивается на поля, значения которых присваиваются внутренним переменным awk $1, $2 и т.д. по числу полей. Разделителем полей по умолчанию служат пробелы или табуляция. Воспользовавшись этим свойством awk, можно легко оставить в каждой строке только первое и пятое поле (имя пользователя и адрес), напечатав их встроенной функцией printf (значение пробела в начале строки будет пояснено позже).
Вмешивается в эту идиллическую картину только имя ресурса, которое может содержать пробелы (как в примере выше) и с точки зрения awk состоять из нескольких полей. Поскольку в выводе smbstatus имя ресурса занимает фиксированное число символов, его можно удалить с помощью colrm, параметрами которой служат номера первой и последней колонки текста, которые нужно удалить (colrm каждый символ в строке воспринимает как отдельную колонку).
Sort и uniq в отдельном пояснении не нуждаются, их назначение полностью понятно из названия, а вот sed более интересен. Это потоковый редактор (Stream EDitor), который помимо всего прочего может заменять соответствующие заданному регулярному выражению подстроки. В приведенном примере это встроенная команда sed "s" (substitute), регулярное выражение «[()]» соответствует символам «(« и »)», которые заменяются на «», то есть просто удаляются. Ключ «g» указывает заменять все символы в строке, попадающие под регулярное выражение, а не только первый.
Полученных данных вполне достаточно для первоначальной настройки системы учета трафика, для динамической переконфигурации потребуется информация в формате «+/- user address». Далеко не самое изящное решение, но, очевидно, в данной ситуации самое простое – сохранять информацию о текущих подключениях, а затем через некоторый промежуток времени сравнивать со вновь полученной информацией. Очень подходит для этой цели diff. Весь скрипт выглядит так (файл count_user):
#!/bin/sh
count_list="samba pppd"
sleeptime=20
TMPDIR="/var/tmp"
export TMPDIR
tmpfile=`mktemp -q -u -t count` || exit 1
cleanup ()
{
rm -f $tmpfile.?
}
trap : 1
trap : 2
trap : 15
set -T
trap "cleanup; exit" 1 2 15
bindir=$(dirname $0) || exit 1
count_eval=""
for progname in $count_list
do
if [ $count_eval ] ; then
count_eval="$count_eval ; $bindir/count_$progname"
else
count_eval="$bindir/count_$progname"
fi
done
count_num=0
touch $tmpfile.$count_num
while [ ! ]
do
if ! ps $PPID >/dev/null 2>&1
then
echo "Parent process died. Cleanup and exit." >&2
break
fi
count_old=$count_num
test $count_num -eq 1
count_num=$?
eval $count_eval | tee $tmpfile.$count_num | diff -b -u $tmpfile.$count_old - | grep "^[+-] [a-z]" | sort -r
sleep $sleeptime
done
cleanup
Основная содержательная часть скрипта уместилась на трех строчках: текущее состояние сравнивается с предыдущим с помощью diff, вывод которой фильтруется grep и сортируется в обратном порядке (так что сначала пойдут строки с «-», а затем «+». Далее следует задержка на несколько секунд, после чего сравнение повторяется. Имя временного файла выбирается один раз, файлы текущего и предыдущего состояния отличаются расширениями 0 или 1. Перехват прерываний необходим для удаления временных файлов по завершении работы.
В качестве источника данных о подключениях используются сразу два скрипта: count_samba и count_pppd. Возможно, что для учета трафика пользователей, подключенных через удаленный доступ, лучше было бы использовать сам pppd, но скрипт count_pppd – очень неплохой пример того, как просто использовать для получения списка пользователей не только samba, но и любое другое приложение, способное выдать такой список. В данном случае это who, а адреса берутся из /etc/ppp/options.:
#!/bin/sh
who | awk "{if(substr($2,1,4)=="cuaa")printf("%s %s ",$1,$2)}" | while read user ttyname
do
grep ":" /etc/ppp/options.$ttyname | awk -F: "{printf(" %s %s ",user,$2)}" user=$user
done
Предполагается, что в данном случае за каждым портом закреплен свой IP-адрес.
Для хранения временных файлов используется специальный каталог с правами 1777 («sticky» бит). Благодаря этому биту только владелец файла, каталога или суперпользователь может удалить или переименовать такой файл. В качестве дополнительной меры безопасности необходимо позаботиться о том, чтобы никто не смог предугадать имя временного файла. Именно эту задачу выполняет mktemp. Каталог выбирается в соответствии с содержимым переменной TMPDIR. Ключ -q указывает не выводить сообщения об ошибках, в случае неудачи при создании файла произойдет выход из скрипта с кодом завершения 1. Ключ -t задает шаблон, к которому в качестве расширения добавляется случайная последовательность символов.
Способ, которым инициализируется переменная tmpfile, называется подстановкой – переменной присваивается вывод команды, заключенной в обратные кавычки. Код завершения команды mktemp используется в дальнейшем. Последовательность || здесь означает ИЛИ (не путать с регулярными выражениями). То есть если mktemp завершится неудачно, будет запущена вторая команда, в данном случае «exit 1».
Другой часто применяемый способ подстановки – использование конструкции «$()» вместо прямых кавычек. Здесь он используется для инициализации переменной «$bindir».
Код завершения – это число, которое равно нулю, если программа завершается успешно или больше нуля, если неудачно. Число это содержится в переменной «$?».
Явно эта переменная используется в скрипте, когда нужно поменять местами номера 0 и 1. Проверка на эквивалентность (-eq) единице с помощью команды test дает требуемое значение. Кстати, все ключи команды test являются аббревиатурами, что помогает их запомнить.
Код завершения – это именно то, на чем работают в скриптах все управляющие структуры. К примеру, в часто встречающемся [ ] первая скобка – не что иное, как жесткая ссылка на test, а вторая скобка – завершающий параметр этой программы, который работает, когда она вызывается именно как [. Команда [ может быть и встроена в оболочку, но сути дела это не меняет.
Таким образом, конструкцию [ ] или test используют для анализа строк и числовых выражений, а во всех остальных случаях используется непосредственно завершающий код какой-либо программы. Более подробно об использовании test можно прочитать на соответствующей странице руководства.
Отсюда следует важное отличие от C: если там «истинным» в управляющих конструкциях считается выражение больше нуля, то в скриптах это, напротив, нуль, поскольку именно он является индикатором успешного завершения программы.
Вместе с тем синтаксис управляющих конструкций в целом похож. Сильно отличается разве что цикл for, в котором присутствует конструкция «variable in word ...», позволяющая одинаково легко пройтись по всем файлам каталога, заменив «word» на «*» (при этом «variable» последовательно принимает значение имени каждого файла), или, как в нашем случае, перебрать все поля списка, записанного в переменную.
Выражение типа [ $string ] истинно, если строка не пустая. Цикл с [ ! ] в качестве параметра бесконечный, прервать его можно командой break.
Пример непосредственного использования кода завершения программы – запуск ps с параметром номера родительского процесса, $PPID. После остановки родительского процесса (ps не может его найти) скрипт завершается.
И наконец, eval служит здесь для объединения вывода двух скриптов в один поток для последующей передачи tee, которая дублирует входной поток в указанный файл и в выходной поток, который diff затем сравнивает с ранее сохраненным файлом, показывая только изменения.
В скрипте count_pppd список пользователей, полученный от who, преобразуется к виду «user ttyname», который затем в цикле передается read. Назначение read – считывать в переменные строки, разделителями полей в которых служат пробелы (или содержимое переменной IFS, если она установлена).
Для того чтобы использовать такую переменную в awk, необходимо сначала присвоить ее значение внутренней переменной awk, конструкция user=$user означает именно это. Ключ -F меняет значение разделителя полей по умолчанию.
Несколько более сложная конструкция «if(substr ($2,1,4)=="cuaa")» используется, чтобы выбрать пользователей, зашедших по удаленному доступу. В данном случае именно с помощью awk сделать это проще всего, тогда как для grep потребовалось бы довольно сложное регулярное выражение.
В качестве проверки каждый из приведенных скриптов можно запустить по отдельности, результаты своей работы они выдают на стандартный вывод. Скрипт count_user реализует требуемый протокол и отвечает на вопросы «когда, кто, откуда». Это само по себе уже представляет некоторый интерес (не нужно анализировать логи отдельных приложений, чтобы ответить на этот вопрос), но в первоначальной постановке задачи основным вопросом было «сколько». Для ответа на этот вопрос недостаточно просто добавить правила в таблицу ipfw, потребуется организовать взаимодействие между ipfw и ipa.
В простейшем случае направление подсчета будет только одно, а значит, достаточно одного правила ipa с одним правилом ipfw внутри (их может быть и больше) на одного пользователя. Очень часто это направление подсчета – входящий трафик из внешней сети к пользователю (количество выкачанных страничек, музыки и проч.).
В расчете на одного пользователя эта часть конфигурации ipa получится такой:
rule username {
ipfw = 32001
}
Номер около 32000 взят неслучайно. Всего в ipfw может быть до 65535 правил (последнее – всегда правило по умолчанию) и этот номер находится примерно посередине таблицы.
Скрипт, организующий взаимодействие между ipfw и ipa, в основном выполняет следующую функцию: сопоставление имени пользователя (а значит, и имени правила ipa) с номером правила ipfw. Поскольку конфигурация ipa содержит такое сопоставление, логично производить его на основе этой конфигурации, то есть на ее основе при известном имени пользователя получать его номер.
#!/bin/sh
bindir=$(dirname $0) || exit 1
connect="$bindir/count_user"
#connect="$bindir/count_client 192.168.0.1 27777"
count_fw=ipfw
count_prog=ipa
count_startnum=32000
logfile="/var/log/utcount.log"
export count_startnum
if [ -f /var/run/count.pid ] && ps `cat /var/run/count.pid` >/dev/null 2>&1
then
echo "Another count process already run. Stop it and try again." >&2
exit 1
fi
trap : 1
trap : 2
trap : 15
set -T
trap "rm -f /var/run/count.pid; exit" 1 2 1
echo -n $$ > /var/run/count.pid || exit 1
$connect | while read action username address
do
if echo "$action $username $address" | grep -E -v -q "^[+-] [a-z].* ([0-9]{1,3}.){3}[0-9]{1,3}$"
then
continue
fi
usernum=`$bindir/count_$count_prog $action $username`
$bindir/count_$count_fw $action $usernum $address
echo "[`date "+%d.%m.%y %H:%M"`] $action $username $address $usernum" >> $logfile
done
Назовем этот скрипт count_gate.
От скрипта, собирающего данные о пользователях, они в цикле передаются read через канал, к которому можно подключить этот скрипт как непосредственно, так и через tcp-клиент. Способ подключения определяет переменная «$connect».
Имя скрипта, ответственного за добавление/удаление правил, определяет переменная «$count_fw», а скрипта, осуществляющего взаимодействие с системой учета трафика переменная «$count_prog». В нашем случае это скрипты count_ipfw и count_ipa. Последний, запущенный с параметрами $action и $username, выдает номер правила, которое затем в качестве одного из параметров передается скрипту count_ipfw.
Переменная $$ содержит номер процесса скрипта. При запуске скрипт записывает ее значение в файл /var/run/count.pid. Встроив проверку на соответствие этого значения реально работающему процессу, можно предотвратить запуск более одного процесса count_user.
Прежде чем передавать данные на обработку скриптам count_ipa и count_ipfw, с помощью grep производится проверка на соответствие определенному шаблону. Получаемым данным не всегда можно доверять, особенно если они передаются по сети с другого сервера. Тем более, что все действия на стороне шлюза во внешнюю сеть производятся под root (необходимо для добавления/удаления правил ipfw).
Собранные данные вместе с датой и временем записываются в лог-файл.
Скрипт count_ipa, получив имя пользователя, должен выдать номер правила ipfw. Задача эта не самая простая, но ее можно сильно облегчить, если наложить на файл конфигурации некоторые ограничения. Во-первых, выделить файл конфигурации пользователей в отдельный файл и подключить его соответствующей директивой:
include {
file(?) = /usr/local/etc/ipa.users
}
Во-вторых, на каждого пользователя должно приходиться по три строки, а номера правил должны последовательно возрастать на 1 с каждым правилом.
Два последних ограничения довольно необычны, но чтобы их снять, потребовалось бы существенно усложнить скрипт. Соблюдать же их просто: достаточно не редактировать файл вручную, а предоставить это скрипту:
#!/bin/sh
ipa_users="/usr/local/etc/ipa.users"
username_prefix="_"
action=$1 ; username=$2
ipa_add_user ()
{
username=$1 ; usernum=$2
printf "rule $username_prefix$username { ipfw = $usernum } " >> $ipa_users
if [ -r /var/run/ipa.pid ] ; then
kill -HUP `cat /var/run/ipa.pid` >/dev/null 2>&1
sleep 5
fi
}
if [ -f $ipa_users ] ; then
startline=`grep -n "^rule $username_prefix$username " $ipa_users | awk -F: "{print $1}"`
if [ $startline ] ; then
usernum=`head -n $(expr $startline + 1) $ipa_users | tail -n 1 | awk -F= "{print $2}"`
else
usernum=`expr $count_startnum + $(wc -l < $ipa_users) / 3 + 1`
ipa_add_user $username $usernum
fi
else
usernum=`expr $count_startnum + 1`
ipa_add_user $username $usernum
fi
if [ "$action" = "-" ] ; then
/usr/local/sbin/ipa -k dump >/dev/null 2>&1
fi
echo $usernum
Если файл конфигурации еще не существует, он создается и добавляется правилом с номером «$count_startnum + 1». Если же файл существует, то происходит проверка на наличие в нем пользователя с именем «$username», и в случае, если такой пользователь есть, выбирается имя соответствующего правила. Если пользователя нет, он добавляется с номером правила, вычисленным на основе числа строк в файле (очень просто, но именно это накладывает описанные выше ограничения). Переменная $username_prefix добавляется в файл конфигурации перед именем пользователя, то есть если она равна «_», то название правила изменится с username на _username, что существенно облегчит выборку и сортировку результатов подсчета трафика командой ipastat.
Непосредственное взаимодействие с ipa производится в двух случаях: когда добавляются пользователи, ipa посылается сигнал HUP, а когда правило удаляется из таблицы ipfw, происходит сброс накопленной информации командой «ipa -k dump».
В приведенном только что скрипте главное – не запутаться в переменных. Скрипту в качестве параметров передаются «$action $username», которые внутри него становятся соответственно $1 и $2. Так же ведет себя функция ipa_add_user, которая располагает переданные «$2 $usernum» в порядке поступления, $1 и $2. Внутренние переменные awk обозначаются так же, но содержат поля входного потока. Чтобы частично устранить путаницу, сначала переменным даются соответствующие алфавитные имена, а затем уже они используются по назначению.
Функция printf позволяет форматировать текст при выводе, например добавлять в строку переносы « ». Комбинация команд grep, head и tail, где для первой номер строки вычисляется с помощью expr, позволяет выделить из файла одну строку, из которой awk выделяет номер правила. В другом варианте (правила еще нет) количество строк в имеющемся файле подсчитывает команда «wc -l».
Как только получен номер правила, остается лишь добавить его в таблицу ipfw. Всевозможные проблемы несовместимости уже имеющегося набора правил и того набора, что модифицируются скриптом count_ipfw, проявляются именно на этом этапе. Написать сразу что-то более-менее универсальное практически невозможно, поэтому скрипт рассчитан только на один стандартный тип настройки ipfw: OPEN.
Для этого нужно указать в /etc/rc.conf:
firewall_enable="YES"
firewall_type="OPEN"
Кстати, если в вашем ядре еще нет поддержки ipfw, совсем необязательно его перекомпилировать. Достаточно загрузить соответствующий модуль, и чтобы загрузка выполнялась автоматически при каждом старте системы, добавить соответствующую строку в /boot/loader.conf (за примерами – в /boot/defaults/loader.conf). Если эти манипуляции производятся на удаленном сервере, не забудьте о мерах предосторожности.
Команда «ipfw show» при настройках ядра (или модуля) по умолчанию покажет следующее:
# ipfw show
00100 0 0 allow ip from any to any via lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0 deny ip from 127.0.0.0/8 to any
65000 0 0 allow ip from any to any
65535 0 0 deny ip from any to any
|
Настройка «OPEN» предполагает добавление правила 65000, разрешающего все всем, так что неважно, с какими параметрами по умолчанию (правило 65535) собран ipfw.
Поиск совпадений в таблице ipfw прекращается сразу после первого же совпадения с правилом deny или allow. Этим можно воспользоваться, чтобы отфильтровать трафик от самого шлюза во внешнюю сеть к пользователям (правило 32000):
#!/bin/sh
fwcmd="/sbin/ipfw"
usernum=$2 ; address=$3
case $1 in
+)
action=add
if ! $fwcmd show $count_startnum >/dev/null 2>&1 ; then
$fwcmd $action $count_startnum allow ip from me to any
fi
if ! $fwcmd show $usernum 2>/dev/null | grep -q $address
then
$fwcmd $action $usernum count ip from any to $address
fi
;;
-)
action=delete
$fwcmd $action $usernum count ip from any to $address
;;
*)
exit 1
;;
esac
В результате запуска count_gate таблица ipfw приобретает такой вид:
00100 0 0 allow ip from any to any via lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0 deny ip from 127.0.0.0/8 to any
32000 0 0 allow ip from me to any
32001 0 0 count ip from any to 192.168.0.167
32002 0 0 count ip from any to 192.168.0.174
65000 0 0 allow ip from any to any
65535 0 0 deny ip from any to any
|
Конечно, можно вместо «count» написать «allow», и добавить примерно такое правило:
60000 0 0 deny ip from any to 192.168.0.0/24
В таком случае воспользоваться доступом к внешней сети смогут только авторизованные пользователи. Но будьте готовы к тому, что при любом сбое (ошибки в скрипте, неверный алгоритм...) могут начаться проблемы.
Команда «ipfw show» – неплохое средство диагностики. Во второй колонке содержится количество попавших под правило пакетов, а в третьей – количество байт (сразу после запуска обе могут быть пусты). В скрипте count_ipfw также используется эта команда, для того чтобы выяснить, существует ли соответствующее правило.
Взаимодействие скриптов может быть представлено на такой схеме:
| samba, pppd, ... | user | --> +/- user address --> | gate | ipfw + ipa |
|
Список слева можно расширить. Первое, что потребуется добавить, – скрипт, выдающий статический список имен пользователей и адресов, не входящих ни в домен Samba, ни в список пользователей удаленного доступа. Связка «ipfw + ipa» может быть заменена на любую другую, выполняющую аналогичные функции.
Таким образом, вся система учета основана на двух «каркасных» скриптах, в значительной мере абстрагированных от конкретных приложений. Общаться по выбранному протоколу эти скрипты могут как непосредственно, так и через tcp-клиент. В первом случае запускаемый count_gate запускает, в свою очередь, count_user, во втором он запускает count_client, который соединяется с сервером count_user.
Сервер не сложно организовать на основе inetd, потребуется добавить в /etc/services:
count 27777/tcp
И в /etc/inetd.conf примерно такую строку:
count stream tcp nowait nobody /usr/local/bin/count_user count
После перезапуска inetd, который выполняется командой «killall -HUP inetd», сервер заработает. Порт 27777 выбран произвольно.
Стандартный сервер inetd предоставляет упрощенный способ создания собственных серверов без написания сетевого кода. В данном случае скрипт использует стандартный вывод, как обычно, а данные через открытый inetd-сокет передаются по сети.
Клиент можно использовать любой, например, telnet или nc, установленный из пакета netcat. Его же, кстати, можно использовать в качестве сервера. Еще лучше подойдет клиент, который отслеживает состояние родительского процесса и завершает работу в случае его отсутствия (то же самое делает count_user в случае, когда его локально запускает count_gate). Это помогает избежать проблем с лишними процессами после остановки count_gate.
Для корректного подсчета трафика очень важно правильно настроить ipa. Подробные рекомендации по настройке находятся по адресу http://ipasystem.sourceforge.net, в том числе на русском. Наиболее важные параметры, которые необходимо установить, находятся в секции global. Вот пример файла /usr/local/etc/ipa.conf:
global {
maxchunk = 1G
update_db_time = 1m
append_db_time = 30m
}
include {
file(?) = /usr/local/etc/ipa.users
}
Скрипт в start/stop стиле, соответствующий tcp-клиент, а также все входящие в пакет скрипты можно найти по адресу http://utcount.sourceforge.net. На момент написания статьи последняя версия пакета 0.04.