URL: https://www.opennet.ru/cgi-bin/openforum/vsluhboard.cgi
Форум: vsluhforumID3
Нить номер: 13347
[ Назад ]

Исходное сообщение
"Тематический каталог: Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"

Отправлено auto_topic , 20-Янв-06 00:17 
Обсуждение статьи тематического каталога: Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)

Ссылка на текст статьи: https://www.opennet.ru/base/net/freebsd_balance.txt.html


Содержание

Сообщения в этом обсуждении
"Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"
Отправлено АКМ , 20-Янв-06 00:17 
а скрипты то че не выложил ????

"Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"
Отправлено Skif , 20-Янв-06 12:09 
В принципе имеет место быть, но все же я бы не назвал это балансировкой, или назвал бы с большой натяжкой.
А на скрипты действительно интересно посмотреть.
По поводу PHP - у меня знакомый большую часть скриптов системных пишет на php. Ниче. Не жалуется :)

"Балансировка загрузки каналов средствами FreeBSD (freebsd balance inte"
Отправлено someone , 24-Янв-06 17:44 
а как у него с временем выполнения этих самых PHP скриптов? особенно если подряд вызвать несколько штук?

На cacti,например, не от хорошей жизни придумали "скриптсервер".


"Скрипты для 'Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)'"
Отправлено Radist UA , 16-Апр-06 02:43 
Не люблю писать коментарии.

Уверен, что во многих местах можно было переписать более корректно. Но работает и так.

Не бить меня за стилистику кавычек условных ветвлений. Ну не люблю я ставить кавычки в строке с if. Да и в vi так легче работать с кодом.

Зарание прошу извинения, если где ошибка. Скрипт робочий, но єто первая версия. Сейчас у меня версия три. На два канала и один сквид. Может где что-то и пропустил когда вырезал лишнее.

У меня из-за ограничений на одном из каналов скрипт работает только для 80 порта. Но это не приципиально.

Если используется прокси, то рекомендуется использовать прокси для каждого из каналов. Так как не исключено, что для другого ай-пи сервер вернет другой контент.

Можно добавить еще задержку перед переключением из альтернативного на основной канал.

Если использовать tcpdump невозможно, например роль внешних каналов играют сквиды, то можно анализировать результат роботы sockstat. Эфективность та-же.

фаервол на шлюзе. фря
после всех проверок командой skipto 50000 all from 10.0.0.0/8 to any 80 исходящие пакеты направляюся на обработку правилами фаерфола созданного скриптами, которые анализируют внешние каналы. Входящие пакеты фря разрулит сама, ибо keep-state.

50000-52000 -  правила фаервола созданные скриптами вида

forward --gate-- tcp from --src-- to –-dst-- 80 keep-state

где
--gate-- - адрес шлюза
--src-- - адрес локальной машины
–-dst-- -  адрес удаленного сервера

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

59990   skipto 65000 tcp from any to any 80
60000   fwd шлюз_2 tcp from any to any 80 keep-state
65000   fwd шлюз_1 tcp from any to any 80 keep-state

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

59990   правило удалено
60000   fwd шлюз_2 tcp from any to any 80 keep-state
65000   fwd шлюз_1 tcp from any to any 80 keep-state

правила 60000 и 65000 не удаляем, а обходим их через skipto т.к. при удалении удяляются и все динамические правила созданные по keep-state. А такие есть всегда, т.к. при первом соединении клиента с сервером скрипт не успевает прописать соответствующее правило и срабатывает одно из последних. Тоесть скрипты добавляет правила, которые работают ТОЛЬКО при повторном и всех последующих соединениях клиента с сервером. Например при загрузке следующей странички с сервера.

сообственно сами скрипты.

запускается все это дело так.
//убиваем старыые скрипты
kill -9 `ps -waux | grep '/usr/sbin/tcpdump -tlvni xl1' | grep -v grep | awk '{print $2};'`
kill -9 `ps -waux | grep '/usr/sbin/tcpdump -tlvni tun0' | grep -v grep | awk '{print $2};'`

//запускаем новые
(/usr/sbin/tcpdump -tlvni tun0 | /usr/local/bin/php traffic_link_selector_1.php  >> log/traffic_link_selector_1.log ) &

(/usr/sbin/tcpdump -tlvni xl1 port 80| /usr/local/bin/php traffic_link_selector_2.php  >> log/traffic_link_selector_2.log ) &

скрипты

traffic_link_selector_1.php – для первого канала. У меня это tun0. Для него tcpdump без фильтров т.к. надо точно определять текущую загрузку канала
<?

$global_low_slot=50001; //границі правил для єтого интерфейса
$global_hi_slot=51000;

