Возможно вы искали: 'Big Top Slots'

June 04 2025 18:54:43
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96526953
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18419
• Обзор The Walking ... 18865
• Обзор DMC: Devil M... 19938
• Обзор на игру Valk... 15934
• Обзор на игру Stars! 17824
• Обзор на Far Cry 3 18014
• Обзор на Resident ... 16079
• Обзор на Chivalry:... 17574
• Обзор на игру Kerb... 18033
• Обзор игры 007: Fr... 16677
Превью о играх
• Превью к игре Comp... 18013
• Превью о игре Mage... 14517
• Превью Incredible ... 14774
• Превью Firefall 13537
• Превью Dead Space 3 16394
• Превью о игре SimC... 14786
• Превью к игре Fuse 15489
• Превью Red Orche... 15598
• Превью Gothic 3 16400
• Превью Black & W... 17417
Главная » Статьи » Разное » Сага о биллинге или считаем траффик на FreeBSD (ng_ipacct + perl+ MySQL) (freebsd isp billing traffic netgraph perl mysql)

Сага о биллинге или считаем траффик на FreeBSD (ng_ipacct + perl+ MySQL) (freebsd isp billing traffic netgraph perl mysql)

Ключевые слова: freebsd, isp, billing, traffic, netgraph, perl, mysql, (найти похожие документы)

From: Skif <http://skif.in.ua>
Newsgroups: email
Date: Mon, 22 Aug 2005 18:21:07 +0000 (UTC)
Subject: Сага о биллинге или считаем траффик на FreeBSD (ng_ipacct + perl+ MySQL)


Первая часть.

Сага о биллинге, или Считаем трафик на FreeBSD (ng_ipacct + perl+ MySQL)

