Документация по ОС FreeBSD Среда, 01.05.2024, 20:09
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
Apache [58]
DNS [25]
FTP [27]
Mail [74]
Samba [24]
Squid [46]
SSH [23]
VPN [35]
РРР [20]
Net [173]

Главная » Статьи » Сеть » Net

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

        if ($modules =~ s/.ko//g) {
        #
        }
        else {
        $modules =~ s/[\s\t]+//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_modules\n";
        }
        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 $mkpeer\n";
        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$connect\n";
        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$name\n";
        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 $mkpeer\n";
        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$name\n";
        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$connect\n";
        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$verbose\n";
        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_threshold\n";
        exec "$set_threshold" or die "Ошибка установки параметра THRESHOLD\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;
            }

        }

Первые две встречающиеся переменные - исполняемые файлы для 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$shutdown\n";
        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$shutdown\n";
        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, для последующего хранения и использования, а так же приведен пример того, как получить наши результаты обратно.
Категория: Net | Добавил: oleg (15.11.2007)
Просмотров: 1009 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024