$global_do_select=1; //может перебрасівать траффик на второй канал манипулируя правилом 59990

require_once('traffic_link_selector.php');

?>

traffic_link_selector_2.php – для второго канала. У меня это xl1
<?

$global_low_slot=51001;
$global_hi_slot=52000;

$global_do_select=0;

require_once('traffic_link_selector.php');

?>


traffic_link_selector.php
<?

//описываем границы загрузки основного канала при превышении которых переключаемся на второй канал
//днем
$global_hi_rate=14*1024;
$global_low_rate=11*1024;
//ночью
$global_hi_rate_n=19*1024;
$global_low_rate_n=15*1024;
//границы ночи
$global_n_start=9;
$global_n_stop=17;


$global_delta_time=30; //интервал подсчета загрузки канала
$global_time_expire=150; //время жизни правила фаервола. По умолчанию, во ФРЕ ИМХО 300 секунд, если явно не закрылось соодениние


$fp=fopen('php://stdin','r'); //подключамся к stdin (tcpdump)

if (!$fp) {report('error 2');} //абшибочка вышла


$time_pre=time();

$last=true;

$global_size=0;

$global_state=0; //0-normal, 1-switched //изначальное положение – используется основний канал

$global_routing_table=array(); //массив пар клиет-сервер

$global_slots=array(); //массив свободніх слотов для фаервола

$global_next_slot=$global_low_slot; следующий свободній слот


$ipfw='/sbin/ipfw';

//*******************************************************


if ($global_do_select==1) //проводим очистку если это скрипт для основного канала
{

    exec($ipfw.' delete 59990');
    exec($ipfw.' add 59990 skipto 65000 tcp from any to any 80');


}

for ($i=$global_low_slot; $i<=$global_hi_slot; $i++) //чистим все свои правила, если есть конечно
{
    if ($i>20000) {exec($ipfw.' delete '.$i);} //заглушка >20000 что-бы не зацепить остальной фаервол при неправльной конфигурации скрипта

}