(Данная статья была опубликована во 2 и 3 номерах за 2005 год журнала
"Системный администратор" http://samag.ru/)

Рано или поздно перед каждым системным администратором встает вопрос
подсчета интернет-трафика. И тут уже не важны причины - то ли проверить
провайдера, то ли проконтролировать, какой объем трафика израсходовал
подключенный пользователь и выставить счет. Конечно, систем биллинга
сейчас много. И найти их в интернете не проблема. Но многие хорошие и
гибкие системы учета трафика, как правило, дороги или имеют достаточно
сложный интерфейс, а некоторые затрудняют использование тех же SQUID или
OOPS. (фраза прокси-серверов несколько не уместна. Ибо иногда это
отражается не на них, а на ряде другого софта)

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

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

В итоге, решение создать свою систему учета перевесило все остальное.
Необходимо было всего ничего, просто посчитать сколько трафика прошло
через интерфейс.

Так всё начиналось. Развилось это в большой набор скриптов, которые не
только считали объем трафика, но и определяли локальный он или нет и
какова его доля от общего объема. Они так же позволили просмотреть все
задействованные порты и протоколы, и сколько именно трафика пришлось на
каждый из них.

Что ж, скрипты это хорошо, но без самого главного, сердца всей этой
системы, ничего бы и не было. Сердце ? ng_ipacct, автором которой
является Роман Палагин. Эта программа, если так можно выразиться,
вариация на тему ipacctd.

ipacctd работает с ipfw, а вот ng_ipacct уже с NETGRAPH плюс она
работает как модуль ядра. Почему именно ng_ipacct, а не просто ipacctd?
NETGRAPH имеет ряд преимуществ. Наверняка, многие, замечали, как
отличается объем трафика, который считаешь при помощи ipfw, и тот,
который прислал провайдер со счетом. Объясняется все достаточно просто,
ipfw отрабатывает не все пакеты, поступившие в bpf - пакетный фильтр
системы. NETGRAPH выступает в данном случае как промежуточное звено,
как маленькое кольцо, через которое проходят пакеты, считаются и
перенаправляются дальше. Одно из его преимуществ - он работает на уровне
ядра, используя минимум времени процессора и памяти. Тонкости работы и
возможности его описаны в статье "Все о NETGRAPH" Арчи Коббса (перевод
статьи на русский язык можно посмотреть на
http://www.opennet.ru/docs/RUS/netgraph_freebsd/index.html).

Мы же разберем, как установить ng_ipacct и сам NETGRAPH.

Перед тем, как делать какие либо шаги, скажу что все это протестировано
на FreeBSD 5.2.1-RELEASE-p10, 5.3-RELEASE-p4, 4.10-RELEASE-p3,
4.11-RELEASE. Стоит обратить внимание, что с переходом на 5.3 и выше
потребуется заново откомпилировать и собрать ng_ipacct. Так же это
потребуется и при каждой новой компиляции ядра(на 5-й ветке).

Таким образом, исходные данные есть. Возьмемся за netgraph. Загружать в
память его можно используя два метода: запускать нужные модули при
старте либо вкомпилировать (нет имено так) сразу же в ядро. Мне
предпочтителен последний вариант.

Делается все достаточно просто. Рассмотрим на примере для FreeBSD-4.10.

Первым делом идем в /usr/src/sys/i386/conf/ и смотрим LINT-файл:

root@ostwest :cd /usr/src/sys/i386/conf/
root@ostwest :less LINT
.............
options NETGRAPH #netgraph(4) system
options NETGRAPH_ASYNC
options NETGRAPH_BPF
options NETGRAPH_CISCO
options NETGRAPH_ECHO
options NETGRAPH_ETHER
options NETGRAPH_FRAME_RELAY
options NETGRAPH_HOLE
options NETGRAPH_IFACE
options NETGRAPH_KSOCKET
options NETGRAPH_L2TP
options NETGRAPH_LMI
# MPPC compression requires proprietary files (not included)
#options NETGRAPH_MPPC_COMPRESSION
options NETGRAPH_MPPC_ENCRYPTION
options NETGRAPH_ONE2MANY
options NETGRAPH_PPP
options NETGRAPH_PPPOE
options NETGRAPH_PPTPGRE
options NETGRAPH_RFC1490
options NETGRAPH_SOCKET
options NETGRAPH_TEE
options NETGRAPH_TTY
options NETGRAPH_UI
options NETGRAPH_VJC
.............


То есть опций достаточно много и есть из чего выбрать. Для избежания
проблем с разного рода устройствами можно их все включить в наше ядро,
но в самом простом случае (считаем только с (нет это обозначает что ?с
такого типа? то есть предлог уместен) ethernet устройства) нам
потребуются только такие опции в ядре:

options NETGRAPH
options NETGRAPH_ETHER
options NETGRAPH_SOCKET
options NETGRAPH_TEE


Дальнейшие наши действия заключаются в компиляции ядра:

root@ostwest :config SKIF
Don't forget to do a ``make depend''
Kernel build directory is ../../compile/SKIF
root@ostwest : cd ../../compile/SKIF && make depend && make && make install && make clean && rehash


Объясним, что же тут сделано. Первая команда:

config SKIF - конфигурирование файла ядра, в моем случае это SKIF
Если ошибок в файле не было выявлено, то она выдаст такое:

Don't forget to do a ``make depend''
Kernel build directory is ../../compile/SKIF


Это маленькое напоминание о том, что необходимо сделать make depend и
где это сделать.

cd ../../compile/SKIF && make depend && make && make install && make clean && rehash
- это полный список команд, необходимый для того, что бы
перейти и скомпилировать наше ядро. Достаточно удобный, если никаких
ошибок не ожидается, но, если возникнут, то выяснить на каком этапе они
произошли будет проблематично. Посему, команды лучше выполнять по
отдельности.

После всех этих манипуляций перезагрузим сервер.

root@ostwest : shutdown -r now


В принципе эту команду можно было добавить сразу же в верхнюю строку.

После перезагрузки мы получаем чистое ядро с поддержкой NETGRAPH.

Что ж, часть работы выполнена. Устанавливаем ng_ipacct. Первым делом
смотрим порты, имеющиеся в системе. Там присутствует только ipacct:

root@ostwest :cd /usr/ports/
root@ostwest :make search key=ipacct
Port: ipacctd-1.46
Path: /usr/ports/net-mgmt/ipacctd
Info: IP accounting using divert socket
Maint: skv@FreeBSD.org
B-deps:
R-deps:
root@ostwest :


Сам же ng_ipacct можно найти здесь:

ftp://ftp.wuppy.net.ru/pub/FreeBSD/local/kernel/ng_ipacct/


На сервере присутствуют версии как для четвертой ветки FreeBSD, так и
для пятой. Они неидентичны, так как реализация NETGRAPH в этих версиях
FreeBSD заметно отличается. Основное отличие - синхронизация. В RELENG_4
она осуществляется через уровни прерываний, о которых можно почитать в
man 9 spl. Весь код netgraph должен выполняться на уровне splnet.

Все граничные ноды, осуществляющие связь между netgraph и другой
подсистемой, например ng_ether ,переходят в уровень splnet перед тем как
отправить данные в граф. Если это невозможно, то данные ставятся в
очередь и позже раздаются в нужной последовательности. Любые внешние
вызовы, которые работают с netgraph, тоже должны первым делом вызывать
splnet(). Таким образом, в одну единицу времени может существовать
только один контекст выполнения netgraph и конфликтовать ему не с кем.

В RELENG_5 ядро многонитевое(multithreads) и синхронизация netgraph
осуществляется с помощью мьютексов (блокировок, используемых для
реализации гарантированной исключительности) и атомарных операций. Ноды
передают друг другу объекты (items) различных типов: данные (mbufs),
сообщения (ng_mesg), ссылки на функции. У объекта есть атрибут - reader
или writer.

Нода может одновременно обрабатывать сколько угодно reader items или
только одну writer item. По умолчанию объекты с данными - readers, а все
остальные writers. Однако это можно указать как на уровне конкретных
объектов, так и на уровне хуков(hooks).

Важным является то, что в момент, когда выполняется код внутри ноды,
тред не держит ни одного мьютекса, что позволяет граничным нодам
вызывать методы других подсистем избегая LOR(Lock order reversal ?
блокирования устанавливаемых изменений).

То есть, это грозит нам как минимум тем, что один и тот же ng_ipacct не
будет работать на разных ветках FreeBSD.

Что ж, скачиваем и распаковываем.

root@ostwest : tar xfvz ng_ipacct-20040109.tar.gz
root@ostwest :cd ng_ipacct/
root@ostwest :make && make install && make clean && rehash


Ничего особо сложного здесь нет и программа без особых проблем
проинсталируется. В принципе это и все, что было необходимо для
установки ng_ipacct. В комплекте к ней идут четыре скрипта, которые
объясняют, как запустить программу для подсчета трафика и как
остановить. Готовый скрипт для запуска и остановки: ng_ipacct_init.sh,
он находиться в распакованной папке ng_ipacct/script. Этот скрипт,
слегка подкорректировав, можно смело поместить /usr/local/etc/rc.d/

Все что нужно в нем прописать это:
прослушиваемые интерфейсы INTERFACES="ed0" - здесь это будет ed0. Для
того, что бы указать более одного интерфейса ? перечислите их через
запятую.

VERBOSE=1 - уровень расширенного вывода статистики, по умолчанию в
скрипте 1, которая выведет нам дополнительно кроме IP-адреса источника и
назначения количества пакетов и байт, еще и порты и протоколы, которые
использовались. Стоит обратить внимание, что названия протоколов, если
указан расширенный вывод(VERBOSE=1), будут отображены в числовом, а не
буквенном виде. Что значит каждый номер, можно посмотреть в
/etc/protocols

THRESHOLD=50000 - количество записей, которые будут храниться программой
в памяти. На этот параметр стоит обратить особое внимание, так как
неправильно подобранный размер threshold может привести к потери части
данных или даже к панике ядра. Это возможно по той причине, что
ng_ipacct работает на уровне ядра и ей не будет доступна полностью вся
память, имеющаяся на машине, а только малая часть, зарезервированная
непосредственно под ядро. В результате переполнения памяти выделенной
системе на ядро может произойти паника со всеми вытекающими
последствиями, как-то, в лучшем случае, остановка сервера и потеря
записей, относительно трафика прошедшего через него. Поэтому если у вас
менее 128 Mb памяти стоит себя ограничить на уровне менее 4000-5000
записей и чаще снимать статистику, чтобы не потерять нужные данные.


Для снятия статистики в ng_ipacct необходимо проделать следующее:

Передать данные в checkpoint (контрольную точку), вывести ее при помощи
show из контрольной точки и очистить контрольную точкку.

Вот так это делается для интерфейса rl0:

root@ostwest : ipacctctl rl0_ip_acct:rl0 checkpoint
root@ostwest : ipacctctl rl0_ip_acct:rl0 show
root@ostwest : ipacctctl rl0_ip_acct:rl0 clear


После show вы увидите все пакеты, которые проходили через интерфейс.
Статистика выводиться в достаточно удобном CISCO формате:

ip_источника port_источника ip_назначения port_назначения протокол пакетов байт


Обычный режим имеет несколько другой формат вывода:

ip_источника ip_назначения пакетов байт


Стоит отметить, что имеется проблема с кодировками в man ipacctctl,
просмотреть его удастся разве что в браузере. Ноэтолегко вылечить:

root@ostwest : zcat /usr/share/man/man8/ipacctctl.8.gz | nroff -man | gzip > /usr/share/man/cat8/ipacctctl.8.gz


В принципе, если вас интересует исключительно возможность поднять
ng_ipacct, то на этом можно остановиться.

Мы же проследуем дальше, ибо этого мне было мало. Мне требовалось,
чтобы все данные хранились в базе MySQL для каждого хоста и интерфейса,
разнесенные по дате и времени.

Вот теперь опишем основные требования, которые были предъявлены биллингу:

Первое:

Система должна хранить данные не только по-интерфейсно, но и по хостам.
Объясню для чего это нужно - что бы быстро разделить трафик между
разными хостами/роутерами с которых считывается статистика. При этом
количество интерфейсов различно и их наименование может совпадать (почти
везде есть rl0 или fxp0).

Второе:

База должна разделять трафик за текущий и предыдущий месяцы
самостоятельно и иметь возможность предоставить пользователю отчет за
каждый из них. Для чего это нужно? Что бы таблицы бессмысленно не
росли. Гораздо проще обработать одну маленькую за месяц, чем одну
большую за год с выборкой за месяц. Просмотр статистики за предыдущие
месяцы, может быть необходим дляотчета перед начальством или выставления
счета клиенту, если такой имеется.

Третье:

В случае недоступности MySQL-сервера необходимо хранить полученные
данные локально до тех пор, пока не будет устранена причина
недоступности сервера базы данных. После чего данные автоматически
должны быть перенесены в базу при следующем сеансе.

Четвертое:

Единый конфигурационный файл с удобным и интуитивно понятным
содержанием.

Пятое:

Графический или web-интерфейс, для удобоваримого отображения статистики.

Шестое:

Неплохо было, что бы система, где необходимо, отличала локальный трафик
от внешнего.

В принципе этот список можно продолжить, но, как по мне, выше
приведенные требования являются ключевыми.

Итак, требования перечислены. Создадим, исходя из этого, наш
конфигурационный файл. Все свои скрипты и программы я размещаю в папки
расположенные в /usr/local/script . Если у вас такой нет, рекомендую
создать. Если у вас путь будет отличен от моего, тогда внесите
необходимые коррективы.

Итак, создаем рабочую папку со скриптами:

root@ostwest : mkdir -p /usr/local/script/ng_stat
root@ostwest : chown skif:wheel /usr/local/script/ng_stat


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

skif@ostwest : mkdir /usr/local/script/ng_stat/etc
skif@ostwest : mkdir /usr/local/script/ng_stat/bin


Этим мы создали папки, где будут лежать наши конфигурационные и
исполняемые файлы.

Что ж создадим конфигурационный файл и внесем первые параметры. По мере
продвижения мы будем дополнять его нужными параметрами.

skif@ostwest : cd /usr/local/script/ng_stat/etc


Здесь мы создадим файл настройки ng_stat.conf и внесем следующие строки.

# Имя сервера, где находиться база данных статистики
server_db = freebsd
# Имя базы данных, где будет сохраняться статистика
db_name = ng_stat
# Имя пользователи для доступа к базе
db_user = nguser
# Пароль для доступа к базе
db_pass = rfn.if
# Имя хоста с которого снимается статистика
listen_host = freebsd2
# Имена интерфейсов, которые прослушиваются на компьютере.
# Указывать через запятую
listen_interfaces = rl0


Думаю пояснений к строкам приведенного конфигурационного файла не нужно.

Итак, первым делом откажемся от поставляемого в комплекте с ng_ipacct
скрипта для его старта и остановки. Лучшенапишемсвой

skif@ostwest : cd /usr/local/script/ng_stat/bin
skif@ostwest : touch ng_stat_start.pl


Данный скрипт будет служить нам скелетом для последующих, и мы будем
частенько от него отталкиваться.

Итак первое что мы сделаем это объявим основной набор переменных:

#!/usr/bin/perl -w
#########################
# Список основных переменных
#########################
my $serverdb = "test";
my $dbname = "test";
my $dbuser = "test";
my $dbpass = "test";
my $table_auth = "test";
my $table_proto = "test";
my $listen_host = "test";
my @listen_interf;


Все переменные созвучны описанным в конфиге и являются глобальными для
данного файла. Внеся заранее значение "test" в них, мы избежали проблемы
получить в самом не подходящем месте undef. Но обратите внимание что,
прослушиваемые интерфейсы обозначены не переменной, а массивом. Сделано
это потому, что интерфейсов может быть несколько, а не один. Вот мы и
используем массив.

Почему были внесены такие непонятные значения переменных? Объясняется
все достаточно просто. Во-первых, сюда можно внести значения реальных
данных по умолчанию, которые будут считываться. Во вторых, если на этапе
отладки будут проблемы ? изменив значения, вы сможете выяснить, с какой
переменной у вас непорядок и где.

Теперь откроем конфигурационный файл и прочитаем значения наших
переменных:

open (CONFIG, "/usr/local/script/ng_stat/etc/ng_stat.conf");
while (<CONFIG>) {

}
close (CONFIG);


Этими строками открывается конфигурационный файл и, при помощи while,
полностью считывается и закрывается. Обратите внимание, что в данном
случае используется полное указание пути к файлу в явном виде, а в
последствии будем указывать его неявно, через переменные.

Что ж первое, что нам нужно сделать, это разобрать строки, которые
поочередно считывает while до тех пор, пока не дойдет до конца файла. Но
среди полезной информации конфигурационный файл несет в себе
комментарии. От них нужно избавиться. Для этого в perl имеется мощнейшие
инструменты поиска в строках/словах. Один из них - конструкция вида
m/шаблон/ограничитель, им и воспользуемся, условившись, что комментарием
будет символ # :

$comment = '#';
if(/^$comment/) {
print "Коментарийn";
}
else {
# разбор строк не ограниченных коментарием
}


Объясним конструкцию if ... else : если вначале строки присутствует
символ комментария, то на экран будет выведено сообщение "Комментарий",
в противном случае строка пойдет по else. Вывод сообщений о наличии
комментариев нам необходим только на этапе отладки. Кстати, можете
проверить, как скрипт работает, в последствии он будет
закомментирован.

Но этого мало, необходимо разобрать и полезную строку.

($param,$arg) = split("=",$_);
chomp $param;
chomp$arg;
$param =~ s/s//g;
$arg =~ s/s//g;


Для разбора использовалась функция split, которая на основе разделителя
?=?, заданного еще в конфигурационном файле, разбила все полезные строки
на две части: параметр и аргумент.Что бы избавиться от пробельных
символов используется оператор замены s/шаблон/замена/ограничитель.Так
как необходимо избавиться от пробельных символов, а не поменять их на
что-то другое, мы не используем параметр ?замена?, оставляя его пустым.
Модификатор s означает любой пробельный символ.

Перед этим были убраны из обоих переменных символы перевода строки при
помощи chomp.

Если в строке присутствуют не только символы пробела, но и табуляции
или если их несколько, то придется прибегнуть к следующей конструкции:

$param =~ s/[st]+//g;
$arg =~ s/[st]+//g;


Теперь необходимо присвоит каждой объявленной переменной ее истинное
значение, находящееся в конфигурационном файле. В этом нам поможет
конструкция следующего вида:

if ($param eq "server_db"){
$serverdb = $arg;
}


Объясним. Если левая часть полученной из файла строки соответствует
server_db (смотрим наш конфигурационный файл), то правая часть
присвоится необходимой переменной.

Но у нас же есть еще несколько значений параметра в одной из строк. Их
мы должны, предварительно разобрав, занести в массив.

Листинг приведен ниже:

#!/usr/bin/perl -w

use DBI;
use POSIX ":sys_wait_h";
#########################
# Список основных переменных
#########################
my $serverdb = "test";
my $dbname = "test";
my $dbuser = "test";
my $dbpass = "test";
my $table_auth = "test";
my $table_proto = "test";
my $listen_host = "test";
my @listen_interf;
my $iface_set = "no";
my @ng_modules;
my $ng_modules_def = "netgraph,ng_ether,ng_socket,ng_tee,ng_ipacct";
my$threshold = 5000;
#########################
# Читаем конфиг. файл.
#########################
open (CONFIG, "/usr/local/script/ng_stat/etc/ng_stat.conf");

while (<CONFIG>) {
$comment = '#';
if(/^$comment/) {
# print "Коментарийn";
}
else {
($param,$arg) = split("=",$_);
chomp $param;
chomp $arg;
my $razdel = "";
$param =~ s/[st]+/$razdel/g;
$arg =~ s/[st]+/$razdel/g;
if ($param eq "server_db"){
$serverdb = $arg;
}
if ($param eq "db_name"){
$dbname = $arg;
}
if ($param eq "db_user") {
$dbuser = $arg;
}
if ($param eq "db_pass") {
$dbpass = $arg;
}
if ($param eq "table_auth") {
$table_auth = $arg;
}
if ($param eq "table_protocols") {
$table_proto = $arg;
}
if ($param eq "listen_host") {
$listen_host = $arg;
}
if ($param eq "listen_interfaces") {
my $coma = ',';
if (defined $arg) {
$iface_set = "ok";
if ($arg ne ""){
if ($arg =~ m/$coma/ ) {
@listen_interf=split($coma,$arg);
}
else {
@listen_interf = $arg;
}
}
}
}
if ($param eq "ng_modules") {

my $coma = ',';
if ($arg =~ m/$coma/ ){
@ng_modules = split($coma,$arg);
}
else {

@ng_modules = split ($coma,$ng_modules_def);
}

}

}
}
close (CONFIG);

if (!defined $listen_interf[0]) {
print "Установите пожалуйста в режим прослушивания хотя бы один интерфейс.n";
}
else {

&check_kld_modules;
&listening;

}


Как видите, мы считали все параметры, и в случае, если интерфейс по
какой либо причине установлен не будет, то на экран будет выдано
сообщение об этом. А если все нормально, то в массив будут внесены
необходимые имена интерфейсов (например, rl0, rl1,rl2,fxp0) и, после
проверки массива @listen_interf на наличие в нем не пустых значений,
будут выполнены подпрограммы: &check_kld_modules и &listening.

Первая проверяет, какие из обязательных модулей загружены. При
необходимости, будет проведена их загрузка.

Вторая включает режим прослушивания интерфейсов.

Рассмотрим первую.

subcheck_kld_modules {
my @modules;
my $pid;
my $ng_module_cfg;
my $chk_ng_file = "/tmp/ng_file";
my $check_ng = 'kldstat -v | grep ng';
$check_ng = "$check_ng";# " > $chk_ng_file";
my $check_netgraph = 'kldstat -v | grep netgraph';
$check_netgraph = "$check_netgraph";#" >> $chk_ng_file";
# $pid = fork;

@modules =split ("n", `$check_ng && $check_netgraph`);
my $mod;
if (defined $modules[0]) {
foreach my $modules (@modules) {
$modules=~ s/d+//g;
if ($modules =~ s/.ko//g) {
#
}
else {
$modules =~ s/[st]+//g;
$mod = "$mod $modules ";
}
}
chop $mod;

foreach my $ng_modules (@ng_modules) {
if ($mod=~m/$ng_modules/g){
# print "$mod содержит $ng_modulesn";
}
else {
my ($pid,$kid);

$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Загрузка необходимого модуля ",$ng_modules,"n";
exec "/sbin/kldload $ng_modules > /dev/null 2>&1" or die "Ошибка загрузки модуля $ng_modules !n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;
}
}
}
else {
foreach my $ng_modules (@ng_modules) {
my ($pid,$kid);

$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Загрузка необходимого модуля ",$ng_modules,"n";
exec "/sbin/kldload $ng_modules > /dev/null 2>&1" or die "Ошибка загрузки модуля $ng_modules !n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;
}

}
}


Итак, первым делом объявляются действующие только в переделах этого
модуля массивы и переменные. В нашем случае это @modules, куда будут
заноситься все модули netgraph присутствующие в ядре или загруженные на
данный момент. $check_netgraph и $check_ng переменные, в которых
записаны команды, проверки загруженных модулей ядра.

Команда эта достаточно проста и имеет вид:

root@ostwest : kldstat -v
...............
234 dummynet
235 if_gif
236 ipfw
237 if_loop
238 ng_async
239 ng_bpf
4 1 0xc272d000 4000 ng_ipacct.ko
Containsmodules:
Name
246 ng_ipacct
....................


Как вы можете заметить вывод не маленький, поэтому пришлось его урезать.
Нам нужны не все модули, а только те, которые имеют отношение к
netgraph. Этим и займутся переменные, когда их используют как значения
для оператораexec.

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

@modules =split ("n", `$check_ng && $check_netgraph`);


Дальше пойдем по проторенному пути, а именно ? выясним, имеется в
массиве хоть какие то данные. Если полученный массив не пустой, то мы
выполним проверку, какие модули нам необходимо подгрузить для работы.

В данном случае информация о том из какого файла был загружен
модуль(linux.ko, logo_server.ko или что-то другое) не нужна. Так же не
нужны ID загруженных модулей. Для их удаления используется все тот же m//:

$modules=~ s/d+//g;


?d? означает любой цифровой символ.

После удаления ID проверяется, что присутствует в выводе, информация о
том из какого модуля загрузился файл или сам модуль. Однозначно на файл
указывает присутствие расширения ?.ko? в строке. А потому все полученные
строки, где присутствует ?.ko? подлежат удалению. В листинге вы видите,
что на месте совпадения if с ".ko" стоит комментарий. Если хотите,
можете провести синтаксически разбор и вывести на экран имя того
модуля, который был загружен вручную.

Нам же интересно только то, что находиться после else. Вывод kldstat
имеет пять колонок (Id,Refs,Address,Size,Name). Все они разделены между
собой пробельными символами. К тому же первые колонки пусты и заполнены
именно этими самыми пробельными символами. Так как нам необходима только
одна колонка Name, то необходимо удалить все пробельные символы. Для
удобства дальнейших манипуляций мы заносим отобранные элементы в одну
строку:

if ($modules =~ s/.ko//g) {
#
}
else {
$modules =~ s/[st]+//g;
$mod = "$mod $modules ";
}


Здесь необходимо остановиться и вернуться немного назад. Только что мы
получили список загруженных модулей. Это хорошо, но мало. Необходимо еще
знать, какие нам НУЖНЫ для работы и если их нет ? загрузить.

# Загружаемые модули NETGRAPH, необходимые для интерфейсов,
# которые будет обслуживать программа
# По умолчанию загружаются следующие модули: netgraph,
# ng_ether,ng_socket,ng_tee,ng_ipacct
ng_modules = netgraph,ng_ether,ng_socket,ng_tee,ng_ipacct


И соответственно считать их. Для этого нужно так же ввести еще несколько
основных переменных. Точнеемассивипеременную.

my@ng_modules;
my$ng_modules_def = "netgraph,ng_ether,ng_socket,ng_tee,ng_ipacct";


Данные из последней переменной будут загружены в массив в случае
отсутствия в конфигурационном файле хотя бы одного модуля netgraph.

Считывание необходимых к загрузке модулей нужно добавить к open ...
close(CONFIG):

if ($param eq "ng_modules") {
my $coma = ',';
if ($arg =~ m/$coma/ ){
@ng_modules = split($coma,$arg);
} else {
@ng_modules = split ($coma,$ng_modules_def);
}


Теперь у нас есть необходимый список модулей. Можем проверить, нужно
что-то загружать или нет.

Для этого необходимо проделать достаточно простую операцию. Проверить
наличие значения каждого элемента полученного массива @ng_modules в
строке $mod. Основываясь на том, есть или нет такое значение массива в
строке, и будет производиться загрузка соответствующего модуля.

foreach my $ng_modules (@ng_modules) {
if ($mod=~m/$ng_modules/g){
# print "$mod содержит $ng_modulesn";
}
else {
my $pid;
$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Загрузка необходимого модуля ",$ng_modules,"n";
exec "/sbin/kldload $ng_modules > /dev/null 2>&1" or die "Ошибка загрузки модуля $ng_modules !n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;
undef $pid;
}
}


В этом примере выполнение внешней команды ? загрузка модуля ?
производиться посредством exec. Особенностью exec является то, что по
выполнении этой функции производится останов программы и выход из
процесса. Но так как присутствует необходимость загрузить не один
модуль, то логичнее было бы использовать system. Но, по соображениям
безопасности, это произвести нельзя. Решением этой проблемы является
разделение программы на различные процессы. Для этого уже существует
функция fork.

Немного поясню, как она работает. По выполнении функции существующий
процесс разделяется на два: родительский и дочерний. Сама функция
возвращает два значения в случае удачного выполнения: номер ID для
дочернего процесса в родительский и 0 в дочерний. Почему ноль, а не
номер полученного процесса? Потому что дочерний процесс может в любой
момент времени получить ID родительского вызвав функцию getppid.
Родительский же процесс получает ID дочернего потому, что способов
узнать, из всего объема процессов, дочерний у него просто нет. Или я его
не знаю.

Возможен так же и третий вариант. Когда fork возвращает неопределенное
значение undef. Это означает, что по какой либо причине разделение на
процессы не произошло.

Так же обратите внимание на такие строчки кода:

do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;


На данном этапе они не так актуальны. Но ниже, когда будет происходить
запуск сбора статистики на интерфейсах, придерживание последовательности
выполняемых команд будет первостепенным. Что же делает этот блок?
Ключевым к нему является всего одна функция waitpid. Данная функция
аналогична в некоторой степени wait, но ждет завершения определенного
дочернего процесса с указанным ID, в данном случае полученного при fork
$pid. Функция возвращает одно из трех возможных значений:

1) PID завершенного процесса
2) 0 ? если флаги, что указаны, задают не блокирующий вызов, а процесс еще не завершен.
3) -1 ? если дочерних процессов нет.