while ((!feof($fp)) or ($last)) читаем из stdin пока не получим последний пакет
{

if (feof($fp)) {$last=false;} последний пакет

$line= fgets($fp);

разбираем что нам выдал tcpdump. Осторожно с версиями. У меня tcpdump стал выдавать немного другую инфу при апгрейде с 4.7 на 4.11
Я люблю по старинке. Можно сделать то-же и с регулярными выражениями.

    $size=trim(substr($line,strpos($line,', len ')+6));
    $size=trim(substr($size,0,strpos($size,')')));

    $line_1=trim(substr($line,0,strpos($line,' > ')));
    $line_1=trim(substr($line_1,strrpos($line_1,' ')));

    $src=explode('.',trim($line_1));
    $src_ip=$src[0].'.'.$src[1].'.'.$src[2].'.'.$src[3];
    $src_port=(int)$src[4];

    $line_2=trim(substr($line,strpos($line,' > ')+3));
    $line_2=trim(substr($line_2,0,strpos($line_2,':')));

    $dst=explode('.',trim($line_2));
    $dst_ip=$dst[0].'.'.$dst[1].'.'.$dst[2].'.'.$dst[3];
    $dst_port=(int)$dst[4];


    $proto='tcp'
    if (strpos($line,'udp')>0) {$proto='udp';}
    if (strpos($line,'icmp')>0) {$proto='icmp';}
    if (strpos($line,'gre')>0) {$proto='gre';}

    $hash1=$proto.'_'.$src_ip.':'.$src_port.'>'.$dst_ip.':'.$dst_port.' -- '.$size;

    $global_size=$global_size+$size; //для определения текущей скрости

    if (($proto=='tcp') and //выбираем нужные пакеты. Можно и не только 80 порт
    (
        (($dst_port==80) and (is_attend($src_ip)))  //is_attend – истина если адрес локальный.
        or
        (($src_port==80) and (is_attend($dst_ip)))
    )
    )
    {
        

        if (is_attend($src_ip))
//сортируем источник-получатель источник – всегда локальный клиент, получатель – удаленнывй сервер. Получателя сразу преобразовываем в диапазон адресов. Можно скачать списки подсетей и и точно определять подсеть. Но как по мне – то это лишняя трата времени и ресурсов.
        {
            $src1=$src_ip;
            $dst1=$dst[0].'.'.$dst[1].'.0.0/16';
        }
        else
        {
            $src1=$dst_ip;
            $dst1=$src[0].'.'.$src[1].'.0.0/16';
        }

        $hash_mask=$src1.'_'.$dst1; //создали хеш


        if (is_array($global_routing_table[$hash_mask])) //уже такие паеты ходили? обновляем время последнего пакета
        {
            $global_routing_table[$hash_mask]['time']=time();
    
        }
        else //новый пакет
        {

            $slot=get_next_slot(); //получаем следующее свободное правило

            $chain=array();
            $chain['src']=$src1;
            $chain['dst']=$dst1;
            $chain['time']=time();
            $chain['slot']=$slot;

            $global_slots[$slot]=$hash_mask; //добавили в список правил ссылку на запись в $global_routing_table. Чтобы можно было искать но номеру правила

            $global_routing_table[$hash_mask]=$chain; //добавили в массив пар клиет-сервер (+время и номер правила)


            echo date('d.m.y H:i:s ',time()).'Add '.$dst1.' '.$slot.' ('.count($global_routing_table).')'."\n";

            if ($global_do_select==1) //выполняемся для основного канала
            {
                exec($ipfw.' add '.$slot.' forward шлюз_1 tcp from '.$src1.' to '.$dst1.' 80 keep-state ');
            }
            else //для альтернативного
            {
                exec($ipfw.' add '.$slot.' forward шлюз_2 tcp from '.$src1.' to '.$dst1.' 80 keep-state ');
            }

            // с этого момента все пакеты from '.$src1.' to '.$dst1.' будут бегать по указанному шлюзу. Таки только будут ибо они уже пошли на этот шлюз по правилу 60000 или 65000. Ибо иначе мы бы их tcpdump не увидели. Так что по только-что добавленому правилу пойдут все пакеты из новых соединений для этого сервера и клиента.

        }


    

    }

    $delta_time=time()-$time_pre;

    if (($delta_time>$global_delta_time) or (!$last)) //интервал уже минул или пакет последний? Тогда будем проверять наши правила на наличие устаревших и решать как дальше ходить пакетам
    {    
        $time_pre=time();

        //вычислеям текущую скорость

        $rate=$global_size/$delta_time;
        $global_size=0;

        echo date('d.m.y H:i:s ',time()).'Rate: '.$rate."\n";

        // второй канал не упал? Если упал, то флажка (файла) нет.
        $is_ix=file_exists('ix_selected_flag');


        if ($global_do_select==1) // мы в скрипте для основного канала?
        {


        определям пределы загзурки для основного канала

        $hi_rate=$global_hi_rate_n;
        $low_rate=$global_low_rate_n;

        if ((date('G',time())>$global_n_start) and (date('G',time())<$global_n_stop))
        {
            $day_of_week=date('w',time());
            if (($day_of_week>0) and ($day_of_week<6))
            {
                $hi_rate=$global_hi_rate;
                $low_rate=$global_low_rate;
            }
        }


        if ($global_state==0)    //мы в нормальном состояниии – пакеты уходят на основной канал
        {
            if ($rate>$hi_rate) //превышение скорости?
            {
                if ($is_ix) // второй канал не упал? переключаемся на него
                {
                    echo date('d.m.y H:i:s ',time()).'Switch to alternative'."\n";
                    $global_state=1; //меняем состояние
                    exec($ipfw.' delete 59990'); переключаемся на второй канал
                }
                else //Таки упал альтернативный канал. Жалко что не вовремя ибо нам он нужен.
                {
                    echo date('d.m.y H:i:s ',time()).'Need switch to alternative but no IX found !!!'."\n";
                }

            }
            else //превышения скорости нет. Все нормально
            {
                echo date('d.m.y H:i:s ',time()).'Normal'."\n";
            }
        }
        else    //мы в состояниии переключеного траффика на альтернативный канал
        {
            if (($rate<$low_rate) or (!$is_ix)) //если ситуация на основном нормализоваласи или альтернатывный упал
            {
                if (!$is_ix)
                {
                    echo date('d.m.y H:i:s ',time()).'No IX - switch to normal immediatly!!!'."\n";
                    exec($ipfw.' delete 60000');
exec($ipfw.' add 60000 fwd шлюз_2 tcp from any to any 80 keep-state’);
//именно убиваем чтобы умерли все динамические правила созданные keep-state
                }
                //переключаемся на основной
                echo date('d.m.y H:i:s ',time()).'Switch to normal'."\n";
                $global_state=0;
                exec($ipfw.' add 59990 skipto 65000 tcp from any to any 80');

            }
            else //на основном канале все-еще загрузка – продолжаем использовать резервный
            {
                echo date('d.m.y H:i:s ',time()).'Alternative'."\n";
            }
        }
        }
        else //мы в ветке для альтернативного канала?
        {
            if (!$is_ix) //упал альтернативный канал?
            {
                echo date('d.m.y H:i:s ',time()).'No IX - delete all routes!!!'."\n";

                foreach($global_routing_table as $key=>$value) //обнуляем время последнего доступа для все правил фаервола альтернативного канала. Правила уб'ются следующим циклом
                {
                    $global_routing_table[$key]['time']=0;
                }
                
                
            }
        }


// для все каналов
        foreach($global_routing_table as $key=>$value) // если пакеты по правилу не ходили слишком долго, то убиваем правило. Если убили правило в момент прохождения пакета, ничего страшного. Создастся новое правило для этого-же канала.
        {
            if ($value['time']+$global_time_expire<time())
            {
                $slot=(int)$value['slot'];
                echo date('d.m.y H:i:s ',time()).'Deleting '.$value['dst'].' '.$slot.' ('.count($global_routing_table).')'."\n";
                //print_r($value);
                unset($global_slots[$value['slot']]);
                unset($global_routing_table[$key]);

                exec($ipfw.' delete '.$slot.' ');

            }

        }


    }

}


print_r($global_routing_table); //на выходе из скрипта печатаем таблицу для диагностики


function get_next_slot() //выдает следующее свободное правило фаервола
{
global $global_slots;
global $global_next_slot;
global $global_low_slot;
global $global_hi_slot;

$round=2;

$next=$global_next_slot+1;

while($round>0)
{


    if ($next>$global_hi_slot)
    {
        $next=$global_low_slot;
        $round--;
    }

    if ($global_slots[$next]=='')
    {
        $global_next_slot=$next;
        return $next;
    }

    $next++;

}

$global_next_slot=$global_hi_slot;
return $global_hi_slot;

}

function is_attend($ip) истина, если ай-пи локальный клиентский
{

if (strpos(' '.$ip,'10.')==1) {return true;}


return false;

}

?>


"Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"
Отправлено Radist UA , 16-Апр-06 02:49 
Скрипты выше

"Про правильное удаление правил ipfw"
Отправлено Dyr , 09-Июн-06 15:57 
В ipfw есть замечательное средство для действий с наборами правил, на которое я нарадоваться не могу. Принцип очень прост. Сначала формируем правила файера с ключём "set" и номером этого набора(set'a). Затем, если надо обновить правила, создаём такой же список, но с другим номером set'a. И после формирования просто...меняем их местами! Операция эта практически мгновенна, и проходит абсолютно безболезненно, я с её помощью манипулирую правилами шейпера для тысячи с копейкой клиентов:

#!/bin/sh
# Удаляем временный набор скриптов, оставшийся от прошлой отработки:
/sbin/ipfw -q delete set 28

# Начинаем заполнять правилами наш списко файервола:

#========  Bandwidth 32  ================#
# Login xxx, ID = 86, IP = 85.xxx.xxx.11
/sbin/ipfw -q add 400 set 28 pipe 1086 all from any to 85.xxx.xxx.11 via em1
/sbin/ipfw -q add 400 set 28 pipe 4086 all from 85.xxx.xxx.11 to any
/sbin/ipfw -q pipe 1086 config bw 32Kbit/s
/sbin/ipfw -q pipe 4086 config bw 64Kbit/s
...
...
...
...
# Меняем местами сформированный временный набор правил (set 28) и текущий рабочий (set 4):
/sbin/ipfw -q set swap 28 4 && \
# Удаляем временный набор, в котором теперь хранятся старые правила
/sbin/ipfw -q delete set 28

### EOF ###

Без набора set'ов, простым "ipfw delete", во время отработки огромного списка ipfw, теряются все пакеты "плюшевых" адресов.


"Балансировка загрузки каналов средствами FreeBSD (freebsd balance inte"
Отправлено Kavva , 07-Дек-06 16:32 
Это работает, я в смысле кто-нибудь кроме автора пробовал?

"Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"
Отправлено Meloun , 11-Ноя-08 18:11 
еще как работает! я правда делал это без лишнего гемороя с tcpdump и лишних скриптов, а использовал всего-лишь возможности штатного фаервола FreeBSD ipfw и транслятора адресов natd. Алгоритм работы очень похож на авторский, единственное сессии равномерно "размазывались" по интерфейсам и интерфейсов внешних было 4-ре ;)

ЗЫ зачем использовать tcpdump-ы и городить кучу скриптов? Сессии и время их истечения можно автоматически отслеживать через keep-satate и check-state, адреса транслировать через несколько даемонов natd запущенных для каждого интерфейса при помощи tee.


"Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"
Отправлено nemot , 20-Окт-09 15:16 
Хощ... А где скрипты то? Дайте хоть глазком глянуть... =)

"Балансировка загрузки каналов средствами FreeBSD (freebsd balance interface ipfw)"
Отправлено ma4o , 26-Авг-10 14:24 
А они были эти скрипты то? Не видать.