Родительский процесс на время выполнения waitpid как бы засыпает, ожидая
результата. В итоге комбинацией fork + exec + waitpid мы добиваемся
жесткой очередности выполнения, как всех команд, так и сопутствующего
программного кода.

Вот эти особенности и использовались для запуска внешних программ.

Но проверить и загрузить нужные модули мало. Нужно еще начать собирать
статистику на интерфейсе. Для этого считываем параметр threshold из
конфигурационного файла. Следовательно, необходимо создать глобальную
переменную:

my $threshold = 5000;


Мы ее создали и присвоили значение 5000 строк, по умолчанию. В
конфигурационном файле можно задать и другое значение.

# Отнеситесь внимательно к выбору этого параметра. Он
# указывает сколько записей будет храниться в буфере
# По умолчанию значение равно 5000, но если у вас меньше
# 128 Мегабайт памяти - уменьшите его. Значение во многом
# зависит от того, какая полоса пропускания на вашем канале
# и от того на сколько он загружен. Для 128k и 64 Мб можно будет
# смело установить и 10000 записей, при условии снятия
# cтатистики хотя бы раз в 15-20 минут. Для канала в 2 Мбита
# этого времени будет уже через чур много
threshold = 5000


Задали. Теперь считаем параметр из файла:

if ($param eq "threshold") {
$threshold = $arg;
}


Все. Основные переменные заданы, конфигурационный файл на данном этапе
заполнен полностью.
Что ж, приступим к запуску.

Я сразу приведу полный листинг модуля, а потом лишь поясню некоторые
моменты, ибо сам по себе модуль достаточно прост, в нем только команды
fork и exec.

sub listening{
my $pid;
$ngctl = "/usr/sbin/ngctl";
$ipacctctl = "/usr/local/sbin/ipacctctl";
while (@listen_interf){

$interface = shift @listen_interf;
#/usr/sbin/ngctl mkpeer ${IFACE}: tee lower right
$mkpeer = "$ngctl mkpeer $interface: tee lower right";
$pid = fork;
if (defined $pid) {
($pid == 0){
print "Создание и подключение нового NETGRAPH-узла к уже существующему:n $mkpeern";
exec "$mkpeer" or die "Ошибка создания нового узла NETGRAPH!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#/usr/sbin/ngctl connect ${IFACE}: lower upper left
$connect = "$ngctl connect $interface: lower upper left";

$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Соединение двух NETGRAPH-узлов на интерфейсе:n$connectn";
exec "$connect" or die "Ошибка соединения двух NETGRAPH-узлов!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#/usr/sbin/ngctl name ${IFACE}:lower ${IFACE}_acct_tee
$name = "$ngctl name $interface:lower $interface_acct_tee ";

$pid = fork;
if (defined $pid) {
($pid == 0){
print "Присвоение имени созданному узлу:n$namen";
exec "$name" or die "Ошибка на этапе присвоения имени созданному узлу!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#/usr/sbin/ngctl mkpeer ${IFACE}_acct_tee: ipacct right2left ${IFACE}_in
$mkpeer = "$ngctl mkpeer $interface_acct_tee: ipacct right2left $interface_in";

$pid = fork;
if (defined $pid) {
($pid == 0){
print "Создание и подключение нового NETGRAPH-узла к уже существующему:n $mkpeern";
exec "$mkpeer" or die "Ошибка создания нового узла NETGRAPH!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#/usr/sbin/ngctl name ${IFACE}_acct_tee:right2left ${IFACE}_ip_acct
$name = "$ngctl name $interface_acct_tee:right2left $interface_ip_acct";

$pid = fork;
if (defined $pid) {
($pid == 0){
print "Присвоение имени созданному узлу:n$namen";
exec "$name" or die "Ошибка на этапе присвоения имени созданному узлу!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#/usr/sbin/ngctl connect ${IFACE}_acct_tee: ${IFACE}_ip_acct: left2right ${IFACE}_out
$connect = "$ngctl connect $interface_acct_tee: $interface_ip_acct: left2right $interface_out";

$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Соединение двух NETGRAPH-узлов на интерфейсе:n$connectn";
exec "$connect" or die "Ошибка соединения двух NETGRAPH-узлов!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#$IPACCTCTL ${IFACE}_ip_acct:$IFACE verbose $VERBOSE
$verbose = "$ipacctctl $interface_ip_acct:$interface verbose 1";

$pid = fork;
if (defined $pid) {
($pid == 0){
print "Установка режима вывода информации:n$verbosen";
exec "$verbose" or die "Ошибка установки режима вывода информацииn";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;

#$IPACCTCTL ${IFACE}_ip_acct:$IFACE threshold $THRESHOLD
$set_threshold = "$ipacctctl $interface_ip_acct:$interface threshold $threshold";

$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Установка THRESHOLD:n$set_thresholdn";
exec "$set_threshold" or die "Ошибка установки параметра THRESHOLDn";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;
}

}


Первые две встречающиеся переменные - исполняемые файлы для netgraph и
ng_ipacct, точнее пути к ним.

Следующим шагом является чтение из массива @listen_interf поочередно
всех занесенных туда интерфейсов и включение на них "прослушивания".

При помощи mkpeer мы создаем новый узел (nodes) к уже существующему.

При помощи connect соединяет узлы

name - присваивает имя узлу

Наиболее интересными является $ipacctctl $interface_ip_acct:$interface
verbose 1 - здесь мы задаем, в каком режиме будет отображаться
статистика. Нам необходим расширенный, посему устанавливаем значение 1.
Должен отметить, что в man ipacctctl стоит значение on - вероятнее
всего, что это ошибка ибо такое значение не влияет на формат вывода
статистики.

В последнем - $ipacctctl $interface_ip_acct:$interface threshold
$threshold - мы указываем количество записей threshold.

По ходу выполняется разветвление процессов, ожидание завершения
дочернего, с последующим обнулением $pid, куда записывалось значение ID
дочернего процесса. Здесь и всплывает важность waitpid для скрипта. Ибо
выполняться все эти команды должны именно в строгой последовательности,
а не как им заблагорассудиться.

В принципе стартовый скрипт создан.

Что в итоге получилось можно глянуть в ng_stat_start.pl

Сделав при помощи chmod файл исполняемым, можно пробовать его выполнить.
Тут поджидает первый неприятный сюрприз. Данный скрипт выполняется с
правами root. Что ж, на данном этапе можно его запустить и с такими
правами.

skif@ostwest :sudo ./ng_stat_start.pl
Password:
skif@ostwest :


Внимательно смотрите за выводом. Отсутствие ?лишнего? говорит о том, что
старт прошел без замечаний. Вот пример того, что скрипт выводит при
старте:

Загрузка необходимого модуля ng_ether

Загрузка необходимого модуля ng_socket

Загрузка необходимого модуля ng_tee

Создание и подключение нового NETGRAPH-узла к уже существующему:
/usr/sbin/ngctl mkpeer fxp1: tee lower right

Соединение двух NETGRAPH-узлов на интерфейсе:
/usr/sbin/ngctl connect fxp1: lower upper left

Присвоение имени созданному узлу:
/usr/sbin/ngctl name fxp1:lower fxp1_acct_tee

Создание и подключение нового NETGRAPH-узла к уже существующему:
/usr/sbin/ngctl mkpeer fxp1_acct_tee: ipacct right2left fxp1_in

Присвоениеимени созданному узлу:
/usr/sbin/ngctl name fxp1_acct_tee:right2left fxp1_ip_acct

Соединение двух NETGRAPH-узлов на интерфейсе:
/usr/sbin/ngctl connect fxp1_acct_tee: fxp1_ip_acct: left2right fxp1_out

Установка режима вывода информации:
/usr/local/sbin/ipacctctl fxp1_ip_acct:fxp1 verbose 1

Установка THRESHOLD:
/usr/local/sbin/ipacctctl fxp1_ip_acct:fxp1 threshold 7000

Для себя можете добавить что-либо, если необходима дополнительная
информация при запуске. Для отработки разных этапов работы скрипта
советую ввести конструкции типа print "Проверяем переменную $lin". Это
поможет проконтролировать получение значений переменными в скрипте и
получить своеобразный отладчик. Но в данном случае это полностью рабочий
скрипт, а посему весь мусор отладки убран.

Запустить мало, необходимо еще и уметь остановить.

Для этого создадим похожий на ng_stat_start.pl скрипт ng_stat_stop.pl. В
принципе, их можно было бы объединить в один, но так проще.

Итак, содержимое абсолютно идентично первому файлу, за исключением того,
что отсутствуют sub и в конструкции if else содержится следующее:

if (!defined $listen_interf[0]) {
print "Установите пожалуйста в режим прослушивания хотя бы один интерфейс.n";
}
else {

foreach my $interface (@listen_interf){
#/usr/sbin/ngctl shutdown ${IFACE}_acct_tee:
$shutdown = "$ngctl shutdown $interface_acct_tee:";
my $pid;
$pid = fork;
if (defined $pid) {
($pid == 0){
print "Отключение созданных узлов на интерфейсе:n$shutdownn";
exec "$shutdown";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;
# sleep 1;
$shutdown = "$ngctl shutdown $interface:";
$pid = fork;
if (defined $pid) {
if ($pid == 0){
print "Отключение NETGRAPH на интерфейсе:n$shutdownn";
exec "$shutdown";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;
undef $pid;
}
}


Здесь производится чтение из массива всех нужных интерфейсов и
последовательно выполняется для них отключение.

Посмотреть как он выглядит полностью можно в ng_stat_stop.pl

Теперь у нас есть скрипты для старта и остановки ng_ipacct. Но и этого
мало. Нужно сделать запуск и останов системы при включении и отключении
сервера. А посему напишем простенький скриптик на shell:

#!/bin/sh
case "$1" in
start)
/usr/local/script/ng_stat/bin/ng_stat_start.pl
echo"ng_stat"
;;
stop)
/usr/local/script/ng_stat/bin/ng_stat_stop.pl
;;
*)
echo ""
echo "Usage: `basename $0` { start | stop }"
""
;;
esac


Сохраним его под названием ng_stat.sh Когда система учета будет готова
достаточно лишь скопировать скрипт в /usr/local/etc/rc.d/ , что бы
ng_stat запустился при старте или отключился при выключении питания.

Половина дела, самая важная его часть, готова. Система стартовала там,
где надо и с нужными параметрами. Осталось за малым ? получить
статистику.

В следующей части статьи будет рассмотрено как получить статистику от
ng_ipacct и передать ее в mysql, для последующего хранения и
использования, а так же приведен пример того, как получить наши
результаты обратно.





Сага о биллинге, или Считаем трафик на FreeBSD (ng_ipacct + perl+ MySQL) часть 2

В предыдущей части статьи мы рассмотрели, как установить и запустить
ng_ipacct, а также создание своих собственных скриптов для запуска и
остановки разрабатываемой системы учета трафика.

Дальнейшая цель ? получить статистику и поместить ее в базу. Что нам для
этого нужно? В первой части статьи, когда описывался ng_ipacct,
указывалось, что для снятия статистики необходимо последовательно
проделать следующее: передать данные в checkpoint базу, потом вывести
данные при помощи show (перенаправить в файл) и очистить checkpoint, для
получения следующей порции данных.

Таким образом, мы сразу же определили, что нам нужно сложить статистику
в файл при помощи перенаправления вывода show. А после этого, уже
считывая из файла данные, отправить в базу. Для того чтобы не было
смешивания всех интерфейсов в одном файле, мы так же должны условиться
заранее, что для каждого интерефейса будет создан свой собственный файл
статистики, а так же один общий, куда будет складываться статистика со
всех интерфейсов. В этих файлах будет указанно, имя хоста, время
получения порции записей, дата и самое главное - интерфейс. Почему так
акцентируется внимание на интерфейсе? Очень просто. У нас могут быть
каналы на одной машине, где локальный трафик считается, а так же где он
бесплатный. У честь нам необходимо платный. Соответственно, нужно знать
какой интерфейс принял или отправил пакет.

Что ж основная установка сделана. Остальное - по ходу повествования.

Для начала создадим две вещи: базу, куда будут записываться данные и
папку, где будут распологаться временные файлы со статистикой
интерфейсов.

Создаем базу данных:

mysql> create database ng_stat;
Query OK, 1 row affected (0.04 sec)
mysql> grant insert,create,update,select,delete on ng_stat.* to nguser@'%' identifiedby 'ngpassword';
Query OK, 0 rows affected (0.08 sec)
mysql>


Одновременно были даны права пользователю nguser на добавление,
обновление, удаление записей и их выборку, а так же на создание таблиц.
Для чего проделано последнее? Чтобы не вести учет того, создана таблица
за текущий месяц или нет, а создавать по мере необходимости.

Итак, вновь возвращаемся к написанию скриптов:

skif@ostwest : touch ng_stat_in.pl


И начинаем вносить данные.

Первым делом необходимо подключить два модуля perl, которые будут
использоваться:

#!/usr/bin/perl -w
use DBI;
use Time::localtime;


Последний у вас должен быть по умолчанию в системе, а вот присутствие
DBI необходимо проверить. Самый простой способ - отправить на
исполнение скрипт уже в таком виде ? выдаст ошибку ? значит, нет или не
соответствует текущей версии perl (например, вы обновили perl, а все
сопутствующие модули нет). Что ж, это поправимо:

root@ostwest : cd /usr/ports/databases/p5-DBI/
root@ostwest : make && make install && make clean && rehash
root@ostwest : cd /usr/ports/databases/p5-DBD-mysql
root@ostwest : make && make install && make clean && rehash


Если у вас стоит MySQL не версии 3.23, а 4-й и выше, то выберите
соответствующий вариант, вместо p5-DBD-mysql. После этого можно смело
приступать к дальнейшим манипуляциям.

Для подключения к базе данных нужно снова считать конфигурационный файл
и все параметры необходимые для того, что бы выяснить

а) какие интерфейсы подключены,
б) имя сервера базы данных,
в) имя базы данных,
г) имя и пароль пользователя для доступа к базе.

Все это описано в конфигурационном файле (смотрите первую часть статьи).
Но сначала опять нужно задать основные переменные.

#########################
# Список основных переменных
#########################
my $serverdb = "test";
my $dbname = "test";
my $dbuser = "test";
my $dbpass = "test";
my $table_auth = "test";
my $table_proto = "test";
my $listen_host = "test";
my @listen_interf;
my @ng_modules;
my $ng_modules_def = "netgraph,ng_ether,ng_socket,ng_tee,ng_ipacct";
my $threshold = 5000;
my $ipacct_log = '/usr/local/script/ng_stat/log/ng.log';


Некоторые из них нам не потребуются. Но это удобная заготовка для всех
скриптов. Мы по очереди вносим необходимые параметры, всего лишь
модернизируя уже имеющийся скрипт. ?Лишние? переменные можно будет
убрать на этапе отладки.

Самое важное в этом списке my $ipacct_log =
'/usr/local/script/ng_stat/log/ng.log' - мы указали расположение
основного файла статистики, куда по умолчанию будет записываться ВСЯ
статистика (с интерфейсами, временем и т.д.).

Что ж, читаем дальше конфигурационный файл. Он остается без изменений,
так что приводить его не буду.

Проверяем время на машине. Именно это время и будет записываться в базу:

#########################
# Проверяем время.
#########################
$gm = localtime();
$year = ($gm->year()) + 1900;
$mounth = ($gm->mon()) + 1;
$mday = $gm->mday();
$date = "$mday-$mounth-$year";
$hour = $gm->hour();
$min = $gm->min();
$sec = $gm->sec();
$hour=sprintf("%02d",$hour);
$min=sprintf("%02d",$min);
$sec=sprintf("%02d",$sec);
$time = "$hour:$min:$sec";
$table_date = "$year_$mounth";


Почему в переменной $year мы добавляем 1900? Очень просто - она ведет
отсчет от 1900 года. Почему в месяцах прибавляем единицу - переменная
возвращает значения от 0 до 11.

Функция sprintf вернет значения переменных $hour, $sec и $min числом из
двух цифр, если полученное значение будет меньше 10. Например, одна
секунда, после получения ее значения, будет 1, а нужно 01.

Последний параметр $table_date определяет имя таблицы в базе данных.

Далее идет конструкция для проверки, установлены интерфейсы или нет.
Если все в порядке, начинаем подготовку к тому, что бы закачивать данные
на сервер.

Первым делом необходимо получить данные с интерфейсов и записать во
временные файлы.

#
while (@listen_interf){
$interface = shift @listen_interf;
my $pid;
$pid = fork;
if (defined $pid) {
if ($pid == 0){
#$IPACCTCTL ${IFACE}_ip_acct:$IFACE checkpoint
exec "/usr/local/sbin/ipacctctl $interface_ip_acct:$interface checkpoint" or die "Ошибка передачи записи в checkpoint-базу!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;

undef $pid;

$pid = fork;
if (defined $pid) {
if ($pid == 0){
#$IPACCTCTL ${IFACE}_ip_acct:$IFACE show >> $DIR/$SDIR/$NAME
exec "/usr/local/sbin/ipacctctl $interface_ip_acct:$interface show >> $ipacct_log.$interface" or die "Ошибка передачи записей из checkpoint-базы в файл!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;

undef $pid;

$pid = fork;
if (defined $pid) {
if ($pid == 0){
#$IPACCTCTL ${IFACE}_ip_acct:$IFACE clear
exec "/usr/local/sbin/ipacctctl $interface_ip_acct:$interface clear"
or die "Ошибка при очистке checkpoint-базы! nБаза не очищена. Возможно переполнение. Очистите базу в ручнуюn";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;

undef $pid;

$TMPLOG= "$ipacct_log.$interface";
open (TMPLOG, "$TMPLOG");
$TMPLOG =~ s/||`|&&|<|>//gi; #Очистка ряда символов | ` && < > из пути к файлу.
while (<TMPLOG>){
$tmp_log_line=$_;
chomp $tmp_log_line;
$tmp_log_line = "$tmp_log_line $date $time $listen_host $interface";
push @ipacct_arr,$tmp_log_line;
}
close (TMPLOG);
truncate ($TMPLOG,0);

undef $pid;
}


Обращаю внимание на то, что полный путь к ipacctctl храниться в
переменной $ipacctctl - так как скрипт будет работать по cron, то здесь
желательно указать полный путь к нему, ибо не всегда cron сможет
получить переменные из профиля того пользователя, от имени которого
будет исполняться команда или программа.

Как видите, первыми идут checkpoint, show, clear. На этапе show мы
перенаправляем данные во временный файл. Временный файл определяется
основным файлом статистики с приставкой имени интерфейса, то есть для
rl0 он будет выглядеть как /usr/local/script/ng_stat/log/ng.log.rl0 . И
так поочередно для каждого из интерфейсов.

После занесение данных эти файлы считываются. Каждая строка из них будет
дополнена необходимой информацией (дата, время, имя хоста, интерфейс) и
занесена в массив.

$tmp_log_line = "$tmp_log_line $date $time $listen_host $interface";
push @ipacct_arr,$tmp_log_line;


После того, как временный файл считан до конца, мы его очищаем (можно, в
принципе и удалить, хотя это не эффективно).

truncate(?$TMPLOG?,0);


так поочередно мы заполним данными со всех интерфейсов массив
@ipacct_arr. Его, кстати, необходимо внести в список основных
переменных, которые были объявлены в начале скрипта.

my @ipacct_arr;
my @ipacct_arr_in;


Я указал кроме него еще один массив - он сейчас тоже потребуется.

open (IPCTLOG,">>$ipacct_log");
while (@ipacct_arr){
$line_arr = shift @ipacct_arr;
$line_arr = "$line_arrn";
print IPCTLOG $line_arr;
}
close(IPCTLOG);


Этим действием все содержимое массива, полученного на предыдущем шаге,
заносится в основной файл статистики. Теперь в случае любых перипетий
(недоступность сервера, отсутствие созданной базы или неправильный
логин/пароль) вся статистика будет накапливаться в нем. Именно по этому
и было указано, то, что в случае пополнения записей они должны
дописываться в конец файла.

open (IPCTLOG,">>$ipacct_log").


Статистика получена. Теперь наступил самый ответственный этап.
Необходимо привести к нужному виду каждую строку в файле статистики.
Проверить доступность сервера и необходимой базы и таблицы. Если все в
полном порядке, то внести данные. Вот как полностью будет выглядеть этот
блок:

#
while (@listen_interf){
$interface = shift @listen_interf;
my $pid;
$pid = fork;
if (defined $pid) {
if ($pid == 0){
#$IPACCTCTL ${IFACE}_ip_acct:$IFACE checkpoint
exec "/usr/local/sbin/ipacctctl $interface_ip_acct:$interface checkpoint" or die "Ошибка передачи записи в checkpoint-базу!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;

undef $pid;

$pid = fork;
if (defined $pid) {
if ($pid == 0){
#$IPACCTCTL ${IFACE}_ip_acct:$IFACE show >> $DIR/$SDIR/$NAME
exec "/usr/local/sbin/ipacctctl $interface_ip_acct:$interface show >> $ipacct_log.$interface" or die "Ошибка передачи записей из checkpoint-базы в файл!n";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;

undef $pid;

$pid = fork;
if (defined $pid) {
if ($pid == 0){
#$IPACCTCTL ${IFACE}_ip_acct:$IFACE clear
exec "/usr/local/sbin/ipacctctl $interface_ip_acct:$interface clear"
or die "Ошибка при очистке checkpoint-базы! nБаза не очищена. Возможно переполнение. Очистите базу в ручнуюn";
exit;
}
}
else {
print "Фатальная ошибка ветвления!n.................n";
die "Разделение на процессы не возможно.n Принудительный выход из дочернего процесса: $!n";
}
do {
$kid = waitpid $pid,0;
if ($kid == -1) {
print "Дочерних процессов в системе нет или система не поддерживает их.n Ошибка!" and die "Выход!n";
} elsif ($kid == 0) {
print "Задан не блокирующий вызов и процесс еще не завершен!n";
}
} until $kid=$pid;

undef $pid;

$TMPLOG= "$ipacct_log.$interface";
open (TMPLOG, "$TMPLOG");
$TMPLOG =~ s/||`|&&|<|>//gi; #Очистка ряда символов | ` && < > из пути к файлу.
while (<TMPLOG>){
$tmp_log_line=$_;
chomp $tmp_log_line;
$tmp_log_line = "$tmp_log_line $date $time $listen_host $interface";
push @ipacct_arr,$tmp_log_line;
}
close (TMPLOG);
truncate ($TMPLOG,0);

undef $pid;
}

open (IPCTLOG,">>$ipacct_log");
while (@ipacct_arr){

$line_arr = shift @ipacct_arr;
$line_arr = "$line_arrn";
print IPCTLOG $line_arr;

}
close(IPCTLOG);

&parse_log_file;
&check_in_mysql;
&insert_data_db;
}


Как видно из кода, присутствует вызов трех подпрограмм. Выполняемые ими
функции интуитивно понятны из названия(&parse_log_file; &check_in_mysql;
&insert_data_db;).

Рассмотрим их поочередно.

sub parse_log_file {
open (PARSFILE, "$ipacct_log");
while ($line_parse=<PARSFILE>) {
chomp $line_parse;
$line_parse =~ s/[st]+/t/g;
push @ipacct_arr_in, $line_parse;
}
close (PARSFILE);
truncate ("$ipacct_log",0);
}


Все, что мы делаем здесь, - производим разбор строки основного файла. И
все имеющиеся символы пробела или табуляции заменяем на единичные
символы табуляции. И вносим данные в объявленный выше массив
@ipacct_arr_in . После того как все данные из фала были внесены в
массив, этот файл обнуляется для записи последующей порции данных.

Что ж, проверим доступность mysql и наличия таблиц:

my ($dbh,$sth,$count);
$dbh = DBI->connect("DBI:mysql:host=$serverdb;database=$dbname", "$dbuser", "$dbpass")
or &error_connection;
$sth = $dbh->prepare("SHOW tables");
$sth->execute ();


Первой строкой мы объявили переменные, которые будут использоваться для
соединения. Второй устанавливаем соединение с MySQL. В ней указываем,
что необходимо использовать драйвер mysql DBI, так же расположение
сервера, БД, имя и пароль, которые получили из файла настройки. В случае
если произойдут ошибки, будет выполнена подпрограмма &error_connection.
Ее опишем несколько позже, а пока условимся, что соединение прошло
успешно. Следующим пунктом будет запрос. В данном случае проверяется
наличие необходимых таблиц в базе (SHOW TABLES), а последняя строка
означает выполнение запроса.

Теперь полученный результат занесем в массив:

my @row;
my $tables;
while (@row = $sth->fetchrow_array) {
foreach $tables (@row){
push @dbtables, $tables;
}
}


Самое интересное во всем этом ? оператор foreach, который присваивает
переменной $table значения массива @row. Значения этой переменной
заносятся в @tables.

$crt_tbl="yes";
while (@dbtables) {
$table = shift @dbtables;
if (defined $table) {
if ($table eq $table_date) {
$crt_tbl="no";
}
}
}


В данном блоке устанавливается значение переменной $crt_tbl в yes, что
бы в случае необходимости создать таблицу, определенную в переменной
$table_date. Последующие действия как раз и описывают этап сравнения
элементов массива с переменной. Если таблица с таким именем
присутствует, то $crt_tbl принимает значение no.

if ($crt_tbl eq "yes") {
# print "Создаем таблицуn";
&crt_table_log;
}
$sth->finish;
$dbh->disconnect;


Если такой таблицы нет, она будет создана при вызове подпрограммы &crt_table_log.

В этом модуле встречаются две новые подпрограммы. Опишем первую, так
как она используется еще в нескольких местах. Итак, в случае ошибки
соединения необходимо срочно остановить выполнение скрипта и сбросить
данные обратно в файл.

sub error_connection {

print "Проверьте правильность имени и пароля на базу в MySQL, ее существованиеn";
print "Возможной причиной ошибки так же может являться то, что сервер временно не доступенn";
print "Будет произведено копирование всех данных в файл:nn$ipacct_log nn";
print "Накопление статистики в файл не лимитипровано, но это может повлечь за собой";
print " всплеск нагрузки на сеть и сервера. По этому обратите внимание на данное";
print " сообщение и выясните конкретную причину.n";
foreach $line_arr(@ipacct_arr_in) {
open (DUMPFILE, ">>$ipacct_log");
$line_arr = "$line_arrn";
print DUMPFILE $line_arr;
close (DUMPFILE);
}
die "Выход.n";
}


Вторая создает таблицу, в которую будет производиться запись данных.

sub crt_table_log {
my ($dbh,$sth,$count);
$dbh = DBI->connect("DBI:mysql:host=$serverdb;database=$dbname", "$dbuser", "$dbpass")
or &error_connection;
$select = "CREATE TABLE $table_date (ip_from varchar(255),s_port varchar(128),ip_to varchar(255),
d_port varchar(128), proto varchar(32), packets int(8), bytes int(16) default 0,
date_ins varchar(32), time_ins time,host varchar(128), interface varchar(8),
index (ip_from),index (ip_to),index (proto),index (packets), index (bytes),
index (host), index (time_ins), index (date_ins), index (interface))";

$sth = $dbh->prepare("$select");
$sth->execute ();
$sth->finish;
$dbh->disconnect;

}


Ну и наконец последнее - заносим данные в базу:

sub insert_data_db {
my ($dbh,$sth,$count);
$dbh = DBI->connect("DBI:mysql:host=$serverdb;database=$dbname","$dbuser","$dbpass")
or &error_connection_in;
$insert = "INSERT INTO $table_date (ip_from,s_port,ip_to,d_port,proto,packets,bytes,date_ins,time_ins,host,interface) VALUES (?,?,?,?,?,?,?,?,?,?,?)";
$sth = $dbh->prepare("$insert");
print "$insertn";
while (@ipacct_arr_in) {
$line_in = shift @ipacct_arr_in; ($ip_from,$s_port,$ip_to,$d_port,$proto,$packets,$bytes,$date_ins,$time_ins,$host,$interface)=split(/[st]+/,$line_in);
if (!defined $proto){
$proto="0";
}
if (!defined $packets){
$packets="0";
}
if (!defined $bytes){
$bytes="0";
}
$sth->execute ($ip_from,$s_port,$ip_to,$d_port,$proto,$packets,$bytes,$date_ins,$time_ins,$host,$interface);
}
$sth->finish;
$dbh->disconnect;
}


Как видите, снова идет пошаговое считывание данных из массива.
Полученные строки разбиваются на составляющие при помощи split. Если
значения в этих переменных отсутствуют, им присваивается значение равное
нулю.

Вот в принципе все. Мы занесли данные в таблицу.

Теперь можно извлекать нужные данные соответствующими запросами на выборку.

Полностью одержимое можно посмотреть в ng_stat_in.pl

Последние штрихи ? помещение созданного сценария в /usr/local/etc/rc.d и
добавление подобной записи в /etc/crontab

*/15 * * * * root /usr/local/script/ng_stat/bin/ng_stat_in.pl


Наша система готова к сбору статистики.

Конечно, данная система не лишена ряда недостатков. Таковыми можно
считать то, что работает она именно от пользователя root, и не имеет
возможности на лету менять конфигурацию.

Самое главное ее преимущество - простота. Она предоставляет самое
удобное хранилище данных, откуда их можно вытянуть и при помощи web, и с
консоли, и даже с машины под управлением Windows. Я думаю, оттолкнуться
от этого и поплыть (думаю лучше оставить как есть ? все такие это часто
употребляемый фразеологизм ?оттолкнуться и поплыть?, так что разночтений
не будет)сможет каждый.

Итак, были выполнены практически все требования к биллингу. Мы
остановимся пока на этом, ибо не маленький объем вышел, для трех
простеньких скриптов. Если у читателей появится желание, приведу
дополнительный набор скриптов и их описание, систему авторизации через
web-интерфейс и, если необходимо, графические клиенты под X-Window и MS
Windows.

В принципе вы и сами можете написать самые простые запросы уже сейчас.
Приведу еще раз заголовки таблицы:

mysql> show columns from 2004_10;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| ip_from | varchar(255) | YES | MUL | NULL | |
| s_port | varchar(128) | YES | | NULL | |
| ip_to | varchar(255) | YES | MUL | NULL | |
| d_port | varchar(128) | YES | | NULL | |
| proto | varchar(32) | YES | MUL | NULL | |
| packets | int(8) | YES | MUL | NULL | |
| bytes | int(16) | YES | MUL | 0 | |
| date_ins | varchar(32) | YES | MUL | NULL | |
| time_ins | time | YES | MUL | NULL | |
| host | varchar(128) | YES | MUL | NULL | |
| interface | varchar(8) | YES | MUL | NULL | |
+-----------+--------------+------+-----+---------+-------+
11 rows in set (0.00 sec)

mysql>


И простенький запрос к базе на выборку за 10 месяц 2004 года сумы
прошедшего трафика через интерфейс rl0 сервера freebsd2:

mysql> select sum(bytes) from 2004_10 where host='freebsd2' and interface='rl0';
+-----------+
|sum(bytes) |
+-----------+
|993453162 |
+-----------+
1 row in set (0.12 sec)

mysql>


Все остальные скрипты, вне зависимости от своего назначения, являются
вариациями на тему запроса.


Подведем итоги.
---------------

Разобранные выше примеры написания системы учета трафика не полноценный
биллинг, так как для такой системы нужно хорошо просчитать структуру
самой БД, ее нагрузку, выбрать оптимальные типы полей в таблицах. Для
примера, в более серьезном и нагруженнном варианте(сервер провайдера и
порядка 20 хостов), необходимо изменить типы полей s_port, d_port,
ip_from, ip_to на тип int(преобразование ip-адреса выполняется
встроенными функциями MySQL), одним словом уделить очень большое
внимание настройке оптимальной производительности самой СУБД ? здесь она
станет узким местом, и возможно перейти на альтернативную СУБД.

Но подводя итоги этой статьи можно с увереностью сказать самое главное:
ng_ipacct является очень удобный программным пакетом для сбора полной и
детализированной статистики. Причем он не требует каких-то особых
заоблочных знаний и предоставляет удобную в отображении информацию. Он
единолично может заменить вам систему учета трафика, даже если вы будете
каждый день мигрировать с одного типа брендмауера на другой. При этом
особо не нагрузит ваш сервер.

Так же можно добавить, что сам процесс сбора статистики и запись
полученных данных, средствами perl не представляет особой сложности ?
весь необходимый набор инструментов встроен в perl либо присутствует в
виде портов и пакетов.

Большую часть подпрограмм (например чтение конфигурационного файла)
можно вынести в отдельный модуль/пакет.

Так же, вполне возможно, что вы несколько перепишете код под себя ? тут
уж никто никого не сдерживает. Цель есть, а как к ней добраться ? каждый
выбирает сам.


Архив со скриптами находиться по адресу:
http://www.samag.ru/source/f0205-1.zip
а так же
http://skif.in.ua/soft/ng_stat_script.tar.gz
http://www.opennet.ru/soft/ng_stat_script.tar.gz

P.S.: Касательно скриптов расположенных на SKIF.IN.UA - эти скрипты
время от времени дорабатываются, так что там может быть разночтенние с
базовыми и приведенным в статье кодом.

P.P.S: Так как статья писалась достаточно давно, то там отображен
период, когда ng_ipacct не входил в порты, на данный момент он там
наличиствует

P.P.S: Nfr

Благодарности: Особая благодарность Глебу Смирнову (Gleb Smirnoff) из
UAFUG рассылки за подробное объяснение того как дышит netgraph. К
сожалению благодарности из статьи были вырезаны редакторами журнала и
мне было несколько не удобно за сей прискорбный факт, думаю теперь я
себя реабилитировал.
995 Прочтений •  [Сага о биллинге или считаем траффик на FreeBSD (ng_ipacct + perl+ MySQL) (freebsd isp billing traffic netgraph perl mysql)] [08.05.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Сага о биллинге или считаем траффик... Ukraine Vova 08.05.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка